# Assembled with the script 'compile.pss' add "#!/usr/bin/perl\n" print clear start: read #-------------- testclass [:space:] jumpfalse block.end.7706 clear jump parse block.end.7706: #--------------- # We can ellide all these single character tests, because # the stack token is just the character itself with a * # Braces {} are used for blocks of commands, ',' and '.' for concatenating # tests with OR or AND logic. 'B' and 'E' for begin and end # tests, '!' is used for negation, ';' is used to terminate a # command. # ( and ) may be used for a test expression grammar testis "(" jumptrue 20 testis ")" jumptrue 18 testis "{" jumptrue 16 testis "}" jumptrue 14 testis ";" jumptrue 12 testis "," jumptrue 10 testis "." jumptrue 8 testis "!" jumptrue 6 testis "B" jumptrue 4 testis "E" jumptrue 2 jump block.end.8204 put add "*" push jump parse block.end.8204: #--------------- # format: "text" testis "\"" jumpfalse block.end.8925 # save the start line number (for error messages) in case # there is no terminating quote character. clear add "line " ll add ", char " cc put clear until "\"" testends "\"" jumptrue block.end.8577 clear add "Unterminated quote character (\") starting at " get add " !\n" print quit block.end.8577: # check for empty quotes as arguments for escape etc # in perl all tests and replace use regexes, so we dont need # quotes around, also need \\Q to stop metacharacters making # problems for the regex (eg ^$.{} etc), but not here because # "add" doesn't need it. clip put clear add "quote*" push jump parse block.end.8925: #--------------- # format: 'text', single quotes are converted to double quotes # but we must escape embedded double quotes. testis "'" jumpfalse block.end.9530 # save the start line number (for error messages) in case # there is no terminating quote character. clear add "line " ll add " (character " cc add ") " put clear until "'" testends "'" jumptrue block.end.9389 clear add "Unterminated quote (') starting at " get add "!\n" print quit block.end.9389: # all tests and replacements are regexes, but "add" is not clip unescape "'" put clear add "quote*" push jump parse block.end.9530: #--------------- # formats: [:space:] [a-z] [abcd] [:alpha:] etc # should class tests really be multiline??! testis "[" jumpfalse block.end.12749 # save the start line number (for error messages) in case # there is no terminating bracket character. clear add "line " ll add " (character " cc add ") " put clear add "[" until "]" testis "[]" jumpfalse block.end.10054 clear add "pep script error at line " ll add " (character " cc add "): \n" add " empty character class [] \n" print quit block.end.10054: testends "]" jumptrue block.end.10341 clear add "Unterminated class text ([...]) starting at " get add "\n" add " class text can be used in tests or with the 'while' and \n" add " 'whilenot' commands. For example: \n" add " [:alpha:] { while [:alpha:]; print; clear; }\n" add " " print quit block.end.10341: # need to escape quotes? ruby uses /.../ to match escape "\"" # the caret is not a negation operator in pep scripts replace "^" "\\^" # save the class on the tape put clop clop testbegins "-" jumptrue block.end.10676 # not a range class, eg [a-z] so need to escape '-' chars clear get replace "-" "\\-" put block.end.10676: testbegins "-" jumpfalse block.end.11059 # a range class, eg [a-z], check if it is correct clip clip testis "-" jumptrue block.end.11053 clear add "[pep script error] near line " ll add ", char " cc add ": \n" add " Incorrect character range class " get add "\n" add " For example:\n" add " [a-g] # correct\n" add " [f-gh] # error! \n" print clear quit block.end.11053: block.end.11059: clear get # restore class text testbegins "[:" jumpfalse 3 testends ":]" jumpfalse 2 jump block.end.11224 clear add "malformed character class starting at " get add "!\n" print quit block.end.11224: # class in the form [:digit:] testbegins "[:" jumpfalse 3 testis "[:]" jumpfalse 2 jump block.end.12459 clip clip clop clop # unicode posix character classes # Also, abbreviations (not implemented in gh.c yet.) # this should not be tricky in ruby because posix is supported testis "alnum" jumptrue 4 testis "N" jumptrue 2 jump block.end.11524 clear add "[[:alnum:]]" block.end.11524: testis "alpha" jumptrue 4 testis "A" jumptrue 2 jump block.end.11572 clear add "[[:alpha:]]" block.end.11572: # ? can use s.ascii_only?() testis "ascii" jumptrue 4 testis "I" jumptrue 2 jump block.end.11654 clear add "[[:ascii:]]" block.end.11654: # non-standard ruby posix class 'word' testis "word" jumptrue 4 testis "W" jumptrue 2 jump block.end.11745 clear add "[[:word:]]" block.end.11745: testis "blank" jumptrue 4 testis "B" jumptrue 2 jump block.end.11793 clear add "[[:blank:]]" block.end.11793: testis "cntrl" jumptrue 4 testis "C" jumptrue 2 jump block.end.11841 clear add "[[:cntrl:]]" block.end.11841: testis "digit" jumptrue 4 testis "D" jumptrue 2 jump block.end.11889 clear add "[[:digit:]]" block.end.11889: testis "graph" jumptrue 4 testis "G" jumptrue 2 jump block.end.11937 clear add "[[:graph:]]" block.end.11937: testis "lower" jumptrue 4 testis "L" jumptrue 2 jump block.end.11985 clear add "[[:lower:]]" block.end.11985: testis "print" jumptrue 4 testis "P" jumptrue 2 jump block.end.12033 clear add "[[:print:]]" block.end.12033: testis "punct" jumptrue 4 testis "T" jumptrue 2 jump block.end.12081 clear add "[[:punct:]]" block.end.12081: testis "space" jumptrue 4 testis "S" jumptrue 2 jump block.end.12129 clear add "[[:space:]]" block.end.12129: testis "upper" jumptrue 4 testis "U" jumptrue 2 jump block.end.12177 clear add "[[:upper:]]" block.end.12177: testis "xdigit" jumptrue 4 testis "X" jumptrue 2 jump block.end.12227 clear add "[[:xdigit:]]" block.end.12227: testbegins "[[" jumptrue block.end.12452 put clear add "pep script error at line " ll add " (character " cc add "): \n" add "Unknown character class '" get add "'\n" print clear quit block.end.12452: block.end.12459: put clear add "/^" get add "+$/" put clear # add quotes around the class and limits around the # class so it can be used with the string.matches() method # (must match the whole string, not just one character) add "class*" push jump parse block.end.12749: #--------------- # multiline and single line comments, eg #... and #* ... *# testis "#" jumpfalse block.end.14007 clear read testis "\n" jumpfalse block.end.12885 clear jump parse block.end.12885: # checking for multiline comments of the form "#* \n\n\n *#" # these are just ignored at the moment (deleted) testis "*" jumpfalse block.end.13766 # save the line number for possible error message later clear ll put clear until "*#" testends "*#" jumpfalse block.end.13511 # convert to python comments (#), python doesnt have multiline # comments, as far as I know clip clip replace "\n" "\n#" put clear # create a "comment" parse token # comment-out this line to remove multiline comments from the # compiled python # add "comment*"; push; jump parse block.end.13511: # make an unterminated multiline comment an error # to ease debugging of scripts. clear add "unterminated multiline comment #* ... *# \n" add "stating at line number " get add "\n" print clear quit block.end.13766: # single line comments. some will get lost. put clear add "#" get until "\n" clip put clear # comment out this below to remove single line comments # from the output add "comment*" push jump parse block.end.14007: #---------------------------------- # parse command words (and abbreviations) # legal characters for keywords (commands) testclass [abcdefghijklmnopqrstuvwxyzBEOFKGPRUWS+-<>0^=] jumptrue block.end.14380 # error message about a misplaced character put clear add "[Pep syntax error] misplaced char '" get add "' near line " ll add " char " cc add "\n" print quit block.end.14380: # my testclass implementation cannot handle complex lists # eg [a-z+-] this is why I have to write out the whole alphabet while [abcdefghijklmnopqrstuvwxyzBEOFKGPRUWS+-<>0^=] #---------------------------------- # KEYWORDS # here we can test for all the keywords (command words) and their # abbreviated one letter versions (eg: clip k, clop K etc). Then # we can print an error message and abort if the word is not a # legal keyword for the parse-edit language # make ll an alias for "lines" and cc an alias for chars testis "ll" jumpfalse block.end.14965 clear add "lines" block.end.14965: testis "cc" jumpfalse block.end.14997 clear add "chars" block.end.14997: # one letter command abbreviations testis "a" jumpfalse block.end.15064 clear add "add" block.end.15064: testis "k" jumpfalse block.end.15094 clear add "clip" block.end.15094: testis "K" jumpfalse block.end.15124 clear add "clop" block.end.15124: testis "D" jumpfalse block.end.15157 clear add "replace" block.end.15157: testis "d" jumpfalse block.end.15188 clear add "clear" block.end.15188: testis "t" jumpfalse block.end.15219 clear add "print" block.end.15219: testis "p" jumpfalse block.end.15248 clear add "pop" block.end.15248: testis "P" jumpfalse block.end.15278 clear add "push" block.end.15278: testis "u" jumpfalse block.end.15311 clear add "unstack" block.end.15311: testis "U" jumpfalse block.end.15342 clear add "stack" block.end.15342: testis "G" jumpfalse block.end.15371 clear add "put" block.end.15371: testis "g" jumpfalse block.end.15400 clear add "get" block.end.15400: testis "x" jumpfalse block.end.15430 clear add "swap" block.end.15430: testis ">" jumpfalse block.end.15458 clear add "++" block.end.15458: testis "<" jumpfalse block.end.15486 clear add "--" block.end.15486: testis "m" jumpfalse block.end.15516 clear add "mark" block.end.15516: testis "M" jumpfalse block.end.15544 clear add "go" block.end.15544: testis "r" jumpfalse block.end.15574 clear add "read" block.end.15574: testis "R" jumpfalse block.end.15605 clear add "until" block.end.15605: testis "w" jumpfalse block.end.15636 clear add "while" block.end.15636: testis "W" jumpfalse block.end.15670 clear add "whilenot" block.end.15670: testis "n" jumpfalse block.end.15701 clear add "count" block.end.15701: testis "+" jumpfalse block.end.15729 clear add "a+" block.end.15729: testis "-" jumpfalse block.end.15757 clear add "a-" block.end.15757: testis "0" jumpfalse block.end.15787 clear add "zero" block.end.15787: testis "c" jumpfalse block.end.15818 clear add "chars" block.end.15818: testis "l" jumpfalse block.end.15849 clear add "lines" block.end.15849: testis "^" jumpfalse block.end.15881 clear add "escape" block.end.15881: testis "v" jumpfalse block.end.15915 clear add "unescape" block.end.15915: testis "z" jumpfalse block.end.15946 clear add "delim" block.end.15946: testis "S" jumpfalse block.end.15977 clear add "state" block.end.15977: testis "q" jumpfalse block.end.16007 clear add "quit" block.end.16007: testis "s" jumpfalse block.end.16038 clear add "write" block.end.16038: testis "o" jumpfalse block.end.16067 clear add "nop" block.end.16067: testis "rs" jumpfalse block.end.16101 clear add "restart" block.end.16101: testis "rp" jumpfalse block.end.16135 clear add "reparse" block.end.16135: # what about upper, lower and cap? # some extra syntax for testeof and testtape testis "eof" jumptrue 8 testis "EOF" jumptrue 6 testis "" jumptrue 4 testis "" jumptrue 2 jump block.end.16342 clear add "$self->{'eof'}" put clear add "test*" push jump parse block.end.16342: testis "==" jumptrue 4 testis "<==>" jumptrue 2 jump block.end.16492 clear add "$self->{'work'} eq ${$self->{'tape'}}[$self->{'cell'}]" put clear add "test*" push jump parse block.end.16492: testis "jump" jumptrue 18 testis "jumptrue" jumptrue 16 testis "jumpfalse" jumptrue 14 testis "testis" jumptrue 12 testis "testclass" jumptrue 10 testis "testbegins" jumptrue 8 testis "testends" jumptrue 6 testis "testeof" jumptrue 4 testis "testtape" jumptrue 2 jump block.end.16820 put clear add "The instruction '" get add "' near line " ll add " (character " cc add ")\n" add "can be used in pep assembly code but not scripts. \n" print clear quit block.end.16820: # show information if these "deprecated" commands are used testis "Q" jumptrue 4 testis "bail" jumptrue 2 jump block.end.17227 put clear add "The instruction '" get add "' near line " ll add " (character " cc add ")\n" add "is no longer part of the pep language (july 2020). \n" add "use 'quit' instead of 'bail', and use 'unstack; print;' \n" add "instead of 'state'. \n" print clear quit block.end.17227: testis "add" jumptrue 82 testis "clip" jumptrue 80 testis "clop" jumptrue 78 testis "replace" jumptrue 76 testis "upper" jumptrue 74 testis "lower" jumptrue 72 testis "cap" jumptrue 70 testis "clear" jumptrue 68 testis "print" jumptrue 66 testis "state" jumptrue 64 testis "pop" jumptrue 62 testis "push" jumptrue 60 testis "unstack" jumptrue 58 testis "stack" jumptrue 56 testis "put" jumptrue 54 testis "get" jumptrue 52 testis "swap" jumptrue 50 testis "++" jumptrue 48 testis "--" jumptrue 46 testis "mark" jumptrue 44 testis "go" jumptrue 42 testis "read" jumptrue 40 testis "until" jumptrue 38 testis "while" jumptrue 36 testis "whilenot" jumptrue 34 testis "count" jumptrue 32 testis "a+" jumptrue 30 testis "a-" jumptrue 28 testis "zero" jumptrue 26 testis "chars" jumptrue 24 testis "lines" jumptrue 22 testis "nochars" jumptrue 20 testis "nolines" jumptrue 18 testis "escape" jumptrue 16 testis "unescape" jumptrue 14 testis "delim" jumptrue 12 testis "quit" jumptrue 10 testis "write" jumptrue 8 testis "nop" jumptrue 6 testis "reparse" jumptrue 4 testis "restart" jumptrue 2 jump block.end.17628 put clear add "word*" push jump parse block.end.17628: #------------ # the .reparse command and "parse label" is a simple way to # make sure that all shift-reductions occur. It should be used inside # a block test, so as not to create an infinite loop. There is # no "goto" in java so we need to use labelled loops to # implement .reparse/parse> testis "parse>" jumpfalse block.end.18287 clear count testis "0" jumptrue block.end.18131 clear add "script error:\n" add " extra parse> label at line " ll add ".\n" print quit block.end.18131: clear add "# parse> parse label" put clear add "parse>*" push # use accumulator to indicate after parse> label a+ jump parse block.end.18287: # -------------------- # implement "begin-blocks", which are only executed # once, at the beginning of the script (similar to awk's BEGIN {} rules) testis "begin" jumpfalse block.end.18498 put add "*" push jump parse block.end.18498: add " << unknown command on line " ll add " (char " cc add ")" add " of source file. \n" print clear quit # ---------------------------------- # PARSING PHASE: # Below is the parse/compile phase of the script. Here we pop tokens off the # stack and check for sequences of tokens eg "word*semicolon*". If we find a # valid series of tokens, we "shift-reduce" or "resolve" the token series eg # word*semicolon* --> command* # At the same time, we manipulate (transform) the attributes on the tape, as # required. parse: # for debugging, add "# line " ll add " char " cc add ": " print clear unstack print stack add "\n" print clear #------------------------------------- # 2 tokens #------------------------------------- pop pop # All of the patterns below are currently errors, but may not # be in the future if we expand the syntax of the parse # language. Also consider: # andlist*, orlist*, test*, !* ,* ;* B* E* # It is nice to trap the errors here because we can emit some # (hopefully not very cryptic) error messages with a line number. # Otherwise the script writer has to debug with # pep -a asm.pp -I scriptfile testis "word*word*" jumptrue 28 testis "word*}*" jumptrue 26 testis "word*!*" jumptrue 24 testis "word*,*" jumptrue 22 testis "quote*word*" jumptrue 20 testis "quote*class*" jumptrue 18 testis "quote*}*" jumptrue 16 testis "class*word*" jumptrue 14 testis "class*quote*" jumptrue 12 testis "class*class*" jumptrue 10 testis "test*test*" jumptrue 8 testis "test*quote*" jumptrue 6 testis "quote*test*" jumptrue 4 testis "class*}*" jumptrue 2 jump block.end.20150 add " (Token stack) \nValue: \n" get add "\nValue: \n" ++ get -- add "\n" add "Error near line " ll add " (char " cc add ")" add " of pep script (missing semicolon?) \n" print clear quit block.end.20150: # todo: use negative test for test* errors # eg what can follow a test* i.e ,* .* )* {* testis "test*!*" jumptrue 10 testis "class*!*" jumptrue 8 testis "quote*!*" jumptrue 6 testis "andlist*!*" jumptrue 4 testis "orlist*!*" jumptrue 2 jump block.end.20626 stack add "[Pep syntax error]\n near line " ll add ", char " cc add "\n" add "\n" add " Reversed negation operator (!)? In Pep/nom syntax the negation \n" add " operator precedes the test it applies to, for example:\n" add " !\"a\" { ... } # correct\n" add " \"a\"! { ... } # error \n" print quit block.end.20626: testis "{*;*" jumptrue 6 testis ";*;*" jumptrue 4 testis "}*;*" jumptrue 2 jump block.end.20817 push push add "[Pep syntax error] near line " ll add ", char " cc add " of pep script: misplaced semi-colon? ; \n" print quit block.end.20817: testis ",*{*" jumptrue 4 testis ".*{*" jumptrue 2 jump block.end.21001 push push add "Error near line " ll add " (char " cc add ")" add " of script: extra comma or dot in test? \n" print clear quit block.end.21001: testis "command*;*" jumptrue 4 testis "commandset*;*" jumptrue 2 jump block.end.21190 push push add "Error near line " ll add " (char " cc add ")" add " of script: extra semi-colon? \n" print clear quit block.end.21190: testis "!*!*" jumpfalse block.end.21411 push push add "[Pep syntax error] near line " ll add ", char " cc add ": \n double negation '!!' achieves nothing \n" add " so please dont use it.\n" print clear quit block.end.21411: testis "!*{*" jumptrue 4 testis "!*;*" jumptrue 2 jump block.end.21726 push push add "error near line " ll add " (char " cc add ")" add " of script: misplaced negation operator (!)? \n" add " The negation operator precedes tests, for example: \n" add " !B'abc'{ ... } or !(eof),!'abc'{ ... } \n" print clear quit block.end.21726: testis ",*command*" jumpfalse block.end.21902 push push add "error near line " ll add " (char " cc add ")" add " of script: misplaced comma? \n" print clear quit block.end.21902: testis "!*command*" jumpfalse block.end.22107 push push add "error near line " ll add " (at char " cc add ") \n" add " The negation operator (!) cannot precede a command \n" print clear quit block.end.22107: testis ";*{*" jumptrue 6 testis "command*{*" jumptrue 4 testis "commandset*{*" jumptrue 2 jump block.end.22316 push push add "error near line " ll add " (char " cc add ")" add " of script: no test for brace block? \n" print clear quit block.end.22316: testis "{*}*" jumpfalse block.end.22450 push push add "error near line " ll add " of script: empty braces {}. \n" print clear quit block.end.22450: testis "B*class*" jumptrue 4 testis "E*class*" jumptrue 2 jump block.end.22681 push push add "error near line " ll add " of script:\n classes ([a-z], [:space:] etc). \n" add " cannot use the 'begin' or 'end' modifiers (B/E) \n" print clear quit block.end.22681: # or swap the comment before the test testis "comment*{*" jumpfalse block.end.22913 push push add "error near line " ll add " of script: comments cannot occur between \n" add " a test and a brace ({). \n" print clear quit block.end.22913: testis "}*command*" jumpfalse block.end.23063 push push add "error near line " ll add " of script: extra closing brace '}' ?. \n" print clear quit block.end.23063: # E"begin*".!"begin*" { # push; push; # add "error near line "; lines; # add " of script: Begin blocks must precede code \n"; # print; clear; quit; # } # #------------ # The .restart command jumps to the first instruction after the # begin block (if there is a begin block), or the first instruction # of the script. testis ".*word*" jumpfalse block.end.24643 clear ++ get -- testis "restart" jumpfalse block.end.24108 clear count # this is the opposite of .reparse, using run-once loops # cant do next before label, infinite loop. # need to set flag variable. There is a subtlety: .restart can # exist without parse> (although it would be unusual). # before (or without) the parse> label testis "0" jumpfalse block.end.23975 clear # use the comment '# restart' so we can replace # this with 'break' if the parse> label appears later add "restart = $true; next; # restart" block.end.23975: testis "1" jumpfalse block.end.24013 clear add "break;" block.end.24013: # after the parse> label put clear add "command*" push jump parse block.end.24108: testis "reparse" jumpfalse block.end.24430 clear count # check accumulator to see if we are in the "lex" block # or the "parse" block and adjust the .reparse compilation # accordingly. testis "0" jumpfalse block.end.24328 clear add "break;" block.end.24328: testis "1" jumpfalse block.end.24362 clear add "next;" block.end.24362: put clear add "command*" push jump parse block.end.24430: push push add "error near line " ll add " (char " cc add ")" add " of script: \n" add " misplaced dot '.' (use for AND logic or in .reparse/.restart \n" print clear quit block.end.24643: #--------------------------------- # Compiling comments so as to transfer them to the perl code testis "comment*command*" jumptrue 6 testis "command*comment*" jumptrue 4 testis "commandset*comment*" jumptrue 2 jump block.end.24898 clear get add "\n" ++ get -- put clear add "command*" push jump parse block.end.24898: testis "comment*comment*" jumpfalse block.end.25012 clear get add "\n" ++ get -- put clear add "comment*" push jump parse block.end.25012: # ----------------------- # negated tokens. # This is a new more elegant way to negate a whole set of # tests (tokens) where the negation logic is stored on the # stack, not in the current tape cell. We just add "not" to # the stack token. # eg: ![:alpha:] ![a-z] ![abcd] !"abc" !B"abc" !E"xyz" # This format is used to indicate a negative test for # a brace block. eg: ![aeiou] { add "< not a vowel"; print; clear; } testis "!*test*" jumpfalse block.end.25642 clear add "!(" ++ get # check for double negative, which is an error ? # B"!(!(" { } -- add ")" put clear add "test*" push jump parse block.end.25642: # transform quotes and classses to tests, this greatly reduces the number # of rules required for other reductions testis ",*quote*" jumptrue 6 testis ".*quote*" jumptrue 4 testis "!*quote*" jumptrue 2 jump block.end.25944 # but need to stop string interp push clear add "$self->{'work'} eq '" get add "'" put clear add "test*" push jump parse block.end.25944: # transform quotes to tests testis "quote*,*" jumptrue 6 testis "quote*.*" jumptrue 4 testis "quote*{*" jumptrue 2 jump block.end.26147 replace "quote*" "test*" push push -- -- add "$self->{'work'} eq '" get add "'" put ++ ++ clear jump parse block.end.26147: # transform classes to tests testis ",*class*" jumptrue 4 testis ".*class*" jumptrue 2 jump block.end.26313 push clear add "$self->{'work'} =~ /" get add "/" put clear add "test*" push jump parse block.end.26313: # transform classes to tests testis "class*,*" jumptrue 6 testis "class*.*" jumptrue 4 testis "class*{*" jumptrue 2 jump block.end.26517 replace "class*" "test*" push push -- -- add "$self->{'work'} =~ /" get add "/" put ++ ++ clear jump parse block.end.26517: #----------------------------------------- # format: E"text" or E'text' # This format is used to indicate a "workspace-ends-with" text before # a brace block. testis "E*quote*" jumpfalse block.end.27124 clear add "test*" push get # quotes don't have ".." in this perl translator testis "" jumpfalse block.end.27030 # empty argument is an error clear add "pep script error near line " ll add " (character " cc add "): \n" add " empty argument for end-test (E\"\") \n" print quit block.end.27030: clear add "$self->{'work'} =~ /" get add "$/" -- put ++ clear jump parse block.end.27124: #----------------------------------------- # format: B"sometext" or B'sometext' # A 'B' preceding some quoted text is used to indicate a # 'workspace-begins-with' test, before a brace block. testis "B*quote*" jumpfalse block.end.27858 clear add "test*" push get testis "" jumpfalse block.end.27692 # empty argument is an error, since it means nothing to say # that a string begins with nothing. clear add "[pep syntax error] near line " ll add ", char " cc add ": \n" add " empty argument for begin-test (B\"\") \n" print quit block.end.27692: # how to quote regex meta chars? clear get escape "$" put clear add "$self->{'work'} =~ /^" get add "/" -- put ++ clear jump parse block.end.27858: #-------------------------------------------- # ebnf: command := word, ';' ; # formats: "pop; push; clear; print; " etc # all commands need to end with a semi-colon except for # .reparse and .restart testis "word*;*" jumpfalse block.end.31443 clear # check if command requires parameter get testis "add" jumptrue 18 testis "while" jumptrue 16 testis "whilenot" jumptrue 14 testis "mark" jumptrue 12 testis "go" jumptrue 10 testis "escape" jumptrue 8 testis "unescape" jumptrue 6 testis "delim" jumptrue 4 testis "replace" jumptrue 2 jump block.end.28408 put clear add "'" get add "'" add " << command needs an argument, on line " ll add " of script.\n" print clear quit block.end.28408: # the new until tape command testis "until" jumpfalse block.end.28575 clear add "$self->until(${$self->{'tape')}[$self->{'cell'}]); # until (tape)" put block.end.28575: testis "clip" jumpfalse block.end.28675 clear add "chop($self->{'work'}); # clip" put block.end.28675: # check indices testis "clop" jumpfalse block.end.28804 clear add "$self->{'work'} =~ s/^.//s; # clop \n" put block.end.28804: testis "clear" jumpfalse block.end.28877 clear add "$self->{'work'} = ''; # clear" put block.end.28877: testis "upper" jumpfalse block.end.28961 clear add "$self->{'work'} = uc($self->{'work'}); # upper" put block.end.28961: testis "lower" jumpfalse block.end.29045 clear add "$self->{'work'} = lc($self->{'work'}); # lower" put block.end.29045: testis "cap" jumpfalse block.end.29120 clear add "$self->{'work'} .capitalize! # capital" put block.end.29120: testis "print" jumpfalse block.end.29238 clear add "$self->{\"output\"}->print($self->{\"work\"}); # print" put block.end.29238: testis "state" jumpfalse block.end.29311 clear add "$self->printState(); # state" put block.end.29311: testis "pop" jumpfalse block.end.29364 clear add "$self->popToken();" put block.end.29364: testis "push" jumpfalse block.end.29419 clear add "$self->pushToken();" put block.end.29419: testis "unstack" jumpfalse block.end.29513 clear add "while ($self->popToken()) { next; } # unstack " put block.end.29513: testis "stack" jumpfalse block.end.29603 clear add "while ($self->pushToken()) { next; } # stack " put block.end.29603: testis "put" jumpfalse block.end.29731 clear add "${$self->{'tape'}}[$self->{'cell'}] = $self->{'work'}; # put " put block.end.29731: testis "get" jumpfalse block.end.29856 clear add "$self->{'work'} .= ${$self->{'tape'}}[$self->{'cell'}]; # get" put block.end.29856: testis "swap" jumpfalse block.end.30058 clear add "$self->{'work'}, @{$self->{'tape'}}[$self->{'cell'}] = \n" add " @{$self->{'tape'}}[$self->{'cell'}], $self->{'work'}; # swap " put block.end.30058: testis "++" jumpfalse block.end.30121 clear add "$self->increment(); # ++" put block.end.30121: testis "--" jumpfalse block.end.30230 clear add "if ($self->{'cell'} > 0) { $self->{'cell'} -= 1; } # --" put block.end.30230: testis "read" jumpfalse block.end.30300 clear add "$self->readChar(); # read" put block.end.30300: testis "count" jumpfalse block.end.30418 clear add "$self->{'work'} .= $self->{'counter'}; # count " put block.end.30418: testis "a+" jumpfalse block.end.30486 clear add "$self->{'counter'} += 1; # a+ " put block.end.30486: testis "a-" jumpfalse block.end.30554 clear add "$self->{'counter'} -= 1; # a- " put block.end.30554: testis "zero" jumpfalse block.end.30624 clear add "$self->{'counter'} = 0; # zero " put block.end.30624: testis "chars" jumpfalse block.end.30739 clear add "$self->{'work'} .= $self->{'charsRead'}; # chars " put block.end.30739: testis "lines" jumpfalse block.end.30854 clear add "$self->{'work'} .= $self->{'linesRead'}; # lines " put block.end.30854: testis "nochars" jumpfalse block.end.30961 clear add "$self->{'charsRead'} = 0; # nochars " put block.end.30961: testis "nolines" jumpfalse block.end.31069 clear add "$self->{'linesRead'} = 0; # nolines " put block.end.31069: # use a labelled loop to quit script, also an error code testis "quit" jumpfalse block.end.31174 clear add "exit();" put block.end.31174: # write to file name not "sav.pp" testis "write" jumpfalse block.end.31289 clear add "File->write('sav.pp', $self->{'work'} )" put block.end.31289: # convert to "pass" which does nothing. testis "nop" jumpfalse block.end.31389 clear add "# nop: no-operation" put block.end.31389: clear add "command*" push jump parse block.end.31443: #----------------------------------------- # ebnf: commandset := command , command ; testis "command*command*" jumptrue 4 testis "commandset*command*" jumptrue 2 jump block.end.31758 clear add "commandset*" push # format the tape attributes. Add the next command on a newline -- get add "\n" ++ get -- put ++ clear jump parse block.end.31758: #------------------- # 3 tokens #------------------- pop #----------------------------- # some 3 token errors!!! # not a comprehensive list of 3 token errors testis "{*quote*;*" jumptrue 22 testis "{*test*;*" jumptrue 20 testis "{*class*;*" jumptrue 18 testis "{*andlist*;*" jumptrue 16 testis "{*orlist*;*" jumptrue 14 testis "commandset*quote*;*" jumptrue 12 testis "command*quote*;*" jumptrue 10 testis "quote*quote*,*" jumptrue 8 testis "quote*quote*.*" jumptrue 6 testis "class*class*,*" jumptrue 4 testis "class*class*.*" jumptrue 2 jump block.end.32296 push push push add "[Pep syntax error]\n near line " ll add ", char " cc add " (misplaced semicolon?) \n" print clear quit block.end.32296: # extra comma in tests, already caught in 2 token errors testis "andlist*.*{*" jumptrue 8 testis "orlist*,*{*" jumptrue 6 testis "test*,*{*" jumptrue 4 testis "test*.*{*" jumptrue 2 jump block.end.32597 push push push add "[Pep syntax error]\n near line " ll add ", char " cc add "\n" add " (trailing extra comma in test?)\n" print clear quit block.end.32597: # missing comma in tests ? testbegins "quote*quote*" jumpfalse 5 testis "quote*quote*" jumptrue 3 testends ";*" jumpfalse 2 jump block.end.32901 put clear add "[Pep syntax error]\n near line " ll add ", char " cc add "\n" add " (trailing extra comma in test?)\n" add " (parse token stack was: " get add ")\n" print clear quit block.end.32901: # errors! mixing AND and OR concatenation without brackets testis "andlist*,*" jumptrue 4 testis "orlist*.*" jumptrue 2 jump block.end.33476 # push the tokens back to make debugging easier push push add "[Pep syntax error] mixing AND (.) and OR (,) concatenation in \n" add " in pep script near line " ll add " char " cc add "\n" add " Use brackets () to combine AND and OR logic." add " \n" add " For example:\n" add " B\".\".!E\"/\".[abcd./] { print; } # Correct!\n" add " (B\".\".E\"/\"),E\".txt\" { print; } # Correct!\n" add " B\".\".!E\"/\",[abcd./] { print; } # Error! \n" print clear quit block.end.33476: # to simplify subsequent tests, transmogrify a single command # to a commandset (multiple commands). testis "{*command*}*" jumpfalse block.end.33670 clear add "{*commandset*}*" push push push jump parse block.end.33670: # brackets around tests will be ignored. This also allows # (eof) to work etc. testis "(*test*)*" jumpfalse block.end.33842 clear ++ get -- put clear add "test*" push jump parse block.end.33842: # brackets will allow mixing AND and OR logic testis "(*orlist*)*" jumptrue 4 testis "(*andlist*)*" jumptrue 2 jump block.end.33997 clear ++ get -- put clear add "test*" push jump parse block.end.33997: # ------------- # parses and compiles concatenated tests # eg: 'a',B'b',E'c',[def],[:space:],[g-k] { ... testis "test*,*test*" jumptrue 4 testis "orlist*,*test*" jumptrue 2 jump block.end.34469 # OR logic concatenation # put brackets around tests even though operator # precedence should take care of it testis "test*,*test*" jumpfalse block.end.34333 clear add "(" get add ")" block.end.34333: testis "orlist*,*test*" jumpfalse block.end.34370 clear get block.end.34370: add " || (" ++ ++ get -- -- add ")" put clear add "orlist*" push jump parse block.end.34469: # ------------- # AND logic # parses and compiles concatenated AND tests # eg: 'a',B'b',E'c',[def],[:space:],[g-k] { ... # negated tests can be chained with non negated tests. # eg: B'http' . !E'.txt' { ... } testis "test*.*test*" jumptrue 4 testis "andlist*.*test*" jumptrue 2 jump block.end.34982 # AND logic concatenation # add brackets testis "test*.*test*" jumpfalse block.end.34844 clear add "(" get add ")" block.end.34844: testis "andlist*.*test*" jumpfalse block.end.34882 clear get block.end.34882: add " && (" ++ ++ get -- -- add ")" put clear add "andlist*" push jump parse block.end.34982: #-------------------------------------------- # ebnf: command := keyword , quoted-text , ";" ; # format: add "text"; testis "word*quote*;*" jumpfalse block.end.39843 clear get testis "replace" jumpfalse block.end.35325 # error add "< command requires 2 parameters, not 1 \n" add "near line " ll add " of script. \n" print clear quit block.end.35325: # check whether argument is single character, otherwise # throw an error. Also, check that argument is not empty # eg "". Its probably silly to allow while/while not to # have a quote argument, since the same can be achieved with # >> while [a]; etc testis "escape" jumptrue 8 testis "unescape" jumptrue 6 testis "while" jumptrue 4 testis "whilenot" jumptrue 2 jump block.end.36545 # This is trickier than I thought it would be. clear ++ get -- # check that arg not empty, (but an empty quote is ok # for the second arg of 'replace' testis "\"\"" jumpfalse block.end.36065 clear add "[pep error] near line " ll add " (or char " cc add "): \n" add " command '" get add "\' cannot have an empty argument (\"\") \n" print quit block.end.36065: # quoted text has the quotes still around it. # also handle escape characters like \n \r etc # this needs to be better clip clop clop clop # B "\\" { clip; } clip testis "" jumptrue block.end.36521 clear add "Pep script error near line " ll add " (character " cc add "): \n" add " command '" get add "' takes only a single character argument. \n" print quit block.end.36521: clear get block.end.36545: testis "mark" jumpfalse block.end.36706 clear add "@{$self->{'marks'}}[$self->{'cell] = " ++ get -- add " # mark" put clear add "command*" push jump parse block.end.36706: testis "go" jumpfalse block.end.36842 clear add "goToMark($mm, " ++ get -- add ") # go" put clear add "command*" push jump parse block.end.36842: testis "delim" jumpfalse block.end.37048 clear # the delimiter should be a single character, no? add "$self->{'delimiter'} = " ++ get -- add " # delim " put clear add "command*" push jump parse block.end.37048: testis "add" jumpfalse block.end.37293 clear add "$self->{'work'} .= " ++ get -- add ";" # handle multiline text # check this! \\n or \n replace "\n" "\"\n$self->{\"work\"} .= \"\\n" put clear add "command*" push jump parse block.end.37293: testis "while" jumpfalse block.end.37546 clear add "while ($self->{'peep'} eq " ++ get -- add "); # while \n" add " if ($self->{'eof'}) { break; }\n $self->readChar();\n" add "}" put clear add "command*" push jump parse block.end.37546: testis "whilenot" jumpfalse block.end.37780 clear add "while $self->{'peep'} != " ++ get -- add "; # whilenot \n" add " if ($self->{'eof'}) { break; }\n read(%mm);\n}" put clear add "command*" push jump parse block.end.37780: testis "until" jumpfalse block.end.38389 clear add "$self->until(" ++ get -- # error until cannot have empty argument testis "$self->until(\"\"" jumpfalse block.end.38253 clear add "Pep script error near line " ll add " (character " cc add "): \n" add " empty argument for 'until' \n" add " \n" add " For example:\n" add " until '.txt'; until \">\"; # correct \n" add " until ''; until \"\"; # errors! \n" print quit block.end.38253: # handle multiline argument replace "\n" "\\n" add ");" put clear add "command*" push jump parse block.end.38389: testis "escape" jumpfalse block.end.39234 clear ++ # argument still has quotes around it # it should be a single character since this has been previously # checked. # Actually this should be handled by a function, since it is # a bit complex. (need to check that the char is not already # escaped, which means counting the number of \\ or other escape # chars before it. get clip clop escape "/" # should be 'unescape' but this isnt well implemented in many # translators. replace "\\\"" "\"" put clear add "$self->{\"work\"} =~ s/\\Q" get add "/\\E" # will this work for mm.escape != '\\' # E'"\'", "' { add "$self->{'escape'}"; } add "$self->{\"escape\"}\\Q" get add "/g; # escape" -- put clear add "command*" push jump parse block.end.39234: # replace \n with n for example (only 1 character) testis "unescape" jumpfalse block.end.39652 clear ++ # unescape is not trivial, need to walk the string # hence the method rather than one-liner #add 'mm.unescapeChar('; get; add ') # unescape'; add "$self->{\"work\"} =~ s/$self->{\"escape\"}+" get add ", " get add ") # escape" -- put clear add "command*" push jump parse block.end.39652: # error, superfluous argument add ": command does not take an argument \n" add "near line " ll add " of script. \n" print clear #state quit block.end.39843: #---------------------------------- # format: "while [:alpha:] ;" or whilenot [a-z] ; testis "word*class*;*" jumpfalse block.end.40650 clear get testis "while" jumpfalse block.end.40237 clear add "# while \n" # fix for perl add "while " ++ get -- add ".match?($self->{'peep)\n" add " if $self->{'eof { break }\n mm->read()\n}" put clear add "command*" push jump parse block.end.40237: testis "whilenot" jumpfalse block.end.40497 clear add "# whilenot \n" add "while !" ++ get -- add ".match?($self->{'peep)\n" add " if $self->{'eof { break }\n" add " mm.read()\n}" put clear add "command*" push jump parse block.end.40497: # error add " < command cannot have a class argument \n" add "line " ll add ": error in script \n" print clear quit block.end.40650: # arrange the parse> label loops # also, change .restart code before the parse label testeof jumpfalse block.end.41725 testis "commandset*parse>*commandset*" jumptrue 8 testis "command*parse>*commandset*" jumptrue 6 testis "commandset*parse>*command*" jumptrue 4 testis "command*parse>*command*" jumptrue 2 jump block.end.41721 clear # indent both code blocks add " " get replace "\n" "\n " # change .restart code before parse> label replace "next # restart" "break; # restart" put clear ++ ++ add " " get replace "\n" "\n " put clear -- -- # add a block so that .reparse works before the parse> label. add "\n# lex block \n" add "while 1 {\n" get add "\n break;\n}\n" ++ ++ add "if (restart) { restart = 0; next; }\n" # indent code block # add " "; get; replace "\n" "\n "; put; clear; # ruby doesnt support labelled loops (but swift does, and go?) # add "parse: \n"; add "\n# parse block \n" add "while 1 {\n" get add "\n break \n} # parse\n" -- -- put clear add "commandset*" push jump parse block.end.41721: block.end.41725: # ------------------------------- # 4 tokens # ------------------------------- pop #------------------------------------- # bnf: command := replace , quote , quote , ";" ; # example: replace "and" "AND" ; testis "word*quote*quote*;*" jumpfalse block.end.42793 clear get testis "replace" jumpfalse block.end.42624 #--------------------------- # a command plus 2 arguments, eg replace "this" "that" clear # remove quotes around replace args # everything is done with regex in perl, no replace string ++ get clip clop escape "/" replace "\\\"" "\"" put clear ++ get clip clop escape "/" replace "\\\"" "\"" put clear -- -- # the \\Q stops special chars being interpreted. add "$self->{'work'} =~ s/\\Q" ++ get add "/\\Q" ++ get add "/; # replace" -- -- put clear add "command*" push jump parse block.end.42624: add "Pep script error on line " ll add " (character " cc add "): \n" add " command does not take 2 quoted arguments. \n" print quit block.end.42793: #------------------------------------- # format: begin { #* commands *# } # "begin" blocks which are only executed once (they # will are assembled before the "start:" label. They must come before # all other commands. # "begin*{*command*}*", testis "begin*{*commandset*}*" jumpfalse block.end.43177 clear ++ ++ get -- -- put clear add "beginblock*" push jump parse block.end.43177: #------------------------------------- # we should not have to check for the {*command*}* pattern # because that has already been transformed to {*commandset*}* testis "test*{*commandset*}*" jumptrue 6 testis "andlist*{*commandset*}*" jumptrue 4 testis "orlist*{*commandset*}*" jumptrue 2 jump block.end.43755 clear # indent the code for readability ++ ++ add " " get replace "\n" "\n " put -- -- clear add "if (" get add ") {\n" ++ ++ get # block end required add "\n}" -- -- put clear add "command*" push # always reparse/compile jump parse block.end.43755: # ------------- # multi-token end-of-stream errors # not a comprehensive list of errors... testeof jumpfalse block.end.44518 testends "test*" jumptrue 6 testends "orlist*" jumptrue 4 testends "andlist*" jumptrue 2 jump block.end.44033 add " Error near end of script at line " ll add ". Test with no brace block? \n" print clear quit block.end.44033: testends "quote*" jumptrue 6 testends "class*" jumptrue 4 testends "word*" jumptrue 2 jump block.end.44258 put clear add "Error at end of pep script near line " ll add ": missing semi-colon? \n" add "Parse stack: " get add "\n" print clear quit block.end.44258: testends "{*" jumptrue 16 testends "}*" jumptrue 14 testends ";*" jumptrue 12 testends ",*" jumptrue 10 testends ".*" jumptrue 8 testends "!*" jumptrue 6 testends "B*" jumptrue 4 testends "E*" jumptrue 2 jump block.end.44514 put clear add "Error: misplaced terminal character at end of script! (line " ll add "). \n" add "Parse stack: " get add "\n" print clear quit block.end.44514: block.end.44518: # put the 4 (or less) tokens back on the stack push push push push testeof jumpfalse block.end.57108 print clear add "\n" add "use strict;\n" add "use autodie;\n" add "#use warnings;\n" add "\n" add " # create the virtual machine object code and save it\n" add " # somewhere on the tape.\n" add "\n" add " # code generated by \"translate.perl.pss\" a pep script\n" add " # see http://bumble.sf.net/books/pars/tr/\n" add "\n" add "package Machine;\n" add " my $true = 1;\n" add " my $false = 0;\n" add "\n" add " sub new {\n" add " my $class = shift; # \n" add "\n" add " my $self = {\n" add " # output => # A reference to the output and input streams\n" add " # input => # if and are not used.\n" add " input => *STDIN,\n" add " output => *STDOUT,\n" add " size => 300, # how many initial elements in tape/marks array\n" add " eof => 0, # end of stream reached?\n" add " charsRead => 0, # how many chars already read\n" add " linesRead => 1, # how many lines already read\n" add " escape => \"\\\\\",\n" add " delimiter => \"*\", # push/pop delimiter (default \"*\")\n" add " counter => 0, # a counter for anything\n" add " work => \"\", # the workspace\n" add " stack => (), # stack for parse tokens \n" add " cell => 0, # current tape cell\n" add " tape => (), # a list of attribute for tokens \n" add " marks => (), # marked tape cells \n" add " peep => \"\" \n" add " };\n" add "\n" add "\n" add " #$self->{\"tape\"} = 100;\n" add " #$self->{\"marks\"} = 100;\n" add " #@{$self->{\"tape\"}} = (\"\") x 100;\n" add " #@{$self->{\"marks\"}} = (\"\") x 100;\n" add " #$self->{\"size\"} = 100;\n" add " # or use this, which may not duplicate the references\n" add " # $my @arr = map { [] } 1..100;\n" add "\n" add " bless $self, $class;\n" add " return $self;\n" add " }\n" add "\n" add " # if (@tape < 5) check if length of tape is 4 or less\n" add " sub setInput {\n" add " my $self = shift; # pointer to the machine\n" add " my $newInput = shift; \n" add " print \"to be implemented\";\n" add " }\n" add "\n" add " # read one character from the input stream and \n" add " # update the machine.\n" add " sub readChar {\n" add " #$self->{\"peep\"} = getc(STDIN);\n" add "\n" add " my $self = shift; # the pep/nom virtual-machine object\n" add " if ($self->{\"eof\"}) { exit; }\n" add " $self->{\"charsRead\"} += 1;\n" add " # increment auto line counter\n" add " if ($self->{\"peep\"} eq \"\\n\") { $self->{\"linesRead\"} += 1; }\n" add " $self->{\"work\"} .= $self->{\"peep\"};\n" add " $self->{\"peep\"} = getc($self->{\"input\"});\n" add " #check\n" add " if (eof($self->{\"input\"})) { $self->{\"eof\"} = 1; }\n" add " }\n" add "\n" add " # test if all chars in workspace are in unicode category\n" add " sub isInCategory {\n" add " my $self = shift; # pointer to the machine\n" add " my $cat = shift; \n" add " #for ch in $self->{\"work\"}\n" add " # if not category(ch).start_with?(cat) then return false end\n" add " #return True\n" add " }\n" add "\n" add " # this walks the string and determines if the given char \n" add " # is already escaped or not # eg \"ab\cab\\cab\c\"\n" add " sub unescapeChar {\n" add " my $self = shift; # the machine\n" add " my $c = shift;\n" add " my $cc = \"\";\n" add " my $result = \"\";\n" add " my $isEscaped = $false;\n" add "\n" add " foreach $cc (split(//,$self->{\"work\"})) {\n" add " if (($isEscaped == $false) && ($cc eq $self->{\"escape\"})) {\n" add " $isEscaped = $true;\n" add " } else { $isEscaped = $false; }\n" add " # remove the last escape character (usually backslash)\n" add " if (($isEscaped == $true) && ($cc eq $c)) {\n" add " $result =~ s/.$//s; \n" add " }\n" add " $result .= $cc; \n" add " }\n" add " $self->{\"work\"} = $result;\n" add " }\n" add "\n" add " # this needs to walk the string and determine if the given char \n" add " # is already escaped or not # eg \"ab\cab\\cab\c\"\n" add " sub escapeChar {\n" add " my $self = shift; # the machine\n" add " my $c = shift;\n" add " my $cc = \"\";\n" add " my $result = \"\";\n" add " my $isEscaped = $false;\n" add "\n" add " foreach $cc (split(//,$self->{\"work\"})) {\n" add " if (($isEscaped == $false) && ($cc eq $self->{\"escape\"})) {\n" add " $isEscaped = $true;\n" add " } else { $isEscaped = $false; }\n" add " if (($isEscaped == $true) && ($cc eq $c)) {\n" add " $result .= $self->{\"escape\"};\n" add " }\n" add " $result .= $cc; \n" add " }\n" add " $self->{\"work\"} = $result;\n" add " }\n" add "\n" add " # a helper for the multiescape until bug\n" add " sub countEscaped {\n" add " my $self = shift; # the machine\n" add " my $suffix = shift; \n" add " my $count = 0;\n" add " # no check\n" add " my $s = $self->{\"work\"};\n" add " $s =~ s/$suffix$//;\n" add " while ($s =~ /$self->{\"escape\"}$/) {\n" add " $count += 1;\n" add " $s =~ s/$self->{\"escape\"}$//;\n" add " }\n" add " return $count;\n" add " }\n" add "\n" add " # reads the input stream until the workspace end with text \n" add " sub until {\n" add " my $self = shift; # the machine\n" add " my $suffix = shift; \n" add " # read at least one character\n" add " if ($self->{\"eof\"}) { return; }\n" add " # pass a reference to the machine hash with \% not %\n" add " $self->readChar();\n" add " while (1) { \n" add " if ($self->{\"eof\"}) { return; }\n" add " # need to count the @escape chars preceding suffix\n" add " # if odd, keep reading, if even, stop\n" add " if ($self->{\"work\"} =~ /\Q$suffix$/) { \n" add " if ($self->countEscaped($suffix) % 2 == 0) { return; }\n" add " }\n" add " $self->readChar()\n" add " }\n" add " } \n" add "\n" add " # this implements the ++ command incrementing the tape pointer\n" add " # and growing the tape and marks arrays if required\n" add " sub increment {\n" add " my $self = shift; # the machine\n" add " $self->{\"cell\"} += 1;\n" add " if ($self->{\"cell\"} >= $self->{\"tape\"}) { \n" add " # lengthen the tape and marks arrays by assigning to\n" add " # length var\n" add " $self->{\"tape\"} = $self->{\"tape\"} + 40;\n" add " $self->{\"marks\"} = $self->{\"marks\"} + 40;\n" add " $self->{\"size\"} = $self->{\"tape\"};\n" add " }\n" add " }\n" add "\n" add " # pop the first token from the stack into the workspace */\n" add " sub popToken {\n" add " my $self = shift; # the machine, not local\n" add " if (!$self->{\"stack\"}) { return 0; }\n" add " $self->{\"work\"} = pop(@{$self->{\"stack\"}}) + $self->{\"work\"};\n" add " if ($self->{\"cell\"} > 0) { $self->{\"cell\"} -= 1; }\n" add " return 1;\n" add " }\n" add "\n" add " # push the first token from the workspace to the stack \n" add " sub pushToken {\n" add " my $self = shift; # a pointer to the machine\n" add " # dont increment the tape pointer on an empty push\n" add " if ($self->{\"work\"} eq \"\") { return 0; }\n" add " # need to get this from the delimiter.\n" add " my $iFirst = index($self->{\"work\"}, $self->{\"delimiter\"});\n" add " if ($iFirst == -1 ) {\n" add " push(@{$self->{\"stack\"}}, $self->{\"work\"}); \n" add " $self->{\"work\"} = \"\"; return 1;\n" add " }\n" add " # s[i..j] means all chars from i to j\n" add " # s[i,n] means n chars from i\n" add " push(@{$self->{\"stack\"}}, substr($self->{\"work\"}, 0, $iFirst));\n" add " $self->{\"work\"} = substr($self->{\"work\"}, $iFirst+1, -1);\n" add " $self->increment();\n" add " return 1;\n" add " }\n" add "\n" add " sub printState {\n" add " # print \"Stack[${@self->{\\"stack\\"}.join(, )}] Work[#{$self->{\\"work\\"}}] Peep[#{$self->{\\"peep\\"}}]\"\n" add " # print \"Acc:#{$self->{\"counter\"}} Esc:#{$self->{\"escape\"}} Delim:#{$self->{\"delimiter\"}} Chars:#{$self->{\"charsRead\"}}\" +\n" add " # \" Lines:#{$self->{\"linesRead\"}} Cell:#{$self->{\"cell\"}}\"\n" add " }\n" add "\n" add " sub goToMark {\n" add " my $self = shift; # a pointer to the machine\n" add " my $mark = shift; \n" add " # search the marks \n" add " for my $ii (0..$self->{\"marks\"}) {\n" add " if (@{$self->{\"marks\"}}[$ii] eq $mark) {\n" add " $self->{\"cell\"} = $ii; \n" add " return;\n" add " }\n" add " }\n" add " # mark was not found- fatal error!\n" add " print(\"bad mark \'$mark!\'\");\n" add " exit();\n" add " }\n" add "\n" add " # parses from a string and returns the result as a string.\n" add " # useful for programatic use. Chopping last char??\n" add " # does nothing if no input given.\n" add " sub parseString {\n" add " my $out = \"\";\n" add " my $self = shift; # the parse machine \n" add " my $in = shift; # input string\n" add " if (!defined $in) { return \"\"; }\n" add " open my $input, \"<\", \\$in or return \"[no input!]\";\n" add " open my $output, \">\", \\$out or return \"[no output!]\";\n" add " $self->parse($input, $output);\n" add " return $out;\n" add " }\n" add "\n" add " # parses from a file and writes to a string\n" add " # useful for creating a pep/nom interpreter.\n" add " sub parseFile {\n" add " my $out = \"\";\n" add " my $self = shift; # the parse machine \n" add " my $infile = shift; # input filename\n" add " if (!defined $infile) { return \"\"; }\n" add " open my $input, \"<\", $infile or return \"[no input file!]\";\n" add " open my $output, \">\", \\$out or return \"[no output!]\";\n" add " $self->parse($input, $output);\n" add " return $out;\n" add " }\n" add "\n" add " # This function performs all sorts of magic and shenanigans. \n" add " # Creates a new method runScript() and evaluates it, \n" add " # thus acting as an interpreter of a scriptfile given to an\n" add " # -f switch. This method only works when the perl translator has \n" add " # been run on itself with:\n" add " # >> pep -f translate.perl.pss translate.perl.pss > interp.pl.pl\n" add " # >> echo \"read; print; print; clear; \" > test.pss\n" add " # >> chmod a+x interp.pl.pl\n" add " # >> echo buzz | ./interp.pl.pl -f test.pss\n" add " # >> (output should be \"bbuuzz\")\n" add " # Only those who have achieved\n" add " # true enlightenment will understand this method.\n" add "\n" add " sub interpret {\n" add " my $self = shift; # the parse machine \n" add " my $filename = \"\";\n" add " if ($ARGV[0] eq \"-f\") { $filename = $ARGV[1]; }\n" add " if ($ARGV[0] =~ /^-f/) { \n" add " $filename = $ARGV[1]; $filename =~ s/^..//;\n" add " }\n" add " if ((!defined $filename) || ($filename eq \"\")) { return; }\n" add " my $method = $self->parseFile($filename);\n" add " # remove everything except the parse method and rename\n" add " $method =~ s/^.*sub parse \\{/sub runScript \\{/s;\n" add " # to debug\n" add " # print $method; return;\n" add " # add this new method to the current class via evaluation\n" add " eval($method);\n" add " # execute the new method, thus interpreting the script-file \n" add " # that was provide\n" add " $self->runScript(*STDIN, *STDOUT);\n" add " }\n" add "\n" add "" # save preamble (class) code in the current tape cell put clear #--------------------- # check if the script correctly parsed (there should only # be one token on the stack, namely "commandset*" or "command*"). pop pop testis "commandset*" jumptrue 8 testis "command*" jumptrue 6 testis "beginblock*commandset*" jumptrue 4 testis "beginblock*command*" jumptrue 2 jump block.end.56417 testis "commandset*" jumptrue 4 testis "command*" jumptrue 2 jump block.end.54387 clear # indenting generated code for readability. add "\n" get replace "\n" "\n " put clear add "\nwhile (!$self->{\"eof\"}) {\n" get add "\n}\n" replace "\n" "\n " put clear # restore the perl preamble (machine definition) from the tape ++ get -- #add 'script: \n'; block.end.54387: testis "beginblock*commandset*" jumptrue 4 testis "beginblock*command*" jumptrue 2 jump block.end.54814 clear # indent begin-block and main code for readability. add "\n" ++ get replace "\n" "\n " put -- clear add "\n" get ++ add "\nwhile (!$self->{\"eof\"}) {\n" get add "\n}\n" replace "\n" "\n " -- put clear # get perl preamble from tape ++ ++ get -- -- add "\n" block.end.54814: add "\n" add "\n" add " # This could have an input/output stream argument, or make the \n" add " # streams an attribute of the object.\n" add " sub parse {\n" add " my $self = shift; # a machine reference\n" add " my $in = shift; # the input stream\n" add " my $out = shift; # output stream\n" add " if (!defined $in) { $in = *STDIN; }\n" add " if (!defined $out) { $out = *STDOUT; }\n" add " #$self->{\"output\"} = *STDOUT;\n" add " $self->{\"output\"} = $out;\n" add " $self->{\"input\"} = $in;\n" add " $self->{\"peep\"} = getc($self->{\"input\"});\n" add " # the restart flag allows .restart to work before the \n" add " # parse label, in languages that dont have labelled loops\n" add " my $restart = $false; \n" # the while loop has already been added around the # script commands. get add "\n" add " }\n" add " # end of Machine methods definition\n" add "\n" add " my $mm = Machine->new();\n" add "\n" add " # reads from and writes to \n" add " $mm->parse();\n" add "\n" add " # --------\n" add " # Other ways to call parse(), parseString() and parseFile()\n" add " # Use an input file and print to stdout.\n" add " # open IN, \"parse(*IN);\n" add "\n" add " # write results to an output file, reading from stdin\n" add " # open OUT, \">test.txt\" or die $!;\n" add " # $mm->parse(*STDIN, *OUT);\n" add "\n" add " # parse/translate a string and return the result as string\n" add " # my $result = $mm->parseString(\"new.new\");\n" add " # print $result;\n" add "\n" add " # parse/translate a file and return the result as a string\n" add " # my $result = $mm->parseFile(\"test.txt\");\n" add " # print $result;\n" add "\n" add " # end of generated perl code\n" # put a copy of the final compilation into the tapecell # so it can be inspected interactively. put print clear quit block.end.56417: push push # try to explain some more errors unstack testbegins "parse>" jumpfalse block.end.56683 put clear add "[error] pep syntax error:\n" add " The parse> label cannot be the 1st item \n" add " of a script \n" print quit block.end.56683: put clear clear add "After compiling with 'translate.perl.pss' (at EOF): \n " add " parse error in input script. \n " print clear unstack put clear add "Parse stack: " get add "\n" add " * debug script " add " >> pep -If script -i 'some input' \n " add " * debug compilation. \n " add " >> pep -Ia asm.pp script' \n " print clear quit block.end.57108: # not eof # there is an implicit .restart command here (jump start) jump start