This document is for Xerox internal use onlyJaMBY John Warnock AND Martin NewellVersion of June 6, 1980[Ivy]JaM.bravoPREFACEThis document gives a brief description of a Mesa based interactive programming environmentcalled "JaM". In this document three general topics are discussed: what is JaM; what applicationslend themselves to the use of JaM; and how JaM interacts with and is interfaced to Mesa.XEROXPALO ALTO RESEARCH CENTER3333 Coyote Hill RoadPalo Alto California 94304This document is for Xerox internal use only3pX, Sq Pzrp rp Kr Gs 7p 4s[ 3 -5 1=)Wt)`pX)`rF)`f3fpX, =dvYJaM2ContentsIntroductionBasic OperationExamplesJaM Command CatagoriesArithmetic CommandsBoolean and Relational CommandsStack Manipulation CommandsExecution Control CommandsDictionary Related CommandsArray Related CommandsInput/Output and Stream Related CommandsAttribute and Conversion CommandsScanner and String Manipulation CommandsGraphic Commands fsG'bp ]K Z  V S P Mr JG G C @ =( :n! 7B( 4:^ 2oc5JJaM3IntroductionThis document gives a brief description of a Mesa based interactive programming environmentcalled "JaM". In this document three general topics are discussed: What is JaM?; what applicationslend themselves to the use of JaM; and how JaM interacts and is used with Mesa.(comment: onlyfirst and part of the second of these is done)Basic OperationTo answer the question: "What is JaM?", several features of the system will be discussed.1.JaM implements a stack oriented, virtual machine. This machine operates on entities calledobjects. In a common sense sort of way JaM can be thought of as an extremely flexibleprogrammable Hewlett Packard type calculator that can handle a wide variety of objects in apowerful way. 2.An integral part of the JaM machine is a virtual memory that provides an address space of 224words for storage of all objects in the system.3.An interactive user interface to the JaM machine is provided. The interface is implemented as alanguage having an extremely simple syntax. Strings in this language are interpreted by ascanner and transformed into a sequence of objects that are, in turn, executed by the JaMmachine.A JaM machine basically consists of three stacks, virtual memory, and the instruction set. The threestacks are: The operand stack, the dictionary (or context) stack, and the execution stack. Undermost circumstances the user is concerned only with the operand stack. A description of thedictionary stack is put off until later, although the understanding of its function and use isimportant. The function of the execution stack need not be of direct concern to the user - it isrequired for the internal execution control of the machine.When the JaM machine is initialized the operand stack is empty, the dictionary stack contains onedictionary called the system dictionary, and the machine is executing the keyboard stream (waitingfor the user to type something.) Lines of input are passed to the JaM scanner to be parsed intotokens. Tokens are delimited by any mixture of spaces, tabs, carriage returns, and commas. Thescanner recognizes 3 classes of tokens: numbers, strings, and identifiers. It converts each token intothe relevant object, then passes it to execution control for execution by the JaM machine.To understand what happens when the user types it is important to understand what the scannerrecognizes, and what objects are generated for execution.Numbers are of three types: integers, reals, and long integers. 23 -465--Examples of integers.-23.4 1. 0.00034--Examples of reals.-123456 1000000--Examples of long integers. Strings are any sequence of characters enclosed in balanced parentheses. For example:(Hello. How are you)(strings may consistof more than one lineof information)(strings may (contain nested (balanced) parentheses)) fsG bp ^s[ ]n8+ [8% Zf. W;p Ts L P2(22O`us92M[2LX I-2KI2G/ D}2B2B>2Auus22? <I ;AR 9S 89^ 6P 51; 2 U 0I .@ -zusV +ususu s *r us" 'F] %9 "?%vw'sw sw s  Uw: 5  =]()JaM4(strings may contain all manner of garbage @#$$$%%~~~&&**~%$$1233;;;except for unbalanced parens)(Strings may contain as many as 32767 characters)An identifiers is any token that is neither a number nor a string. For example:.add.printabc$$$123aabc.111-.aaaare all examples of valid identifiers.Execution of objects has the following semantics:number:the value is pushed onto the operand stack;string:the string is pushed onto the operand stack;identifier:the object associated with the identifier is looked up using the dictionary stack as acontext (described later), and the associated object is in turn executed.ExamplesTo see how this all works, several simple examples are given.Example 1.Consider the input:123 456 .add .cvs .print1.The scanner will recognize 123 as a number, convert it to an integer object and pass it tothe JaM machine for execution, which will leave the integer value 123 on the operandstack.2.In a similar way, 456 will be pushed onto the operand stack.3.The scanner will recognize .add as an identifier. ".add" is known in the system dictionaryas a command object which, when executed will replace two numbers on the top of theoperand stack with their sum. The operand stack will now contain the integer object: 579.4.The scanner will recognize .cvs as an identifier. ".cvs" is known in the system dictionaryas a command object which, when executed will convert the object on the top of theoperand stack into its string object equivalent. i.e. the integer 579 will be converted to thestring "579", and put on the operand stack.5.In a similar way, .print is a command object which will print the string found on theoperand stack. In this case:579 fsGbwD`]1 ZfsPW;wUT3RQ+ON# Js& G12D+2Au,2>J  u sus will indicate three objects on the stack, with beingthe object on top, next, and beneath . Certain letters will be used to indicate specifictypes of objects. and will represent numbers, a boolean, a dictionary, anarray, and will represent streams; and and will represent any kind of object. Whena command returns elements, then is indicated by "=>" followed by the returned elements. Thenotation "--" is used to indicate that the command returns no elements. Commands normallyremove their arguments from the operand stack, do the operation on the arguments, and thenreturn results. Some commands, however, return their input arguments. The descriptions of thecommands will indicate how each works.There are roughly ten categories of JaM commands. These include:Arithmetic CommandsThe arithmetic commands provide for the basic arithmetic operations between mixed types ofnumbers. These commands take their operands from the operand stack, and leave the results on fsG2b ^p [s#usus2 ZC6* XP W;5, U2. T32QMw!2Jsws*uw 2I-s@ws2FBw2?swsw s2>&E2<9ww26Ks 3 .4 1&? 0O ,T +iC (=p %s&4 #A$ " "ws# ws ws wsws4 wswsws wsws }wswswsws) X u: Z mW & A p gs8" ,1 =]o5JaM7the operand stack. Included are:.add: .add => .sub: .sub => .mul: .mul => .div: .div => .neg: .neg => <-n>.cos: .cos => .sin: .sin => .atan: .atan => .exp: .exp => .log: .log => Boolean and Relational CommandsThese commands generate boolean constants by performing relational tests between numeric (orstring) objects, and boolean operations between boolean objects. Included are:.true: .true => <.true>.false: .false => <.false>.eq: .eq => <.true> if n = m otherwise <.false> (also works on strings).gt: .gt => <.true> if n > m otherwise <.false> (also works on strings).lt: .lt => <.true> if n < m otherwise <.false> (also works on strings).not: .not => <~b>.and: .and => .or: .or => .xor: .xor => Stack Manipulation CommandsThese commands provide a set of functions that allow the user to manipulate the operand stack.These functions include facilities to duplicate portions of the stack, rearrange portions of the stack,eliminate portions of the stack, and count the entries on the stack. Included are:.pop: .pop => --.copy: ... .copy => ... ... .cntstk: | ... .cntstk => | ... .roll: ... .roll => ... ....dup: .dup => .clrstk: | ... .clrstk => |.exch: .exch => Stack Marking and Mark Manipulation CommandsThese commands provide a set of functions that allow the user to mark the operand stack. Thesefunctions are used for keeping track of variable numbers of arguments on the operand stack.Included are:.mark: .mark => (this command puts a Mark type object on the operand stack.).cnttomrk: ... .cnttomrk => ... .clrtomrk: ... .clrtomrk => Execution Control CommandsThis set of commands provides for control of execution. JaM has no "go to" type of command.Instead heavy use is made of "if - else", "looping", and "select" kinds of control mechanisms. Mostexecution control commands expect objects on the operand stack. These objects are executed as afunction of other objects on the stack. For example: The .if command expects a boolean object and fsG b! _yF ] \ [] Z  X W Vg TUMT SRS Pzp MOs7% KO HyF G FHL E L CL B AR@AR@AR @AR@AR ????? ???? >=v>=v> =v>=v> :p 7sK 6KY 4S 1yF 0/0/0/0/0/0/0/0/0/0 ..M..M..M..M..M..M. -3,-3,-3,-3, -3,-3,-3,-3 + *N)*N)*N)*N ( %p, "ss_ = k &yFN  [[[[[[ A AAA Kp  s7$ K ,3 ws% l L>\JaM8any other object on the operand stack. If the boolean equals .true then the other object isexecuted, otherwise the .if command pops the object from the stack. The execution controlcommands include: .exec: .exec => -- (executes object x).if: .if => -- (if b = .true then execute x).ifelse: .ifelse => -- (if b = .true then execute x else execute y).rept: .rept => -- (execute x -- i times).loop: .loop => -- (execute x until a .exit command is executed).exit: .exit => -- (exit from the current .rept, .loop, .dictforall, .arrayforall commands).stop: .stop => -- (clear the execution stack.).singlestep: .singlestep => -- (put execution control into singlestep mode.).runfree: .runfree => -- (take execution control out of singlestep mode.).quit: .quit => -- (save virtual memory and exit to the operating system.)Dictionary Related CommandsDictionary objects are general symbol tables that may be used either as data structures or as part ofthe user's execution context.As we learned earlier, the scanner, when it encounters an identifier, looks up the identifier in thedictionaries on the dictionary stack. What actually happens in this case is quite simple. Theunknown identifier is first looked up in the dictionary on the top of the dictionary stack. If theidentifier has a value in this dictionary, then the value is returned. If the identifier has no entry inthis dictionary, then the search continues through each dictionary on the dictionary stack until theentry is found. At this point the value associated with the identifier is returned. The user hascontrol over the contents of the dictionary stack via the .begin and .end commands, and thereforethe user has control over his execution context. Later we will see how this control may be used.When dictionaries are used as data structures, they are loaded, as objects, onto the operand stack.Various commands can then operate on these objects to store new definitions, or to retrievedefinitions found in the dictionaries. Also a command exists for creating new dictionaries. In the following definitions and can be any objects, but are used to denote the "key " and"value" respectively.The dictionary related commands are:.dict: .dict => (dictionary with capacity of i entries).def: .def => -- (associates the value v with the key k in the current dictionary).del: .del => -- (deletes the key k from the dictionary d).load: .load => (loads the value associated with k in the current dictionary).store: .store => -- (finds a definition of k in the current context and replaces that definition with value v. If no definition of k exists then the definition is placed in the current dictionary.).put: .put => -- (associates value v with key k in dictionary d.).get: .get => (retrieves the value associated with k in dictionary d.).known: .known => <.true> if key k is in dictionary d <.false> otherwise..where: .where => <.true> if k is found in some dictionary d <.false> otherwise..clrdict: .clrdict => -- (clears all entries from dictionary d.).dictforall: .dictforall => -- (puts on the stack, and then executes . This is done for every k,v pair in dictionary d).begin: .begin => -- (makes d the current dictionary on the dictionary stack.).end: .end => -- (pops the current dictionary from the dictionary stack.).sysdict: .sysdict => .length: .length => (replaces the dictionary with its current number of entries)..maxlength : .maxlength => (replaces the dictionary with its size).Array Related CommandsArray objects are linear arrays of objects. Commands exist to create arrays, store into arrays,retrieve objects from arrays etc. Most of these commands either expect array objects on the fsG b.ws `< _ \y% Z0 YF XU+ W@ UW T* S_ A R"A PE Mp Jsb I- F W D}J BP AuX ?I >mI <"wsws ;e10 89F 6L 51J 2wsws / 0 -V$ *NyF: )U '= &P %XW $@ "2 !G bM %J Q < l X /% L E v 9R  @ p s S 0, >]LfJaM9operand stack or return array objects on the operand stack.In addition to their usefulness as data structures, execution of a verb array results in execution ofeach of its elements in turn. Procedures can be converted into an array form which, in some sense,corresponds to the compiled form of a procedure in other machines.The array related commands consist of:.array: .array => (new array of length i.).subarray: .subarray => (a' is the subarray of a starting at position i and withlength j.).aput: .aput => -- (store v in the ith position of a.).aget: .aget => (get v from the ith position of a.).aload: .aload => ... .astore: ... .astore => (store x1 ... xi into array a of length i.).arrayforall: .dictforall => -- (puts the contents of ai on the stack, and then executes .This is done for every ai pair in array a).length: .length: .length => (replaces the array with its length).Input/Output and Stream Related CommandsThis catagory of commands deals primarily with string objects and stream objects. There existprimitive JaM commands to create streams, execute streams, read streams, write streams, anddestroy streams. These commands include:.print: .print => -- (.prints the string s on the current output stream.).bytestream: .bytestream => (this command creates a bytestream with theaccess characteristics represented by . Here = 1 for read, 2 for write, 4 for append -- orthe sum of any of these. The created stream type objectis left on the operand stack.).wordstream: .wordstream => (this command creates a wordstream with theaccess characteristics represented by . Here = 1 for read, 2 for write, 4 for append -- orthe sum of any of these. The created stream type objectis left on the operand stack.).keystream : .keystream => (this command creates a keystream and leaves it on the operandstack.).killstream: .killstream => (this command kills the given stream.).readline: .readline => <.true> (this command reads a line from the stream).readitem: .readitem => <.true> (this command reads a item from the stream) <.false> (if no more items in the stream).writebytes: .writebytes => -- (write bytes in string s appended to stream t.).loadbcd: .loadbcd => -- (load mesa bcd and start.)Attribute and Conversion CommandsThese commands allow the user to determine the types of objects and to convert from one objecttype to another. This command set is not complete as yet, but the currently provided commandsinclude:.type: .type => (deliver the name of the type on top of operand stack.Current types include. .nulltype, .intergertype,.longintegertype, .realtype, .booleantype, .stringtype,.streamtype, .arraytype,.dicttype, .commandtype, .stacktype,.frametype, .marktype.).itype: .itype => (deliver the number of the type on top of the operandstack. Current number assignments are:nulltype = 0integertype = 1longintegertype = 2realtype = 3booleantype = 4 fsG b; ^ W ]n): [B X& UyF, TyW'S< Q; P9 ONONONO MMOMMOMMOMMOMMOM L5 W'J2C I: Etp( BIsN @; ?A) <8yFG : U'94'86'7B7'6 4 U'34'2L6'17'/ . T -V , < *Y )Y(`0 '# K %6 "p! sF .>  yFR'd0''7''$'n 1S'&' 'x' ;' '  n =]nTJaM10stringtype = 5streamtype = 6commandtype = 7dicttype = 8arraytype = 9stacktype = 10frametype = 11marktype = 12.length: .length => (length of: string (in characters); array (in elements); dictionary(in entries).).cvs: .cvs => (convert to string equivalent.).cvis: .cvis => (convert into given string space. This command will use s for allnumber and boolean conversions.).cvrs: .cvrs => (convert with radix to string equivalent.).cvirs: .cvirs => (convert with radix into given string space. This command will use s for allnumber and boolean conversions.).litchk: .litchk => <.true> if a noun otherwise <.false>.cvx: .cvx => (convert into executable equivalent.).cvlit: .cvlit => (convert into literal form -- works for strings, and arrays.)Scanner and String Manipulation CommandsCommands in this group allow for string searching and manipulation. Also an interface to the JaMscanner exists in the ".token" command..token: .token => <.true><.false> (if token present then return .true and strip first token from given stream or string. Return remainder and token on the stack.If no token, then return only a .false.).string: .string => ( is a string of length i)..length: .length => (replaces the string with its length in characters)..substring: .substring => ( is a substring of s starting at position i for jcharacters.).putstring: .putstring => ( is the same as except for the substring s startingat position i.).search: .search =><.true>(if there is a substring of t matching s)<.false>(if no substring of t matches s)..asearch: .search =><.true>(if there is a starting substring of t matching s)<.false>(if no starting substring of t matches s).Graphics CommandsCommands in the Graphics group are supplimentary commands and are enabled by loadingjamgraphics.bcd. Within this mesa module a display context stack is maintained so thattransformations and other state information can easily be saved and restored. The .pushdc, .popdcand .initdc commands control this stack.State information concerning the graphics device is held in an entity called the diplay context. andmost transformation, clipping, and painting commands alter this state. Most drawing commandsboth alter state and use state..initdc:.initdc (Initializes the stack of display contexts.).pushdc:.pushdc (Pushes a copy of the current display context onto the display context stack.).popdc:.pushdc (Pops the current display context from the display context stack.).setview:.translate: .translate=>-- (Concatenates translation matrix with current transformation matrix.).scale: .scale=>-- (Concatenates scale matrix with current transformation matrix.).rotate: .rotate=>-- (Concatenates rotation matrix (theta in degrees) with current transformationmatrix.).drawto: .drawto =>-- (Draws from the current position to the given position.).rdrawto: .rdrawto =>-- (Adds x,y to the current position and draws from the current position to thecomputed position.) fsG?'bAyF 'a '_'^ ']K '\ 'Z 'Y XUX W U3 TW S_ R"D Ph O Ni6 M,8 KR Hp( D7s'9 B' ?y >' =S F <( :3 9I 8] U 7 5 7$ 4 3g 23g23g23g  1U .! .M -.M-.M & ,< .* )p $as< "). !Y/3 ( ); &?  y5 \W K   \ fT )a  M pC 3 f =]{JaM11.moveto: .moveto =>-- (Sets the current position to the given position.).rmoveto: .rmoveto =>-- (Adds x,y to the current position and sets the current position to thecomputed position.).drawbox:.drawbox =>-- (Draws a box with the given lower left corner and upper rightcorner. The draw position is left at the lower left corner.).drawboxarea :.drawboxarea =>-- (Draws a filled box with the given lower left cornerand upperright corner. The drawposition is left at the lowerleft corner.).drawspline:....drawspline =>--(Draws an open ended curve through n given points. Thiscurve is a natural spline consisting of n cubics.).drawcspline:....drawcspline =>--(Draws a closed curve through n given points. This curveis a natural spline consisting of n+1 cubics.).drawblob:....drawblob =>--(Fills a closed curve through n given points. This curve is anatural spline consisting of n+1 cubics.).drawcubic:.drawcubic=>-- (Draws a cubic with the givencoefficients.).beziertocubic:.beziertocubic=> (Converts four Bezier controlpoints to a parametric form of a cubic.).startpath:.startpath =>-- (Starts a path for the area generation machinery.).starteopath:.starteopath =>-- (Starts a path for the area generation machinery except that even/odd parity isused to determine interior.).enterpoint:.enterpoint =>-- (Enters the given point into the current path.).entercubic:.entercubic =>-- (Enters the given cubic into thecurrent path.).newboundary :.newboundary => -- (Starts a new boundary in the current path.).drawarea:.drawarea => -- (Fills the interior of the areas comprising the current path. Also deletes the path.).erase:.erase => -- (Erases all area inside the current clipping regions.).setclip:.setclip => --(Sets the current clipping region to be the given rectangle.).addclip:.setclip => --(Adds the given rectangle to the set of clipping regions.).resetclip:.resetclip => -- (Resets the list of clipping rectangles to nil.).enableclip:.enableclip => -- (Enables the clipper.).disableclip:.disableclip => -- (Disables the clipper.).testnoclip:.testnoclip => .true if clipping disabled else .false..initboxer:.initboxer =>-- (Initializes the boxing machinery.).readboxer:.readboxer=> (Reads the current box values.).boxcorners:.boxcorners => (convertsbox parameters to four corners).stopboxer:.stopboxer=>-- (Stops the current boxing calculation).testbox:.testbox =>-- (tests a given box against the current clippingregions).testboxin:.testboxin=> .true if tested box is in..testboxout:.testboxout=> .true if tested box is out..paint: .paint => -- sets the painting function (see bitblt).texture: .texture =>-- sets texture (4x4 bit pattern in low half of integer)..touch:.touch=> (yields x,y position of mouse at time when the red button is lifted. The returnedvalues are in the current coordinate system.).mouse:.mouse=> (yields x,y position of current mouse position. The returned values are in thecurrent coordinate system.).setfont:.setfont=> (sets the current font.).drawchar: .drawchar => (Draws a character at the current drawposition and updates the draw position toreflect the width of the character.).erasechar: .erasechar => (erases a character at the current drawposition and updates the draw position toreflect the negative width of the character.).drawstring: .drawstring => (Draws a string at the current drawposition and updates thedraw position to reflect the length of the string.).getstringbox : .getstringbox => (Returns the boundingbox of the given string.).getstringwidth : .getstringwidth => (Returns the length of the given string.)Error and User Definable CommandsCommands in this group are used in the JaM system but either have no definitions or are givensome default definition which is meant to be changed. Included in this group are all JaM errors,and some miscellaneous debugging commands.When an error occurs in the JaM machine (e.g. stackunderflow), the JaM machine executes a fixedidentifier for that error. It is the intent of this mechanism that the user define these names to beprocedures that are appropriate to the user's application. The current list of error names is: fsG? bAyG a.. _ ^8( ]K< \:7 Z@ Y -2 XU2 W M U. Ta S_) R" 35  P O /5 Ni2 M,( K D J Q Is H6 G F L E D}M C@N BD @` ?] >J B = ) ; + : 7 9T 4 8 R 6 \ 5 4^ 6 3 Y 1 0 ( /h * .*E ,O +11 *r- )4G ' &1 %| S $>$ # G !. O H3  P  T p! sP wM * N g41 ?N =]oJaM12.undefkey: A dictionary lookup failed. when this identifier is executed the offending name is on theoperand stack..longname: A file name passed to .run or .bytestream is too long (256 chars). The contents of the stackcontains the offending command plus its input parameters..badname: is generated from an attempt to create a stream with the given name. This error can come from either the .run command or the .bytestream command..typechk: a command is executed which has been passed objects of the wrong type on the stack. Boththe command and the original arguments are on the operand stack when .typechk is executed..dictfull: an attempt to define a new entry into a full dictionary has occurred. The offending commandand the argument prior to the call are on the stack..stkundflw: an attempt to retrieve an argument from an empty stack has occured..syntaxerr: an isolated right paren has been found by the scanner..overflow: numeric overflow has occurred in scanning a number..stkovrflw: a stack has overflowed. This error may come from various parts of the JaM machine. Whenthe error occurs, all stacks are stored into arrays and put on the operand stack in the order:opstack, dictstack, execstk..rangechk: some string or array operation is attempting to store out of bounds.Certain debugging commands are provided in JaM. These commands are executed only in certaincontexts. With these commands it is straightforward to build both tracing facilities andbreakpointing facilities. The commands include:.singlestep: This command puts execution control into single step mode. When in this mode, executioncontrol puts each object (except commands), as encountered, onto the operand stack. It thenexecutes the identifier ".step" which the user should have previously defined. After executing.step execution control executes the object that was on the operand stack, and continues..runfree: Takes execution control out of single step mode..interrupt: Is executed when the key (spare2--below the "_" key) is pressed. This command is definedto be equivalent to .stop. This command is used to get out of infinite loops..step: is used with .singlestep (see above) and has no default definition.The Programming and Use of JaMBecause JaM is quite different from other programming languages, it is appropriate to give hints onhow JaM is programmed and how to put JaM to good use. The following paragraphs will givesome of these hints.1. The JaM input language has very little syntax. This attribute is both good and bad. Thelack of syntax is good because a uniform representation is attained. The lack of syntax isbad because the code is hopelessly unreadable. In JaM it is almost true that any line ofcode can do anything (given the appropriate redefinitions of identifiers). Because of thisproperty of JaM code, it is desirable to do several things when programming. First,document each function as it is written (there are utility functions "/def" and "/xdef" tohelp with this). Second, use naming conventions for funtions that all belong to a given class(you have noticed the convention of starting each intrinsic command name with "."). Thelatter convention will allow a person reading the code to tell what catagory a function is inby its name.2. Since JaM is a stack oriented machine the user must mentally keep track of the contents ofthe stack. It is easier to program JaM if each routine performs only one function and isvery short. Normally a JaM routine should be at most one or two lines long. For example,suppose we wish to use the absolute value of a given number, then rather than introducethe code in line, it is desirable to write an "abs" function and then use that function e.g.(abs)(.dup 0 .lt (.neg) .cvx .if) .cvx .defAlso if complex parameters are being passed to JaM control statements, it is better to namethe parameters rather than have lines and lines full of parentheses. For example:(func)(-some kind of test- (--- fsG? `yF[_ ^B_]9 [Z Z8 YLZXZ V]U4 TV E S 8 Q5 P YO`^N# LF Is0, H6 J! F0 Dy FYB\A^@[Y ?2 = Z8"=/L1: S8-06K5x<36'2p6"0:/hH-1* $;mCc TIMESROMAN  HELVETICA TIMESROMAN TIMESROMAN LOGO TIMESROMAN  TIMESROMAN  HELVETICA  HELVETICA   HELVETICA ;% / 8 A /M W!]j/`^* jam.bravowyattSeptember 5, 1980 4:09 PM