%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % hw3.ps - PostScript REPL % Scott Laufer % for CSE 305, Spring 2014 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% nulldevice % because sometimes -sDEVICE=nullpage just isn't enough 2 vmreclaim % because garbage collection is turned off by default in GS (???) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % DATA TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %TYPES %PRIMITIVES /T_BOOL 0 def /P_ADD 0 def /P_POP 8 def /P_EQUAL 17 def /T_INT 1 def /P_SUB 1 def /P_EXC 9 def /P_IF 18 def /T_NAME 2 def /P_MUL 2 def /P_QUIT 10 def /P_APPLY 19 def /T_STRING 3 def /P_DIV 3 def /P_DUP 11 def /P_LESSTHAN 16 def /T_ERR 4 def /P_REM 4 def /P_BINDS 12 def /T_PRIM 5 def /P_NEG 5 def /P_AND 13 def /T_LAMBDA 6 def /P_BIND 6 def /P_OR 14 def /T_CLOSURE 7 def /P_LOAD 7 def /P_NOT 15 def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % BUFFERS AND STREAMS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /stdout (%stdout) (w) file def % standard output /stdin (%stdin) (r) file def % standard input /readbuf 1024 string def % read buffer %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % LIBRARY FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % yes, i actually wrote all of these /pf { print flush } def % print and flush /pif { 20 string cvs pf } def % print integer and flush (for debugging) /case { 1 exch repeat } def % case -- some syntactic sugar for using loops as case statements /idiv_proper { 2 array astore aload aload aload pop mod 5 1 roll idiv 4 -1 roll 4 array astore dup 3 get 0 lt { dup 1 get dup abs idiv exch dup dup 2 get 4 -1 roll sub 2 exch put dup dup dup 1 get 4 1 roll 3 get 4 -1 roll abs add 3 exch put } if 2 2 getinterval aload pop } def /arraycopy { dup length array copy } def % copy an array /arraygrow { aload length 1 add array null exch astore } def % grow an array by 1 /arraypush { exch arraygrow dup dup 4 1 roll length 1 sub 3 -1 roll put } def % put an object into the last slot of an array /arraypop { aload length 1 sub array dup length 3 -1 roll exch 2 add 1 roll astore exch } def % pop the last element off of an array, into the stack /arraypopd { aload length 1 sub array exch pop astore } def % pop the last element off of an array, into oblivion /getneg { exch dup length 3 -1 roll sub get } def % get array elements, counting backwards from the end /stringcopy { dup length string copy } def /iswhitespace { dup 9 eq { true } { dup 10 eq { true } { dup 32 eq { true } { false } ifelse } ifelse } ifelse exch pop } def % if a char is whitespace /isnum { dup 48 ge { dup 57 le { true } { false } ifelse } { false } ifelse exch pop } def % if a char is numeric /isnumstr { true exch dup dup 0 exch 1 exch length 1 sub { dup 3 1 roll get dup isnum { pop pop } { 45 eq { 0 eq not { false 3 -1 roll pop exch null exit } if } { pop false 3 -1 roll pop exch null exit } ifelse } ifelse dup } for pop pop } def /isupper { dup 65 ge { dup 90 le { true } { false } ifelse } { false } ifelse exch pop } def % if a char is an uppercase letter /islower { dup 97 ge { dup 122 le { true } { false } ifelse } { false } ifelse exch pop } def % if a char is a lowercase letter /isalpha { dup isupper { true } { dup islower { true } { false } ifelse } ifelse exch pop } def % if a char is a letter /isalnum { dup isalpha { true } { dup isnum { true } { false } ifelse } ifelse exch pop } def % if a char is alphanumeric /isalnumstr { true exch { isalnum not { pop false exit } if } forall } def % if a string is alphanumeric /isnamestr { dup 0 get isalpha { dup length 1 sub 1 exch getinterval isalnumstr } { pop false } ifelse } def % if a string is a valid name /isint { 0 get T_INT eq } def /isbool { 0 get T_BOOL eq } def /isstring { 0 get T_STRING eq } def /iserr { 0 get T_ERR eq } def /islambda { 0 get T_LAMBDA eq } def /isliteral { dup isint { true } { dup isbool { true } { dup isstring { true } { dup iserr } ifelse } ifelse } ifelse exch pop } def % if a token is a literal type /isprim { 0 get T_PRIM eq } def % if a token is a primitive /isname { 0 get T_NAME eq } def % if a token is a name /istrap { dup length 3 ge { 2 get null ne { true } { false } ifelse } { pop false } ifelse } def % if a token has a trapped name errordict begin % error handler overrides for file opens /undefinedfilename { 4 1 roll pop pop pop false exch } def /invalidfileaccess { 4 1 roll pop pop pop false exch } def end /openfile { true 3 1 roll file exch } def % open a file and return a file handle and true/false /readfile { 4 dict begin % read an entire file into a string (r) openfile { /buf 16 string def % read buffer /fp exch def % get file pointer /i 0 def % index { fp read { buf length i eq { % resize the buffer if necessary buf length 256 add string % create new buffer dup 0 buf putinterval % copy old buffer into new buffer /buf exch def % bind new buffer } if /rc exch def % get the char we just read buf i rc put % put char into buffer /i i 1 add def % increment index } { exit } ifelse } loop buf 0 i getinterval true % put out the file } { false } ifelse end } def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % LEXER FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /toktype { % figure out what the type of a token is (strings are handled separately) { %not an actual loop, just using it for breakouts dup (add) eq { pop [ T_PRIM P_ADD ] exit } if % constant literals dup (sub) eq { pop [ T_PRIM P_SUB ] exit } if dup (mul) eq { pop [ T_PRIM P_MUL ] exit } if dup (div) eq { pop [ T_PRIM P_DIV ] exit } if dup (rem) eq { pop [ T_PRIM P_REM ] exit } if dup (neg) eq { pop [ T_PRIM P_NEG ] exit } if dup (bind) eq { pop [ T_PRIM P_BIND ] exit } if dup (load) eq { pop [ T_PRIM P_LOAD ] exit } if dup (pop) eq { pop [ T_PRIM P_POP ] exit } if dup (exc) eq { pop [ T_PRIM P_EXC ] exit } if dup (quit) eq { pop [ T_PRIM P_QUIT ] exit } if dup (dup) eq { pop [ T_PRIM P_DUP ] exit } if dup (binds) eq { pop [ T_PRIM P_BINDS ] exit } if dup (and) eq { pop [ T_PRIM P_AND ] exit } if dup (or) eq { pop [ T_PRIM P_OR ] exit } if dup (not) eq { pop [ T_PRIM P_NOT ] exit } if dup (lessThan) eq { pop [ T_PRIM P_LESSTHAN ] exit } if dup (equal) eq { pop [ T_PRIM P_EQUAL ] exit } if dup (if) eq { pop [ T_PRIM P_IF ] exit } if dup (apply) eq { pop [ T_PRIM P_APPLY ] exit } if dup (:true:) eq { pop [ T_BOOL true ] exit } if dup (:false:) eq { pop [ T_BOOL false ] exit } if dup (:error:) eq { pop [ T_ERR null ] exit } if dup isnumstr { [ exch T_INT exch cvi ] exit } if % integers dup isnamestr { [ exch T_NAME exch stringcopy ] exit } if % names pop [ T_ERR false ]% no match, throw an error } case } def /closurebraceisokay { 2 dict begin { /i exch def /str exch def str i get 123 eq { % open bracket ({) i 0 eq { true % if this begins the string, left-char is okay } { str i 1 sub get iswhitespace % otherwise make sure left-char is whitespace } ifelse i 1 add str length lt { % make sure we're not gonna get a rangecheck str i 1 add get iswhitespace % make sure right-char is whitespace } { false % rangecheck = fail } ifelse and % see if both tests passed exit % and done } if str i get 125 eq { % close bracket (}) i 0 gt { str i 1 sub get iswhitespace } { false } ifelse i 1 add str length eq { true % if this ends the string, right-char is okay } { str i 1 add get iswhitespace % otherwise make sure right-char is whitespace } ifelse and % see if both tests passed exit % and done } if false } case end } def /stringstartokay { 2 dict begin /i exch def /str exch def i 0 eq { true } { str i 1 sub get iswhitespace } ifelse end } def /stringendokay { 2 dict begin /i exch def /str exch def i 1 add str length eq { true } { str i 1 add get iswhitespace } ifelse end } def /lex { 7 dict begin /str exch def /size str length def /i 0 def /j 0 def /depth 0 def /char 0 def /tokens [] def % MAIN LOOP { i str length ge { exit } if /char str i get def % SWITCH OVER CURRENT CHAR { % WHITESPACE char iswhitespace { /i i 1 add def exit } if % STRINGS char 34 eq str i stringstartokay and { /j 1 def { i j add size ge { exit } if /char str i j add get def char 34 eq { exit } if /j j 1 add def } loop i j add size ge str i j add stringendokay not or { { % seek next whitespace to eat up error i j add str length eq { exit } if str i j add get iswhitespace { exit } if /j j 1 add def } loop /tokens tokens [ T_ERR false ] arraypush def } { /tokens tokens [ T_STRING str i 1 add j 1 sub getinterval stringcopy ] arraypush def % STRING TOKEN OUTPUT } ifelse /i i j add 1 add def exit } if % CLOSURES char 123 eq str i closurebraceisokay and { /j 1 def /depth 1 def { i j add size ge { exit } if /char str i j add get def char 123 eq str i j add closurebraceisokay and { /depth depth 1 add def } if char 125 eq str i j add closurebraceisokay and { /depth depth 1 sub def } if depth 0 eq { exit } if /j j 1 add def } loop % make sure the function was closed depth 0 eq { /tokens tokens [ T_LAMBDA str i 1 add j 1 sub getinterval lex ] arraypush def % push function onto array } { /tokens tokens [ T_ERR false ] arraypush def % unclosed function } ifelse /i i j add 1 add def exit } if % ALL OTHER WORDS true { /j 1 def { i j add size ge { exit } if /char str i j add get def char iswhitespace { exit } if /j j 1 add def } loop /tokens tokens str i j getinterval toktype arraypush def /i i j add def % rectify counter exit } if } case } loop tokens end } def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % PRIMITIVE FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /p_binmath {4 dict begin { % primitive binary math, composited for readability /dstack exch def % get stack arg and make sure it has enough elements dstack length 2 lt { dstack [ T_ERR null ] arraypush exit } if /op exch def % get op arg /arg1 dstack 1 getneg def % get args and make sure they're okay arg1 isint not { dstack [ T_ERR null ] arraypush exit } if /arg2 dstack 2 getneg def arg2 isint not { dstack [ T_ERR null ] arraypush exit } if op P_DIV eq { arg1 1 get 0 eq { dstack [ T_ERR null ] arraypush exit } if } if % division by zero op P_REM eq { arg1 1 get 0 eq { dstack [ T_ERR null ] arraypush exit } if } if % remainder by zero dstack arraypopd arraypopd % pop args off of stack { op P_ADD eq { [ T_INT arg2 1 get arg1 1 get add ] exit } if op P_SUB eq { [ T_INT arg2 1 get arg1 1 get sub ] exit } if op P_MUL eq { [ T_INT arg2 1 get arg1 1 get mul ] exit } if op P_DIV eq { [ T_INT arg2 1 get arg1 1 get idiv_proper pop ] exit } if op P_REM eq { [ T_INT arg2 1 get arg1 1 get idiv_proper exch pop ] exit } if op P_LESSTHAN eq { [ T_BOOL arg2 1 get arg1 1 get lt ] exit } if op P_EQUAL eq { [ T_BOOL arg2 1 get arg1 1 get eq ] exit } if } case arraypush % put new token on stack for return } case end } def /p_binbool {4 dict begin { % binary boolean functions (and, or) /dstack exch def % get stack arg and make sure it has enough elements dstack length 2 lt { dstack [ T_ERR null ] arraypush exit } if /op exch def % get op arg /arg1 dstack 1 getneg def % get args and make sure they're okay arg1 isbool not { dstack [ T_ERR null ] arraypush exit } if /arg2 dstack 2 getneg def arg2 isbool not { dstack [ T_ERR null ] arraypush exit } if dstack arraypopd arraypopd % pop args off of stack { op P_AND eq { [ T_BOOL arg2 1 get arg1 1 get and ] exit } if op P_OR eq { [ T_BOOL arg2 1 get arg1 1 get or ] exit } if } case arraypush % put new token on stack for return } case end } def /p_neg {2 dict begin { /dstack exch def % get stack arg dstack length 1 lt { dstack [ T_ERR null ] arraypush exit } if % make sure it's big enough /arg1 dstack 1 getneg def % get first arg from stack arg1 isint not { dstack [ T_ERR null ] arraypush exit } if % make sure it's an integer dstack arraypopd % pop argument off of stack [ T_INT arg1 1 get neg ] arraypush % put new token on stack for return } case end } def /p_bind {7 dict begin { /dstack exch def % get stack arg and make sure it has enough elements /binds exch def % get binds arg dstack length 2 lt { binds dstack [ T_ERR null ] arraypush exit } if /arg1 dstack 1 getneg def % get args and make sure they're okay /arg2 dstack 2 getneg def arg2 istrap not arg2 isname not and { binds dstack [ T_ERR null ] arraypush exit } if arg2 istrap { /name arg2 2 get def % if there is a trapped name, use that } { /name arg2 1 get def % otherwise, use value of T_NAME } ifelse /bindst binds 1 get def % binds table /bindsl binds 0 get def % binds link to parent env false % flag on stack determines if we found a matching bind bindst { % iterate over all binds 0 get name eq { pop true exit } if % we found one, swap flag and break } forall { binds dstack [ T_ERR null ] arraypush exit } if % error out if the bind already exists [ bindsl bindst [ name [ arg1 0 get arg1 1 get ] ] arraypush ] % push new bind onto binds binds /_GBINDS load eq { % if we're altering the global bindings, update _GBINDS % NOTICE THAT ARRAY COMPARISONS ARE TRUE ONLY IF THEY SHARE THE SAME REFERENT % THIS IS NOT TRUE OF STRING COMPARISONS HOWEVER dup /_GBINDS exch store } if /dstack dstack p_exc def % swap name arg to top of stack dstack arraypopd % pop name arg off of stack } case end } def /p_pop {1 dict begin { /dstack exch def % get stack arg and make sure it has enough elements dstack length 1 lt { dstack [ T_ERR null ] arraypush exit } if dstack arraypopd % pop off last element and pass stack back out } case end } def /p_exc {3 dict begin { /dstack exch def % get stack arg and make sure it has enough elements dstack length 2 lt { dstack [ T_ERR null ] arraypush exit } if /arg1 dstack 1 getneg def % get args /arg2 dstack 2 getneg def dstack arraypopd arraypopd arg1 arraypush arg2 arraypush % swap args } case end } def /p_dup {2 dict begin { /dstack exch def dstack length 1 lt { dstack [ T_ERR null ] arraypush exit } if /arg1 dstack 1 getneg def dstack arg1 arraycopy arraypush } case end } def /p_load {3 dict begin { /dstack exch def % get dstack and binds off of stack /binds exch def dstack length 1 lt { binds dstack [ T_ERR null ] arraypush exit } if % make sure there's at least one arg on the stack /arg1 dstack 1 getneg def % get arg arg1 isstring not { binds dstack [ T_ERR null ] arraypush exit } if % make sure the arg is a string /dstack dstack arraypopd def % pop arg off of stack arg1 1 get readfile { % try to read file, see if we got anything lex % send file contents off to lexer dstack binds gram % send lexed tokens off to grammar /dstack exch def /binds exch def binds dstack [ T_BOOL true ] arraypush % opened file, push true } { binds dstack [ T_BOOL false ] arraypush % couldn't open file, push false } ifelse } case end } def /p_not {2 dict begin { /dstack exch def dstack length 1 lt { binds dstack [ T_ERR null ] arraypush exit } if % make sure there's at least one arg on the stack /arg1 dstack 1 getneg def % get arg arg1 isbool not { binds dstack [ T_ERR null ] arraypush exit } if % make sure the arg is a bool dstack arraypopd % pop arg off stack [ T_BOOL arg1 1 get not ] arraypush % push new arg on stack } case end } def /p_if {2 dict begin { /dstack exch def dstack length 3 lt { binds dstack [ T_ERR null ] arraypush exit } if % make sure there's at least 3 args on the stack /arg1 dstack 1 getneg def % get the first arg arg1 isbool not { binds dstack [ T_ERR null ] arraypush exit } if % make sure the arg is a bool dstack arraypopd % pop first arg off of stack arg1 1 get { arraypopd % if arg1 is true, pop off top of stack } { p_exc arraypopd % if arg1 is false, pop off second from top of stack } ifelse } case end } def /p_apply {3 dict begin { /dstack exch def % get data stack /binds exch def % get binds, these do not need to be returned dstack length 1 lt { binds dstack [ T_ERR null ] arraypush exit } if % make sure there's at least one arg on the stack /arg1 dstack 1 getneg def % get arg arg1 islambda not { binds dstack [ T_ERR null ] arraypush exit } if % make sure the arg is a function % APPLYING GRAMMAR arg1 1 get % arg 1: tokens to evaluate dstack arraypopd % arg 2: stack to operate on (minus the incoming argument) [/_GBINDS load []] % arg 3: bindings (empty/linked to global for new closures, for now) gram % grammar returns new stack and bindings exch pop % swap new bindings to front and pop them off to discard them } case end } def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % GRAMMAR FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /gram {4 dict begin % a grammar /binds exch def % get binds from args /dstack exch def % get stack from args /tokens exch def tokens { % iterate over all tokens /token exch def % this token { % act according to datatype token isprim { % primitive tokens { token 1 get % get primitive ID from token dup P_ADD eq { dstack p_binmath /dstack exch def exit } if dup P_SUB eq { dstack p_binmath /dstack exch def exit } if dup P_MUL eq { dstack p_binmath /dstack exch def exit } if dup P_DIV eq { dstack p_binmath /dstack exch def exit } if dup P_REM eq { dstack p_binmath /dstack exch def exit } if dup P_LESSTHAN eq { dstack p_binmath /dstack exch def exit } if dup P_EQUAL eq { dstack p_binmath /dstack exch def exit } if dup P_NEG eq { pop dstack p_neg /dstack exch def exit } if dup P_AND eq { dstack p_binbool /dstack exch def exit } if dup P_OR eq { dstack p_binbool /dstack exch def exit } if dup P_NOT eq { pop dstack p_not /dstack exch def exit } if dup P_BIND eq { pop binds dstack p_bind /dstack exch def /binds exch def exit } if dup P_LOAD eq { pop binds dstack p_load /dstack exch def /binds exch def exit } if dup P_POP eq { pop dstack p_pop /dstack exch def exit } if dup P_EXC eq { pop dstack p_exc /dstack exch def exit } if dup P_QUIT eq { pop quit exit } if dup P_DUP eq { pop dstack p_dup /dstack exch def exit } if dup P_IF eq { pop dstack p_if /dstack exch def exit } if dup P_APPLY eq { pop binds dstack p_apply /dstack exch def exit } if dup P_BINDS eq { binds == pop exit } if % debug bind table dump } case } if token islambda { /dstack dstack token arraypush def % append token to stack } if token isname { % name tokens /rbinds binds def true % flag on stack determines if we found a matching bind or not { % deep search for existing bind rbinds 1 get { % scan each binding in this environment dup 0 get token 1 get eq { % see if this is the binding 1 get % get bind value token 1 get arraypush dup == % add trap name to token (no need to copy) /dstack exch dstack exch arraypush def % push value onto stack pop false % set success flag exit % break out of loop } if pop % pop off non-matching binding } forall dup not { exit } if % stop following links if we found a binding /rbinds rbinds 0 get def % follow next static link rbinds null eq { exit } if % give up if we've exhausted all static links } loop { /dstack dstack token arraypush def } if % push name literal on stack if we didn't find a binding exit % break out } if token isliteral { % literal tokens /dstack dstack token arraypush def % append token to stack exit % break out } if } case } forall binds dstack % pass stack and binds back out end } def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % MAIN REPL %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /prettyprint { % print out a token all pretty-like dup 0 get { dup T_BOOL eq { pop 1 get { (:true:\n) pf } { (:false:\n) pf } ifelse exit } if dup T_INT eq { pop 1 get == exit } if dup T_NAME eq { pop 1 get pf (\n) pf exit } if dup T_STRING eq { (") pf pop 1 get pf ("\n) pf exit } if dup T_ERR eq { pop pop (:error:\n) pf exit } if dup T_LAMBDA eq { (:closure:\n) pf exit } if } case } def /repl {2 dict begin /dstack [] def % create the stack /binds [null [[(TestBind00) [T_INT 42]] [(TestBind01) [ T_INT 43 ]]]] def % create the binds /_GBINDS binds def % global binds, will later be referenced using load, see manual for load % DO NOT DEFINE _GBINDS IN ANY SUBORDINATE DICTIONARY OR THINGS WILL GO PEAR-SHAPED { % input loop (repl> ) pf % print out the prompt stdin readbuf readline % read a line in not { quit } if % if we got eof, exit lex % lex this string dstack binds gram % gram the tokens we just lexed /dstack exch def % get new data stack /binds exch def % get new binds 1 1 dstack length { dstack exch getneg prettyprint } for % iterate over the stack backwards to print it out } loop end } def % BE VEWWY VEWWY QUIET. I'M HUNTING STACK WEAKS!