% Forth in PostScript % Copyright 1990 Bradley Forthware % Permission is hereby granted to use this code for any % purpose, provided that this notice remains attached. % Mitch Bradley, February 9, 1990 % Address correspondence to: % Bradley Forthware % P.O. Box 4444 % Mountain View, CA 94040 % Bradley Forthware supplies a variety of Forth-related products and % service. % To use, "run" this file and then execute "quit" (this is the Forth % "quit", which really should have been named "restart", not the PostScript % "quit") % The comments describe the implementation in PostScript of a % subset of Forth. The comments assume that the reader knows Forth, % but does not know PostScript. % This does not presume to be a complete implementation of Forth, but % instead an intellectual exercise, a "proof of concept", and a brief % introduction for Forth programmers to some interesting aspects of the % PostScript language. Despite its deficiencies, Forth programmers might % still this program useful as a way to program laser printers using a % syntax that is more familiar to them. While I personally feel that % PostScript syntax is superior to Forth syntax in many ways, my long % experience with Forth allows me to "think" in Forth more readily than % in PostScript. % This program supports Forth colon definition syntax, IF .. ELSE .. THEN % conditionals, and CREATE .. DOES> (however, "does>" had to be spelled % without the ">" because PostScript doesn't allow the special characters % ()[]{}<>/% in names). Some Forth stack and arithmetic operators are % provided; others are missing. Most of the missing ones could be added % with little effort. PostScript procedures may be executed from within % the Forth environment and compiled into definitions, so the Forth syntax % may be used to write graphics programs using the PostScript graphics % operators. % It is a tribute to the power and beauty of the PostScript language % that I was able to write the following code (excluding the commentary) % in about 4 hours, while still learning PostScript. % Define a new procedure "-def" which is like the normal defining % procedure "def", except that the operands are reversed. "def" % associates a keyword with a procedure or a value; "def" performs % the function of the Forth words ":" , "CONSTANT" , and "VARIABLE" /-def { exch def } def % proc key -- % Create a new dictionary (like a Forth vocabulary) named "immeddict", % with space for 20 keywords (names). This dictionary will be used to % contain the "immediate" words for the Forth implementation. /immeddict 20 dict def % -- dict % Create a new dictionary "forthdict", with space for 300 keywords (names). % This dictionary will be used to contain the "non-immediate" words for % the Forth implementation. /forthdict 300 dict def % -- dict % Create a procedure named "forth" which will push the "forthdict" % dictionary on the PostScript dictionary stack (search order stack). % The procedure "begin" pops a dictionary object from the data % stack and pushes it on the dictionary stack. /forth { forthdict begin } def % -- % Push the forthdict dictionary on the PostScript dictionary stack. % This causes that dictionary to be searched first, and also causes % new definitions to be placed in that dictionary. forth % Define some Forth names for common operations. These operations % all have direct PostScript equivalents, usually with different % names. Here we alias them to the equivalent Forth names for the % convenience of us Forth programmers. The "load" operator is for % increased efficiency; it forces the names to be looked up at compile % time and replaced by the actual executable object. Otherwise, the % aliases words would be looked-up at run time, every time that the % word is executed. This run-time lookup is the default PostScript % behavior, in contrast to Forth which defaults to compile-time binding. % The "load" and "bind" PostScript operators force compile-time binding. % Note that the "/" operator must be specified differently from the % rest. "/" has special syntactic meaning in PostScript; it signifies % that the rest of the name is to be interpreted as a literal keyword, % instead of being executed. /dup /dup load def % obj -- obj obj /swap /exch load def % o1 o2 -- o2 o1 /drop /pop load def % obj -- /+ /add load def % n1 n2 -- n1+n2 /- /sub load def % n1 n2 -- n1-n2 /* /mul load def % n1 n2 -- n1*n2 (/) cvn /idiv load def % n1 n2 -- int(n1/n2) % Now that we have redefined "/" , you may wonder why it can still be % used in its normal way in the following code. The answer is that % "/" has meaning to the lexical scanner; it is not "looked up in % the dictionary". By analogy, the character "." in some Forths has % special meaning to the number parser (signifying a double number), % even though there is also a "." entry in the dictionary with a different % meaning. % Some Forth names for common stack manipulation operations. Note the % PostScript operator "roll". It is quite powerful; rotating "n" % stack cells by "m" positions, in either direction. The Forth "ROLL" % is a special case of the PostScript "roll". The Forth "ROLL" could % be defined as /forth-roll { -1 roll } def /over { 1 index } def % o1 o2 -- o1 o2 o1 /nip { swap drop } def % o1 o2 -- o2 /2dup { 2 copy } def % o1 o2 -- o1 o1 o1 o2 /rot { 3 -1 roll } def % o1 o2 o3 -- o2 o3 o1 /-rot { 3 1 roll } def % o1 o2 o3 -- o3 o1 o2 % Here are a couple of handy Forth words. Their definition also % illustrates 2 PostScript features: nested procedures and the "if" % operator. The innermost set of braces defines a nested procedure. % The "if" operator is completely postfix; its stack diagram is % boolean proc -- % The procedure object is executed if the flag is true, and dropped if the % boolean value is false. PostScript also has "ifelse"; its stack diagram is % boolean true-proc false-proc -- /max { 2dup lt { swap } if drop } def % n1 n2 -- max /min { 2dup gt { swap } if drop } def % n1 n1 -- min % Here are the run-time words which the Forth implementation compiles % for the IF .. THEN and IF .. ELSE .. THEN constructs. "0 ne" % converts a number into a flag. Forth conditionals use particular % values of integers to signify truth values. PostScript has a separate % boolean data type, and a "typecheck" run-time error will result if % a conditional operator encounters a number on the stack instead of % a boolean. /pif { swap 0 ne swap if } bind def % n proc -- /pifelse { rot 0 ne -rot ifelse } bind def % n t-proc f-proc -- % Handy constant /bl 32 def % -- n % The Forth text interpreter uses "STATE" to distinguish compilation from % interpretation. In this case, it is more like a "VALUE" than a % "VARIABLE", because it doesn't need to be followed by "@" in PostScript. /state 0 def % -- n % Read an edited line from the keyboard, placing it into the string % object on the stack, returning the filled substring of that string /expect { % string -- substring (%lineedit) (r) file /ifd -def % string ifd swap readstring drop % substring ifd closefile % substring % dup length 1 sub 0 max % workaround for bug in % 0 swap getinterval % Imagen UltraScript } def % Read an edited line, placing it in a string named "tib" /query { 80 string expect /tib -def } def % -- % Parse a delimited string from the input string "source", updating % the source string to contain the remainder of the input after the % string that was parsed. /word { % delim -- string /ws 1 string def % delim ws 0 rot put source ws search { nip swap } { () } ifelse /source -def } def % Convert a numeric string to an integer /number { cvi } def % string -- int % Search for a string in all the dictionaries on the dictionary stack /find { % str -- str false | proc true dup cvn where % str dict true | str false { drop cvn cvx true } { false } ifelse % str false | proc true } def % Here's how the Forth interpreter handles a word when it is in compile % state. Note that a compiled word is not "comma'ed into the dictionary"; % instead, a procedure object for that word is left on the stack for later % inclusion in a new procedure array. "immediate" words are those which % are located in the "immeddict" dictionary. /do-compile { % str -- [ proc | n ] immeddict over cvn known % str flag % Execute immediate words { immeddict swap cvn get cvx exec } % % Leave procs on stack for later, % leave number if word not found { find not { number } if } % [ proc | n ] ifelse % proc | n | } def % This is how the Forth interpreter handles a word in interpret state. % If the word is found, it is executed. Otherwise, it is converted to % a number. If the numeric conversion fails, PostScript will automatically % signal an error, which we will intercept at a higher level. /do-interpret { find { exec } { number } ifelse } def % Interprets a line of text. Note that PostScript loops are postfix too. % "loop" executes the preceding procedure until "exit" is executed. % "exit" terminates execution of the nearest enclosing "loop" /interpret { % -- ?? tib /source -def %% Set up the text line { bl word % string dup length 0 eq { drop exit } if % string state 0 ne { do-compile } { do-interpret } ifelse } loop } def % Okay, here it is. The top level of the Forth text interpreter. % "stopped" catches error signals (PostScript error signals are sort % of like Forth "ABORT" except that the program can catch and handle % them if it chooses). "stopped" prevents errors from kicking you out % of the Forth interpreter and back into the PostScript interpreter. % "( OK)" is the string " OK", which is displayed as the user prompt. /quit { { query % { interpret } stopped % error? { handleerror } { ( OK) print } if % } loop } def % Some more handy Forth words /. /= load def % obj -- /.s /pstack load def % ?? -- ?? /depth { count } def % Some debugging tools; "words" displays the words in the dictionary. % "psee" and "see" are decompilers. "psee" takes a keyword from the % stack and decompiles the object or procedure it refers to. % "see" takes a name from the input stream and decompiles it. % Note how simple these are to implement. That is because PostScript % has a function to enumerate the words in a dictionary ("forall") % and also a general purpose display word "==" which will basically % decompile anything. Forth really ought to at least have a standard % way to enumerate the words in a vocabulary! /words { currentdict { pop = } forall } def /psee { dup where { swap get == } { drop (?) print } ifelse } def /see { bl word cvn psee } def % Some Forth-style literals /' { bl word cvx } def % -- proc /" { (") 0 get word } def % -- string /"" { bl word cvn } def % -- name % Data space operations. We create a big array called "memory" from which % we can "allot" space. Fetch and store access elements of this array. % When I say "adr" from here on out, I really mean an index into this % data array, not a real machine address. /limit 10000 def % -- adr /memory limit array def % -- array /here 0 def % -- adr /allot { here + /here -def } def % size -- /@ { memory swap get } def % adr -- obj /! { memory swap rot put } def % obj adr -- /, { here 1 allot ! } def % obj -- % Here's where it really starts to get interesting! % Now we start to build some defining words. % The easiest defining word is "constant". It's trivial because % PostScript can associate a number with a keyword, and that's just % what a constant is. By the way, "cvn" converts a string to a number. /constant { bl word cvn swap def } def % Now we deal with colon definitions. The PostScript equivalent of a % Forth colon definition is an "executable array". The array is an % important data type in PostScript. Arrays contain objects, and % basically everything in PostScript is an object. Numbers are objects, % as are strings, procedures, arrays, keywords (names), and dictionaries. % The objects contained in an array can be of any type, and different % types may be mixed within the same array. % A PostScript procedure is an array which is marked as executable. % A non-executable array is simply pushed on the stack when it is % encountered. An executable array is executed. An array can be % converted from non-executable to executable at any time. % We compile a Forth colon definition by building an array containing % the procedures the comprise the definition. When the colon definition % is terminated with ";", we close the array and mark it executable. % The basic tools we use for creating executable arrays are "+level" % and "-level". "+level" begins the array, and "-level" finishes it. % Executable arrays may be nested; we need this for implementing % control structures within colon definitions. The nesting depth is % indicated by the value of "state", which is incremented by "+level" % and decremented by "-level". When state is non-zero, we are in % compile state and each word is added to the array we are building, % otherwise we are in interpret state, and each word is executed as it % is encountered. /+level { /state state 1 + def [ } def /-level { ] cvx bind /state state 1 - def } def % Here's ":". We remember the name on the stack and start building % an array. When the matching ";" is encountered, we will finish the % array, make it executable, and attach the name to it. /: { bl word cvn +level } def % CREATE ... DOES> is pretty tricky. The basic idea is that CREATE % makes a word of the form: {
noop noop } %
is the value of HERE at the time the word was created. % If the CREATE'd word is not later modified by DOES>, then the % noop's will be executed, doing nothing, and the address will be % left on the stack. If there is a DOES>, then the first "noop" % will be replaced with a procedure which implements the DOES> run-time % action, and the second "noop" will be replaced by "exec", which will % execute that procedure. /noop { } def /create { [ here /noop cvx /noop cvx ] % Remember the non-executable array as "lastacf" % so that we can replace the noop's later if we need to dup /lastacf -def % Make the array executable and bind its procedures. cvx bind % Read the name from the input stream and associate it % with the new procedure. bl word cvn -def } def % Flag which tells ";" whether or not there is a DOES> clause. /indoes false def % The run-time action executed by defining words. Modifies the child % word so that it will execute the DOES> clause /dodoes { lastacf 1 rot put lastacf 2 /exec cvx put } def % From now on, we define immediate words, placing them in the "immeddict" % dictionary immeddict begin % A CREATE .. DOES> defining word is compiled as follows: % : foo CREATE a b c d DOES> e f g ; % ==> % /foo { a b c d { e f g } dodoes } def % /does { +level /indoes true def } def % ";" has to know whether or not there was a DOES> clause, so it can % decide whether or not compile "dodoes". /; { indoes { /indoes false def -level (dodoes) cvx } if -level def } def % IF .. ELSE .. THEN is really quite easy. It compiles as follows: % % : foo a b IF c d THEN e f ; % ==> % /foo { a b { c d } pif e f } def % % : foo a b IF c d ELSE e f THEN g h ; % ==> % /foo { a b { c d } { e f } pifelse g h } def % % The reason we have to compile "pif" or "pifelse" instead of the normal % PostScript "if" and "ifelse" is because the PostScript operators % require a boolean type operand, whereas Forth uses numbers for flags. % The "nip /pifelse cvx" in "else" replaces the "pif" left by "if" % with "pifelse". /if { /pif cvx +level } def /else { -level nip /pifelse cvx +level } def /then { -level swap } def % Other Forth conditionals are left to the reader as an exercise. % Hint: Use "loop" and "exit" to build BEGIN .. WHILE .. REPEAT , % BEGIN .. AGAIN , and BEGIN .. UNTIL . Use "for" to build DO .. LOOP % and DO .. +LOOP , being careful of boundary conditions! end