%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % hw2.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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /T_BOOL 0 def /P_ADD 0 def /P_BIND 6 def /P_BINDS 12 def /T_INT 1 def /P_SUB 1 def /P_LOAD 7 def /T_NAME 2 def /P_MUL 2 def /P_POP 8 def /T_STRING 3 def /P_DIV 3 def /P_EXC 9 def /T_ERR 4 def /P_REM 4 def /P_QUIT 10 def /T_PRIM 5 def /P_NEG 5 def /P_DUP 11 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 { true } { false } ifelse } def /isbool { 0 get T_BOOL eq { true } { false } ifelse } def /isstring { 0 get T_STRING eq { true } { false } ifelse } def /iserr { 0 get T_ERR eq { true } { false } ifelse } def /isliteral { dup isint { true } { dup isbool { true } { dup isstring { true } { dup iserr { true } { false } ifelse } ifelse } ifelse } ifelse exch pop } def % if a token is a literal type /isprim { 0 get T_PRIM eq { true } { false } ifelse } def % if a token is a primitive /isname { 0 get T_NAME eq { true } { false } ifelse } def % if a token is a 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 { <<>> 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 (: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 /lex {<<>> begin % a lexer /str exch def % save the input string /size str length def % get string length /i 0 def % start index /j 0 def % end index /tokens [] def { % loop over entire string i size ge { exit } if % break if we're at the end str i get iswhitespace not { % if str[i] isn't whitespace str i get 34 eq { % if str[i] is a double-quote (") /i i 1 add def % i++ /j 0 def % j = 0 { % loop forever size i j add eq { exit } if % break for end of string str i j add get 34 eq { exit } if % break for quote /j j 1 add def % j++ } loop i j add size ge { % did we find an unmatched quote? /tokens tokens [ T_ERR false ] arraypush def % push an error } { /tokens tokens [ T_STRING str i j getinterval stringcopy ] arraypush def % put string in token array } ifelse /i i j add def % i += j } { % if str[i] isn't a double-quote /j 0 def % j = 0 { size i j add eq { exit } if % break for end of string str i j add get iswhitespace { exit } if % break for whitespace /j j 1 add def % j++ } loop /tokens tokens arraygrow def tokens dup length 1 sub str i j getinterval toktype put /i i j add def % i += j } ifelse } if /i i 1 add def % i++ } loop tokens % push tokens out as return value end } def %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % PRIMITIVE FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /p_binmath {<<>> 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 } case arraypush % put new token on stack for return } case end } def /p_neg {<<>> 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 {<<>> 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 isname not { binds dstack [ T_ERR null ] arraypush exit } if false % flag on stack determines if we found a matching bind binds { % iterate over all binds 0 get arg2 1 get eq { pop true exit } if % we found one, swap flag and break } forall { binds dstack [ T_ERR null ] arraypush exit } if % see if we found the bind, error out if so binds [ arg2 1 get [ arg1 0 get arg1 1 get ]] arraypush % push new bind onto binds dstack arraypopd arraypopd % pop args off of stack } case end } def /p_pop {<<>> 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 {<<>> 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 {<<>> 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 {<<>> 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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % GRAMMAR FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% /gram {<<>> 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_NEG eq { pop dstack p_neg /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_BINDS eq { binds == pop exit } if % debug: dumps bind table } case } if token isname { % name tokens true % flag on stack determines if we found a matching bind binds { % iterate over all binds /bind exch def % this bind bind 0 get token 1 get eq { % if the name matches this bind /dstack dstack bind 1 get arraycopy arraypush def % _COPY_ the bind's value onto the stack pop false exit } if } forall { /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 array 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 } if } case } def /repl {<<>> begin /dstack [] def % create the stack /binds [ [ (TestBind00) [ T_INT 42 ] ] [ (TestBind01) [ T_INT 43 ] ] ] def % create the binds { % 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!