Introduction This section presents a very simple example of a Cedar program for your reading pleasure (it assumes you are already familiar with the Cedar Language, either by previous osmosis or by having read the Cedar Language Overview). This an actual program that can be run, used as parts of other programs, or treated as templates to be edited into new programs with similar structures. For each example there is a short discussion of its purpose followed by the program. After each program there is a set of notes to help in reading it. The notes are keyed to the programs by the numbers in parentheses to the right of some lines, e.g., as ``--(note 3.1)''. You will also find references to lines in the program from the notes, as, e.g., "[see line 1.1]". The corresponding lines in the programs are marked as, e.g., [1.1]. When Mesa identifiers appear in the notes, they are displayed in italics to make reading easier; e.g., ReverseName, CommandProc. You should read the examples for understanding and use the notes as references, rather than the other way around. The programs in this document are stored in /Cedar10.1/Top/SimpleExample.df. Use the DF software to BringOver /Cedar10.1/Top/SimpleExample.df into a subdirectory when you want to modify the examples; you don't need to bringover the example to only run them. Warning: don't try to simply ``shift-select'' the text out of this document. You'll likely get something that won't compile correctly. Just do the BringOver and edit the source. For example: % mkdir SimpleExample % cd SimpleExample /net/palain/rosa/boki/Cedar/Development/SimpleExample/ % mkdir sun4 % BringOver /Cedar10.1/Top/SimpleExample.df it types a lot of stuff ... % open ReverseName.mesa % open Calculate.mesa 1. A simple but complete program This first example shows how to write a simple but complete Cedar program. It illustrates a number of features of the Cedar language and system as well as some of the stylistic conventions used by Cedar programmers (see the document CedarProgramStyle.tioga): It uses Cedar ROPEs for string manipulation; It uses the IO interface to create and use terminal input and output streams; It registers procedures with the Cedar command processor (Commander) so that you can invoke them like built-in commands of the Commander; One of these registered commands creates a process by means of a FORKed procedure call each time you invoke it from the CommandTool; The process then creates a simple Viewer with which you can interact to calculate the values of simple expressions. The programs can be run by typing "Run name-of-program" to the Commander (Programs in the release can be run by simply issuing the commands). All it does at that point is register two commands with the Commander. The first command, ReverseName, simply types your logged-in user name backwards in the Commander typescript (unless you are Mark Weiser). The second command, Calculate, creates a separate viewer into which you can type simple expressions of the form number { {+ |  } number }* terminated by a carriage return. It will display the value of the expression. If you wish, you can create multiple calculator viewers by giving the Calculate command to the CommandTool more than once. 1.1 ReverseName.mesa DIRECTORY Commander USING [CommandProc, Register], IO USING [PutF1, PutRope, rope, STREAM], Rope USING [Concat, Equal, Fetch, Find, FromChar, Length, ROPE, Substr], SystemNames USING [UserName]; ReverseName: CEDAR PROGRAM IMPORTS Commander, IO, Rope, SystemNames ~ BEGIN --(note 1.3) ROPE: TYPE = Rope.ROPE; --(note 1.4) ReverseName: Commander.CommandProc ~ { --(note 1.5) userName: ROPE ¬ SystemNames.UserName[]; --(note 1.6) out: IO.STREAM ¬ cmd.out; -- cmd is an arg to ReverseName. backwordsName: ROPE ¬ NIL; dotPos: INT = userName.Find["."]; --(note 1.7) IF dotPos # -1 THEN userName ¬ Rope.Substr[userName, 0, dotPos]; IF Rope.Equal[s1: userName, s2: "Weiser", case: FALSE] THEN IO.PutRope[out, "Hi, Mark!\n"]; FOR i: INT DECREASING IN [0..Rope.Length[userName]) DO backwordsName ¬ Rope.Concat[backwordsName, Rope.FromChar[Rope.Fetch[userName, i]]] ENDLOOP; IO.PutF1[out, "Your user name backwards is: %g.\n", IO.rope[backwordsName]]; --(Note 1.9) }; --(note 1.16) Commander.Register[key: "ReverseName", proc: ReverseName, doc: "Reverses your user name"]; END. 1.2 Note for Reverse (1.1) These stylized comments give the name of the module, and who last edited it. See the document CedarProgramStyle.tioga for a list of the stylistic conventions recommended for Cedar programmers. (1.3) ReverseName imports four interfaces, Commander, IO, Rope, and SystemNames because it needs to call procedures defined in those interfaces. (1.4) Since ROPEs are so heavily used in the program, an unqualified version of the type name is generated simply by equating it with the type in the Rope interface. This is a commonly used means for making one or a small set of names from interfaces available as simple identifiers in a module. (1.5) The argument and returns lists given here as comments show the meanging of the type Commander.CommandProc; they were inserted semi-automatically using Tioga's ``Expand Abbreviation'' command to assist in reading the program. ReverseName has this type so that it can be registered with the CommandTool as a command that a user can invoke by typing a simple identifier (see note 1.18). (1.6) UserCredentials.Get has the type PROC RETURNS [name, password: ROPE] userName is initialized to the value of the ``name'' return value from UserCredentials.Get by qualifying the call with ``.name''. Well, the current release does provides a different means to access the user name. This is an example that our documentation typically is behind. (1.7) The notation userName.Find["."] is interpreted by the compiler as follows: Look in the interface where the type of userName is defined (Rope in this case) and look for the procedure Find, whose first parameter should be a ROPE. Generate code to call that procedure, inserting userName at the head of its argument list. Thus userName.Find["."] is an alternate way of saying Rope.Find[userName, "."]. The userName.Find form is called ``object-style notation'', and the Rope.Find form is called ``procedure-oriented notation''. Note also that dotPos is supposed to be constant over the scope of its declaration, so it is initialized with ``='' (as opposed to ``_'') to prevent any subsequent assignment to it. We do not recommend that this notation will be used. (1.8) Find, Fetch, Equal, Substr, Cat, and FromChar are all procedures from the Rope interface. The program used to use object-style notation for those calls whose first argument is a nameable object, e.g., userName, and procedure-oriented notation otherwise. [line 1.1] is a good example of both. Here it is again, spread out to exhibit the two forms: backwordsName _ backwordsName.Cat[ -- object-style notation Rope.FromChar[ -- procedure-oriented notation userName.Fetch[i] -- object-style notation ] ]; However, since object notation is hard to read we have changed the program to not use it here. (1.9) IO.PutF is the standard way of providing formatted output to a stream (much like FORTRAN output with FORMAT). Its first argument is an IO.STREAM. The second is a ROPE with embedded Fortran-like formatting commands where variables are to be output. The ``%g'' format is the most useful one; it will handle any sort of variable: INTEGER, CHARACTER, ROPE, etc., in a general default format. The third through last arguments are values to be output, surrounded by calls to inline IO procedures that tell PutF the type of the argument: int[answer], for example. For details, see the description of IO in IODoc.tioga. (1.16) Here, at the end of the module, is the code that is executed when the module is started. You can create an instance of a module and start it using the "Run" command in CommandTool. The loader creates instances of programs when loading configurations. If a component of a configuration is not STARTed explicitly, then it will be started automatically (as the result of a trap) the first time one of its procedures is called. See the Cedar Language Reference Manual for more information. In this case, the start code for the module consists of two calls on the procedure Commander.Register, which register the commands ``Calculate'' and ``ReverseName'' with the Command Tool so that you can invoke the procedures MakeCalculator and ReverseName, respectively, by typing their command names. These procedure calls also illustrate the ability to specify the association between a procedure's formal parameters and the arguments using keyword notation. Generally, keyword notation is preferred over positional for all but simple one- or two-argument calls, and it is definitely better for two-argument calls if the types of the arguments are the same. Either Copy[to: arg1, from: arg2] or Copy[from: arg2, to: arg1] is preferable to Copy[arg1, arg2] 1.3 Calculate.mesa DIRECTORY Commander USING [CommandProc, Register], IO USING [char, Error, GetChar, GetInt, int, PeekChar, PutF1, PutFR1, PutRope, Reset, SkipWhitespace, STREAM], Process USING [Detach], Rope USING [ROPE], ViewerIO USING [CreateViewerStreams]; Calculate: CEDAR MONITOR --(note 1.2) IMPORTS Commander, IO, Process, ViewerIO = BEGIN ROPE: TYPE = Rope.ROPE; windowCount: INT ¬ 0; -- a count of the number of calculators created. MakeCalculator: ENTRY Commander.CommandProc = { --(note 1.10) title: ROPE; windowCount ¬ windowCount + 1; title ¬ IO.PutFR1["Adding Machine #%g", IO.int[windowCount]]; --(note 1.11) TRUSTED {Process.Detach[FORK Calculate[title]];} --(note 1.12) }; Calculate: PROC[title: ROPE] = { in, out: IO.STREAM; [in: in, out: out] ¬ ViewerIO.CreateViewerStreams[title]; --(note 1.13) DO -- Read an expression, terminated by a CR: ENABLE IO.Error => --(note 1.14) IF ec=SyntaxError THEN { IO.Reset[in]; IO.PutRope[out, "\nIncorrect input. Please retype the expression.\n\n"]; LOOP; } ELSE EXIT; answer: INT ¬ 0; -- initialize sum to zero. opChar: CHAR ¬ '+; -- first integer should be added. IO.PutRope[out, "Type one or more integers separated by + and - and terminate with CR to compute value:\n"]; DO -- Read an operator and an integer, skipping leading blanks, eg., NUL, CR, SP, etc. number: INT ¬ IO.GetInt[in]; SELECT opChar FROM '+ => answer ¬ answer + number; '- => answer ¬ answer - number; ENDCASE => { IO.PutF1[out, "Illegal operator (%g). Please retype the expression.\n\n", IO.char[opChar]]; EXIT; }; IF IO.PeekChar[in]='\n THEN GO TO NextLine; -- the normal way out. []¬ IO.SkipWhitespace[in]; opChar ¬ IO.GetChar[in]; REPEAT NextLine => { [] ¬ IO.GetChar[in]; -- toss CR (note 1.15) IO.PutF1[out, "\nThe answer is: %g.\n\n", IO.int[answer]]; }; ENDLOOP; ENDLOOP; }; Commander.Register[key: "Calculate", proc: MakeCalculator, doc: "A simple adding machine"]; END. 1.4 Notes for Calculate (1.2) A MONITOR module is like a PROGRAM module except that it can be used to control concurrent access to shared data (see note 1.11). (1.10) MakeCalculator is an ENTRY procedure to this monitor because it updates the variable windowCount, which is global in SimpleExample and therefore could be incorrectly updated if multiple processes were to invoke MakeCalculator concurrently. When invoked, MakeCalculator creates a new calculator viewer on the screen by invoking Calculate and forking it as a new process (see note 1.13). (1.11) IO.PutFR has the same arguments as IO.PutF, but returns a ROPE containing the resultant output rather than sending it to a particular stream. (1.12) The FORK operation creates a new process to execute a normal procedure call. It returns a PROCESS value, which can be used to synchronize with the process later (when its root procedure executes a RETURN) and acquire its return values. Alternatively, it can be passed to the procedure Process.Detach, in which case the process can no longer be JOINed to and will simply disappear when its root procedure RETURNs. Note that the combined FORKing and detaching is enveloped in a TRUSTED block. This is because the Detach operation is not intrinsically SAFE, although the stylized combination of FORK and Detach used here actually is. You should use this paradigm when detaching a FORKed process: don't assign the process handle returned by FORK to anything, just use it as the single argument to Process.Detach and surround the whole by a TRUSTED block. (1.13) CreateViewerStreams returns a pair of IO.STREAM objects, in for input and out for output. These two returned values are assigned to the local variables in and out using a keyword extractor to ensure that we assign them correctly. (1.14) Calculate parses a simple expression from the in stream using some useful IO functions for input, such as GetInt to parse an INT (skipping over leading blanks) and PeekChar to look at the next character while leaving it in the stream for the next input call. These IO procedures can raise the ERROR IO.Error, so there is a catch phrase enabled over the outer (endless) loop to deal with it. IO.Error carries an argument, ec, which is from an enumerated type defined in IO. If ec is SyntaxError, the input was not lexically correct; the program regains control and goes around the outer loop again. The only other possibility is that the in and out streams were closed (ec is StreamClosed) as a side effect of the user having destroyed the Calculator viewer by bugging the Destroy menu item. This error and code can emanate from any of the IO operations in the loop. In this case the program exits the outer loop, returns from Calculate, and the process that was forked then terminates and disappears. (1.15) If a procedure returns values, the Cedar language requires you to assign them to something, even if you have no use for them in a specific case. The standard mechanism for ignoring a procedure's returned value(s) is an empty extractor, ``[]'', as used here. 1.5 Exercises for SimpleExample 1. What do you think would happen if you typed the following to the Command Tool? ( represents hitting the carriage-return key.) a) Run SimpleExample b) ReverseName c) Calculate Calculate 2. What would happen if you typed the following in the calculator's viewer? ( represents hitting the Space bar.) a) 1+2+3 b) 1 + 2 - 3 c) 1 - 2+ 3 d) 1 / 2 3. (Skip this question unless you are really interested in Cedar IO details.) Predict what the calculator will do if you type the following. Why? a) 1++ b) 1++ 2 c) 1 -- d) 1 -- -2 e) 1 - - 2 : SimpleExampleDoc.tioga Copyright Ó 1990 by Xerox Corporation. All rights reserved. Brian Oki, May 3, 1990 9:36 am PDT Christian Jacobi, May 14, 1992 3:14 pm PDT ReverseName.mesa --(note 1.1) Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved. Brian Oki, May 14, 1990 10:27 am PDT Christian Jacobi, May 14, 1992 2:22 pm PDT [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] Reverses the user's login name and prints it out in the CommandTool window. Remove anything after the period in user name, e.g., ".PA", and check for the name Weiser. Now reverse the name and concatenate them in reverse order. --[1.1] (note 1.8) Start code registers a ReverseName command, which must be invoked for this program to do anything: Calculate.mesa Copyright Ó 1992 by Xerox Corporation. All rights reserved. Last edited by: Mitchell on December 16, 1982 2:32 pm Last edited by SChen on May 1, 1984 3:53:45 pm PDT Last edited by Bob Hagmann on May 8, 1984 8:46:31 am PDT Last changed by Pavel on May 5, 1989 7:15:02 pm PDT Brian Oki, May 2, 1990 11:26 am PDT Christian Jacobi, May 14, 1992 3:00 pm PDT [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] Puts up a calculator window. Creates typescript window separate from CommandTool with given title. Uses IO procedures to read an integer expression from the typescript and print its value. Start code registers a Calculate and ReverseName command, which must be invoked for this program to do anything: Ê Þ•NewlineDelimiter –(cedardoc) style™•Mark LastEdited™J– LastEdited™