{Begin SubSec Breaking Functions and Debugging} {Title Breaking Functions and Debugging} {Text {Tag BreakingFunctions} {index *PRIMARY* Breaking functions} {index *PRIMARY* Tracing functions} {index *PRIMARY* Debugging functions} {index *PRIMARY* Function debugging} {note The break package was written by W. Teitelman.} Debugging a collection of LISP functions involves isolating problems within particular functions and/or determining when and where incorrect data are being generated and transmitted. In the Interlisp system, there are three facilities which allow the user to (temporarily) modify selected function definitions so that he can follow the flow of control in his programs, and obtain this debugging information. All three redefine functions in terms of a system function, {fn BREAK1}{index BREAK1 FN} (see {PageRef Fn BREAK1}). {fn BREAK}{index BREAK FN} ({PageRef Fn BREAK}) modifies the definition of a function {arg FN}, so that whenever {arg FN} is called and a break condition (defined by the user) is satisfied, a function break occurs. The user can then interrogate the state of the machine, perform any computation, and continue or return from the call. {fn TRACE}{index TRACE FN} ({PageRef Fn TRACE}) modifies a definition of a function {arg FN} so that whenever {arg FN} is called, its arguments (or some other values specified by the user) are printed. When the value of {arg FN} is computed it is printed also. {fn TRACE} is a special case of {fn BREAK}.{index BREAK FN} {index BREAKIN FN}{fn BREAKIN} ({PageRef Fn BREAKIN}) allows the user to insert a breakpoint {it inside} an expression defining a function. When the breakpoint is reached and if a break condition (defined by the user) is satisfied, a temporary halt occurs and the user can again investigate the state of the computation. The following two examples illustrate these facilities. In the first example, the user traces the function {lisp FACTORIAL}. {fn TRACE} redefines {lisp FACTORIAL} so that it print its arguments and value, and then goes on with the computation. When an error occurs on the fifth recursion, a full interactive break occurs. The situation is then the same as though the user had originally performed {lisp (BREAK FACTORIAL)} instead of {lisp (TRACE FACTORIAL)}, and the user can evaluate various Interlisp forms and direct the course of the computation. In this case, the user examines the variable {lisp N}, and instructs {fn BREAK1} to return {lisp 1} as the value of this cell to {lisp FACTORIAL}. The rest of the tracing proceeds without incident. The user would then presumably edit {lisp FACTORIAL} to change {lisp L} to {lisp 1}. {lispcode _PP FACTORIAL (FACTORIAL [LAMBDA (N) (COND ((ZEROP N) L) (T (ITIMES N (FACTORIAL (SUB1 N]) FACTORIAL _(TRACE FACTORIAL) (FACTORIAL) _(FACTORIAL 4) FACTORIAL: N = 4 FACTORIAL: N = 3 FACTORIAL: N = 2 FACTORIAL: N = 1 FACTORIAL: N = 0 UNBOUND ATOM L (FACTORIAL BROKEN) :N 0 :RETURN 1 FACTORIAL = 1 FACTORIAL = 1 FACTORIAL = 2 FACTORIAL = 6 FACTORIAL = 24 24 _} In the second example, the user has constructed a non-recursive definition of {lisp FACTORIAL}. He uses {index BREAKIN FN}{fn BREAKIN} to insert a call to {index BREAK1 FN}{fn BREAK1} just after the {fn PROG} label {lisp LOOP}. This break is to occur only on the last two iterations, when {lisp N} is less than {lisp 2}. When the break occurs, the user tries to look at the value of {lisp N}, but mistakenly types {lisp NN}. The break is maintained, however, and no damage is done. After examining {lisp N} and {lisp M} the user allows the computation to continue by typing {lisp OK}. A second break occurs after the next iteration, this time with {lisp N}={lisp 0}. When this break is released, the function {lisp FACTORIAL} returns its value of {lisp 120}. {lispcode _PP FACTORIAL (FACTORIAL [LAMBDA (N) (PROG ((M 1)) LOOP (COND ((ZEROP N) (RETURN M))) (SETQ M (ITIMES M N)) (SETQ N (SUB1 N)) (GO LOOP]) FACTORIAL _(BREAKIN FACTORIAL (AFTER LOOP) (ILESSP N 2] SEARCHING... FACTORIAL _(FACTORIAL 5) ((FACTORIAL) BROKEN) :NN U.B.A. NN (FACTORIAL BROKEN AFTER LOOP) :N 1 :M 120 :OK (FACTORIAL) ((FACTORIAL) BROKEN) :N 0 :OK (FACTORIAL) 120 _} {index *PRIMARY* Breaking CLISP expressions} Note: {fn BREAK} and {fn TRACE} can also be used on CLISP words which appear as {fn CAR} of form, e.g. {lisp FETCH}, {lisp REPLACE}, {lisp IF}, {lisp FOR}, {lisp DO}, etc., even though these are not implemented as functions. For conditional breaking, the user can refer to the entire expression via the variable {index EXP Var}{var EXP}, e.g. {lisp (BREAK (FOR (MEMB 'UNTIL EXP)))}. {note this variable EXP should also be mentioned in the CLISP chapter} {FnDef {FnName BREAK0} {FnArgs FN WHEN COMS {anonarg} {anonarg}} {Text Sets up a break on the function {arg FN}; returns {arg FN}. If {arg FN} is not defined, returns {lisp ({arg FN} NOT DEFINED)}. The value of {arg WHEN}, if non-{lisp NIL}, should be an expression that is evaluated whenever {arg FN} is entered. If the value of the expression is non-{lisp NIL}, a break is entered, otherwise the function simply called and returns without causing a break. This provides the means of conditionally breaking a function. The value of {arg COMS}, if non-{lisp NIL}, should be a list of break commands, that are interpreted and executed if a break occurs. (See the {arg BRKCOMS} argument to {fn BREAK1}, {PageRef Var BRKCOMS}.) {fn BREAK0} sets up a break by doing the following: (1) it redefines {arg FN} as a call to {index BREAK1 FN}{fn BREAK1} ({PageRef Fn BREAK1}), passing an equivalent definition of {arg FN}, {arg WHEN}, {arg FN}, and {arg COMS} as the {index BRKEXP Var}{var BRKEXP}, {index BRKWHEN Var}{arg BRKWHEN}, {index BRKFN Var}{arg BRKFN}, and {index BRKCOMS Var}{arg BRKCOMS} arguments to {fn BREAK1}; (2) it defines a {fn GENSYM} ({PageRef Fn GENSYM}) with the original definition of {arg FN}, and puts it on the property list of {arg FN} under the property {index *PRIMARY* BROKEN Prop}{prop BROKEN}; (3) it puts the form {lisp (BREAK0 {arg WHEN} {arg COMS})} on the property list of {arg FN} under the property {index BRKINFO Prop}{prop BRKINFO} (for use in conjunction with {index REBREAK FN}{fn REBREAK}); and (4) it adds {arg FN} to the front of the list {index BROKENFNS Var}{var BROKENFNS}. If {arg FN} is non-atomic and of the form {lisp ({arg FN1} IN {arg FN2})}, {fn BREAK0} breaks every call to {arg FN1} from within {arg FN2}. This is useful for breaking on a function that is called from many places, but where one is only interested in the call from a specific function, e.g., {lisp (RPLACA IN FOO)}, {lisp (PRINT IN FIE)}, etc. It is similar to {index BREAKIN FN}{fn BREAKIN} described below, but can be performed even when {arg FN2} is compiled or blockcompiled, whereas {index BREAKIN FN}{fn BREAKIN} only works on interpreted functions. If {arg FN1} is not found in {arg FN2}, {index BREAK0 FN}{fn BREAK0} returns the value {lisp ({arg FN1} NOT FOUND IN {arg FN2})}. {indexX {Name IN} {Type (arg to BREAK0)} {Text {lisp ({arg FN1} IN {arg FN2})}} } {indexX {Name NOT FOUND IN} {Type (value of BREAK0)} {Text {lisp ({arg FN1} NOT FOUND IN {arg FN2})}} } {fn BREAK0} breaks one function {it inside} another by first calling a function which changes the name of {arg FN1} wherever it appears inside of {arg FN2} to that of a new function, {lisp {arg FN1}-IN-{arg FN2}}, which is initially given the same function definition as {arg FN1}. Then {fn BREAK0} proceeds to break on {lisp {arg FN1}-IN-{arg FN2}} exactly as described above. In addition to breaking {lisp {arg FN1}-IN-{arg FN2}} and adding {lisp {arg FN1}-IN-{arg FN2}} to the list {index BROKENFNS Var}{var BROKENFNS}, {fn BREAK0} adds {arg FN1} to the property value for the property {index NAMESCHANGED Prop}{prop NAMESCHANGED} on the property list of {arg FN2} and puts {lisp ({arg FN2} . {arg FN1})} on the property list of {lisp {arg FN1}-IN-{arg FN2}} under the property {index *PRIMARY* ALIAS Prop}{prop ALIAS}. This will enable {index UNBREAK FN}{fn UNBREAK} to recognize what changes have been made and restore the function {arg FN2} to its original state. {note I assume that the values are ADDED to the NAMESCHANGED property, as compared to SETTING the value of that property. right?? on the other hand, the ALIAS prop of fn1-in-fn2 is SET. right??} If {arg FN} is nonatomic and not of the above form, {fn BREAK0} is called for each member of {arg FN} using the same values for {arg WHEN}, {arg COMS}, and {arg FILE}. This distributivity permits the user to specify complicated break conditions on several functions. For example, {lispcode (BREAK0 '(FOO1 ((PRINT PRIN1) IN (FOO2 FOO3))) '(NEQ X T) '(EVAL ?= (Y Z) OK) )} will break on {lisp FOO1}, {lisp PRINT-IN-FOO2}, {lisp PRINT-IN-FOO3}, {lisp PRIN1-IN-FOO2} and {lisp PRIN1-IN-FOO3}. {Note how about a simpler example} If {arg FN} is non-atomic, the value of {index BREAK0 FN}{fn BREAK0} is a list of the functions broken. }} {FnDef {FnName BREAK} {FnArgs X} {Type NOSPREAD NLAMBDA} {Text For each atomic argument, it performs {lisp (BREAK0 {arg ATOM} T)}. For each list, it performs {lisp (APPLY 'BREAK0 {arg LIST})}. For example, {lisp (BREAK FOO1 (FOO2 (GREATERP N 5) (EVAL)))} is equivalent to {lisp (BREAK0 'FOO1 T)} and {lisp (BREAK0 'FOO2 '(GREATERP N 5) '(EVAL))}. }} {FnDef {FnName TRACE} {FnArgs X} {Type NOSPREAD NLAMBDA} {Text For each atomic argument, it performs {lisp (BREAK0 {arg ATOM} T '(TRACE ?= NIL GO))}. The flag {lisp TRACE} is checked for in {index BREAK1 FN}{fn BREAK1} and causes the message "{lisp {arg FUNCTION} :}" to be printed instead of {lisp ({arg FUNCTION} BROKEN)}. For each list argument, {fn CAR} is the function to be traced, and {fn CDR} the forms the user wishes to see, i.e., {fn TRACE} performs: {lisp (BREAK0 (CAR {arg LIST}) T (LIST 'TRACE '?= (CDR {arg LIST}) 'GO))} For example, {lisp (TRACE FOO1 (FOO2 Y))} will cause both {lisp FOO1} and {lisp FOO2} to be traced. All the arguments of {lisp FOO1} will be printed; only the value of {lisp Y} will be printed for {lisp FOO2}. In the special case that the user wants to see {it only} the value, he can perform {lisp (TRACE ({arg FUNCTION}))}. This sets up a break with commands {lisp (TRACE ?= (NIL) GO)}. {note how is indenting of recursive traced functions done??} }} Note: the user can always call {index BREAK0 FN}{fn BREAK0} himself to obtain combination of options of {index BREAK1 FN}{fn BREAK1} not directly available with {index BREAK FN}{fn BREAK} and {index TRACE FN}{fn TRACE}. These two functions merely provide convenient ways of calling {fn BREAK0}, and will serve for most uses. Note: {fn BREAK0}, {fn BREAK}, and {fn TRACE} print a warning if the user tries to modify a function on the list {index UNSAFE.TO.MODIFY.FNS Var}{var UNSAFE.TO.MODIFY.FNS} ({PageRef Var UNSAFE.TO.MODIFY.FNS}). {Begin Note} Date: 8 FEB 1982 2053-PST From: KAPLAN.PA Subject: Calls/breakin glitches If you say (BREAK (FOO IN FIE)), and FOO happens to be called from a compiler generated subfunction of FIE, then no break takes place. On the other hand, if you say CALLS(FIE), the compiler generated subfunctions don't show up as being called, which makes it difficult to discover what subfunctions there are in which FOO may be broken explicitly. Seems to me that the guy that does the breaking from compiled code ought to scan subfunctions. This might also be the way it works on the 10 (I didn't check), but it is more of a problem in D because ERSETQ's generate subfunctions. Anybody understand this? {End Note} {FnDef {FnName BREAKIN} {FnArgs FN WHERE WHEN COMS} {Type NLAMBDA} {Text {fn BREAKIN} enables the user to insert a break, i.e., a call to {index BREAK1 FN}{fn BREAK1} ({PageRef Fn BREAK1}), at a specified location in the interpreted function {arg FN}. {fn BREAKIN} can be used to insert breaks before or after {fn PROG} labels, particular {fn SETQ} expressions, or even the evaluation of a variable. This is because {fn BREAKIN} operates by calling the editor and actually inserting a call to {fn BREAK1} at a specified point {it inside} of the function. If {arg FN} is a compiled function, {fn BREAKIN} returns {lisp ({arg FN} UNBREAKABLE)} as its value. {indexX {Name UNBREAKABLE} {Type (value of BREAKIN)} {Text {lisp ({arg FN} UNBREAKABLE)}} } {arg WHEN} should be an expression that is evaluated whenever the break is entered. If the value of the expression is non-{lisp NIL}, a break is entered, otherwise the function simply called and returns without causing a break. This provides the means of creating a conditional break. Note: For {fn BREAKIN}, unlike {fn BREAK0}, if {arg WHEN} is {lisp NIL}, it defaults to {lisp T}. {arg COMS}, if non-{lisp NIL}, should be a list of break commands, that are interpreted and executed if a break occurs. (See the {arg BRKCONMS} argument to {fn BREAK1}, {PageRef Var BRKCOMS}.) {arg WHERE} specifies where in the definition of {arg FN} the call to {index BREAK1 FN}{fn BREAK1} is to be inserted. {arg WHERE} should be a list of the form {lisp (BEFORE {ellipsis})}, {lisp (AFTER {ellipsis})}, or {lisp (AROUND {ellipsis})}. The user specifies where the break is to be inserted by a sequence of editor commands, preceded by one of the litatoms {index *PRIMARY* BEFORE (as argument to BREAKIN)}{lisp BEFORE}, {index *PRIMARY* AFTER (as argument to BREAKIN)}{lisp AFTER}, or {index *PRIMARY* AROUND (as argument to BREAKIN)}{lisp AROUND}, which {fn BREAKIN} uses to determine what to do once the editor has found the specified point, i.e., put the call to {index BREAK1 FN}{fn BREAK1} {lisp BEFORE} that point, {lisp AFTER} that point, or {lisp AROUND} that point. For example, {lisp (BEFORE COND)} will insert a break before the first occurrence of {fn COND}, {lisp (AFTER COND 2 1)} will insert a break after the predicate in the first {fn COND} clause, {lisp (AFTER BF (SETQ X &))} after the {it last} place {lisp X} is set. Note that {lisp (BEFORE TTY:)} or {lisp (AFTER TTY:)}{index TTY: EditCom} permit the user to type in commands to the editor, locate the correct point, and verify it, and exit from the editor with {editcom OK}. {fn BREAKIN} then inserts the break {lisp BEFORE}, {lisp AFTER}, or {lisp AROUND} that point. Note: A {index STOP EditCom}{editcom STOP} command typed to {index TTY: EditCom}{lisp TTY:} produces the same effect as an unsuccessful edit command in the original specification, e.g., {lisp (BEFORE CONDD)}. In both cases, the editor aborts, and {fn BREAKIN} types {lisp (NOT FOUND)}.{indexx {Name NOT FOUND} {Type printed by BREAKIN} {Text {lisp (NOT FOUND)}} } If {arg WHERE} is {index BEFORE (as argument to BREAKIN)}{lisp (BEFORE {ellipsis})} or {index AFTER (as argument to BREAKIN)}{lisp (AFTER {ellipsis})}, the break expression is {lisp NIL}, since the value of the break is irrelevant. For {index AROUND (as argument to BREAKIN)}{lisp (AROUND {ellipsis})}, the break expression will be the indicated form. In this case, the user can use the {index EVAL BreakCom}{breakcom EVAL} command to evaluate that form, and examine its value, before allowing the computation to proceed. For example, if the user inserted a break after a {fn COND} predicate, e.g., {lisp (AFTER (EQUAL X Y))}, he would be powerless to alter the flow of computation if the predicate were not true, since the break would not be reached. However, by breaking {lisp (AROUND (EQUAL X Y))}, he can evaluate the break expression, i.e., {lisp (EQUAL X Y)}, look at its value, and return something else if he wished. If {arg FN} is interpreted, {fn BREAKIN} types {index SEARCHING... (Printed by BREAKIN)}{lisp SEARCHING...} while it calls the editor. If the location specified by {arg WHERE} is not found, {fn BREAKIN} types {lisp (NOT FOUND)} and exits. If it is found, {fn BREAKIN} puts {lisp T} under the property {index *PRIMARY* BROKEN-IN Prop}{prop BROKEN-IN} and {lisp ({arg WHERE} {arg WHEN} {arg COMS})} under the the property {index BRKINFO Prop}{prop BRKINFO} on the property list of {arg FN}, and adds {arg FN} to the front of the list {index BROKENFNS Var}{var BROKENFNS}. {indexX {Name NOT FOUND} {Type (printed by BREAKIN)} {Text {lisp (NOT FOUND)}} } Multiple break points, can be inserted with a single call to {fn BREAKIN} by using a list of the form {lisp ((BEFORE {ellipsis}) {ellipsis} (AROUND {ellipsis}))} for {arg WHERE}. It is also possible to call {index BREAK FN}{fn BREAK} or {index TRACE FN}{fn TRACE} on a function which has been modified by {fn BREAKIN}, and conversely to {fn BREAKIN} a function which has been redefined by a call to {fn BREAK} or {fn TRACE}. }} The message typed for a {fn BREAKIN} break is {lisp (({arg FN}) BROKEN}), where {arg FN} is the name of the function inside of which the break was inserted. Any error, or typing {index control-E (Interrupt Character)}control-E, will cause the full identifying message to be printed, e.g., {lisp (FOO BROKEN AFTER COND 2 1)}. A special check is made to avoid inserting a break inside of an expression headed by any member of the list {index *PRIMARY* NOBREAKS Var}{var NOBREAKS}, initialized to {lisp (GO QUOTE *)}, since this break would never be activated. For example, if {lisp (GO L)} appears before the label {lisp L}, {fn BREAKIN} {lisp (AFTER L)} will not insert the break inside of the {fn GO} expression, but skip this occurrence of {lisp L} and go on to the next {lisp L}, in this case the label {lisp L}. Similarly, for {lisp BEFORE} or {lisp AFTER} breaks, {fn BREAKIN} checks to make sure that the break is being inserted at a "safe" place. For example, if the user requests a break {lisp (AFTER X)} in {lisp (PROG {ellipsis} (SETQ X &) {ellipsis})}, the break will actually be inserted after {lisp (SETQ X &)}, and a message printed to this effect, e.g., {index BREAK INSERTED AFTER (Printed by BREAKIN)}{lisp BREAK INSERTED AFTER (SETQ X &)}. {FnDef {FnName UNBREAK} {FnArgs X} {Type NOSPREAD NLAMBDA} {Text {fn UNBREAK} takes an indefinite number of functions modified by {index BREAK FN}{fn BREAK}, {index TRACE FN}{fn TRACE}, or {index BREAKIN FN}{fn BREAKIN} and restores them to their original state by calling {fn UNBREAK0}. Returns list of values of {fn UNBREAK0}. {lisp (UNBREAK)} will unbreak all functions on {index BROKENFNS Var}{var BROKENFNS}, in reverse order. It first sets {index BRKINFOLST Var}{var BRKINFOLST} to {lisp NIL}. {index UNBREAK FN}{lisp (UNBREAK T)} unbreaks just the first function on {var BROKENFNS}, i.e., the most recently broken function. }} {FnDef {FnName UNBREAK0} {FnArgs FN {anonarg}} {Text Restores {arg FN} to its original state. If {arg FN} was not broken, value is {lisp (NOT BROKEN)} and no changes are made. If {arg FN} was modified by {fn BREAKIN}, {index UNBREAKIN FN}{fn UNBREAKIN} is called to edit it back to its original state. If {arg FN} was created from {lisp ({arg FN1} IN {arg FN2})}, (i.e., if it has a property {index ALIAS Prop}{prop ALIAS}), the function in which {arg FN} appears is restored to its original state. All dummy functions that were created by the break are eliminated. Adds property value of {index BRKINFO Prop}{prop BRKINFO} to (front of) {index BRKINFOLST Var}{var BRKINFOLST}. {indexX {Name NOT BROKEN} {Type (value of UNBREAK0)} {Text {lisp (NOT BROKEN)}} } Note: {lisp (UNBREAK0 '({arg FN1} IN {arg FN2}))} is allowed: {index UNBREAK0 FN}{fn UNBREAK0} will operate on {lisp ({arg FN1}-IN-{arg FN2})} instead. }} {FnDef {FnName UNBREAKIN} {FnArgs FN} {Text Performs the appropriate editing operations to eliminate all changes made by {index BREAKIN FN}{fn BREAKIN}. {arg FN} may be either the name or definition of a function. Value is {arg FN}. {fn UNBREAKIN} is automatically called by {index UNBREAK FN}{fn UNBREAK} if {arg FN} has property {index BROKEN-IN Prop}{prop BROKEN-IN} with value {lisp T} on its property list. }} {FnDef {FnName REBREAK} {FnArgs X} {Type NOSPREAD NLAMBDA} {Text Nlambda nospread function for rebreaking functions that were previously broken without having to respecify the break information. For each function on {arg X}, {index REBREAK FN}{fn REBREAK} searches {var BRKINFOLST} for break(s) and performs the corresponding operation. Value is a list of values corresponding to calls to {index BREAK0 FN}{fn BREAK0} or {index BREAKIN FN}{fn BREAKIN}. If no information is found for a particular function, returns {lisp ({arg FN} - NO BREAK INFORMATION SAVED)}. {indexX {Name NO BREAK INFORMATION SAVED} {Type (value of REBREAK)} {Text {lisp ({arg FN} - NO BREAK INFORMATION SAVED)}} } {lisp (REBREAK)} rebreaks everything on {index BRKINFOLST Var}{var BRKINFOLST}, so {lisp (REBREAK)} is the inverse of {lisp (UNBREAK)}. {lisp (REBREAK T)} rebreaks just the first break on {var BRKINFOLST}, i.e., the function most recently unbroken. }} {index *PRIMARY* Editing compiled code} {FnDef {FnName CHANGENAME} {FnArgs FN FROM TO} {Text Replaces all occurrences of {arg FROM} by {arg TO} in the definition of {arg FN}. If {arg FN} is defined by an expr definition, {fn CHANGENAME} performs {lisp (ESUBST {arg TO} {arg FROM} (GETD {arg FN}))} (see {PageRef Fn ESUBST}). If {arg FN} is compiled, {fn CHANGENAME} searches the literals of {arg FN} (and all of its compiler generated subfunctions), replacing each occurrence of {arg FROM} with {arg TO}. Note that {arg FROM} and {arg TO} do not have to be functions, e.g., they can be names of variables, or any other literals. {fn CHANGENAME} returns {arg FN} if at least one instance of {arg FROM} was found, otherwise {lisp NIL}. }} {FnDef {FnName VIRGINFN} {FnArgs FN FLG} {Text The function that knows how to restore functions to their original state regardless of any amount of breaks, breakins, advising, compiling and saving exprs, etc. It is used by {fn PRETTYPRINT}, {fn DEFINE}, and the compiler. If {arg FLG}={lisp NIL}, as for {fn PRETTYPRINT}, it does not modify the definition of {arg FN} in the process of producing a "clean" version of the definition; it works on a copy. If {arg FLG}={lisp T}, as for the compiler and {fn DEFINE}, it physically restores the function to its original state, and prints the changes it is making, e.g., {lisp FOO UNBROKEN}{index UNBROKEN (Printed by System)}, {lisp FOO UNADVISED}{index UNADVISED (Printed by System)}, {lisp FOO NAMES RESTORED},{index NAMES RESTORED (Printed by System)} etc. Returns the virgin function definition. }} }{End SubSec Breaking Functions and Debugging} ?1(DEFAULTFONT 1 (GACHA 10) (GACHA 8) (TERMINAL 8)) ?1(DEFAULTFONT 1 (GACHA 10) (GACHA 8) (TERMINAL 8)) ?1(DEFAULTFONT 1 (GACHA 10) (GACHA 8) (TERMINAL 8)) YsYszē