#!/usr/bin/ruby # code generated by "translate.ruby.pss" a pep script # http://bumble.sf.net/books/pars/tr/ # require 'something' class Machine # make a new machine attr_accessor :work, :charsRead, :linesRead, :escape, :delimiter, :counter, :stack, :tape, :cell, :marks, :eof, :peep def initialize() @size = 300 # how many elements in stack/tape/marks @eof = false # end of stream reached? @charsRead = 0 # how many chars already read @linesRead = 1 # how many lines already read @escape = "\\" @delimiter = "*" # push/pop delimiter (default "*") @counter = 0 # a counter for anything @work = "" # the workspace @stack = [] # stack for parse tokens @cell = 0 # current tape cell @tape = Array.new(@size) {String.new} # a list of attribute for tokens @marks = Array.new(@size) {String.new} # marked tape cells # or dont initialse peep until "parse()" calls "setInput()" @peep = ARGF.readchar end def setInput(newInput) puts "to be implemented" end # read one character from the input stream and # update the machine. def read if @eof then exit end @charsRead += 1; # increment lines if @peep == "\n" then @linesRead += 1 end @work += @peep @peep = ARGF.readchar if @peep.nil? then @eof = true; end end # test if all chars in workspace are in unicode category def isInCategory(cat) #for ch in @work # if not category(ch).start_with?(cat) then return false end #return True end # this needs to actually walk the string # eg "abcab\cabc" # not trivial def unescapeChar(c) @work.gsub!("#{@escape}#{c}", c) end # add escape character : trivial? def escapeChar(c) @work.gsub!(c, @escape+c) end # a helper for the multiescape until bug def countEscaped(suffix) count = 0 #s = @work.sub(/#{suffix}$/, "") s = @work.delete_suffix(suffix) while s.end_with?(@escape) count += 1 s.delete_suffix!(@escape) end # puts "count=#{count}" return count end # reads the input stream until the workspace end with text def until(suffix) # read at least one character if @eof then return end self.read() while true do if @eof then return end # need to count the @escape chars preceding suffix # if odd, keep reading, if even, stop if @work.end_with?(suffix) then if (self.countEscaped(suffix).even?) then return end end self.read() end end # this implements the ++ command incrementing the tape pointer # and growing the tape and marks arrays if required def increment() @cell += 1 if @cell >= @size then @tape.append("") @marks.append("") @size += 1 end end # pop the first token from the stack into the workspace */ def pop() if @stack.length == 0 then return false end @work = @stack.pop() + @work if @cell > 0 then @cell -= 1 end return true end # push the first token from the workspace to the stack def push() # dont increment the tape pointer on an empty push if @work == "" then return false end # need to get this from the delimiter. iFirst = @work.index(@delimiter) if iFirst.nil? @stack.push(@work); @work = ""; return true # also @stack << @work end # s[i..j] means all chars from i to j # s[i,n] means n chars from i @stack.push(@work[0..iFirst]) @work = @work[iFirst+1..-1] self.increment() return true end def printState() puts "Stack[#{@stack.join(', ')}] Work[#{@work}] Peep[#{@peep}]" puts "Acc:#{@counter} Esc:#{@escape} Delim:#{@delimiter} Chars:#{@charsRead}" + " Lines:#{@linesRead} Cell:#{@cell}" end def goToMark(mark) ii = @marks.find_index(mark) if !ii.nil? then @cell = ii else print("bad mark '" + mark + "'!") exit end end # this is where the actual parsing/compiling code should go # so that it can be used by other ruby classes/objects. Also # should have a stream argument. def parse(s) # a reset or "setinput()" method would be useful to parse a # different string/file/stream, without creating a new # machine object. # could use code like this to check if input is string or file #if isinstance(s, file) print("") # @reset(s) # @reader = s #elseif isinstance(s, string) #f = StringIO.StringIO("test") #for line in f print(line) #else # f = STDIN #end #puts "not implemented" end end # end of Machine class definition # will become: # mm.parse(sys.stdin) or # mm.parse("abcdef") or # open f; mm.parse(f) # the restart flag, which allows .restart to work before the # parse label, in languages (like ruby) that dont have # labelled loops restart = false mm = Machine.new while !mm.eof do # lex block while true mm.read() # read #-------------- if (mm.work.match?(/^[[:space:]]+$/)) then mm.work = '' # clear break end #--------------- # 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. if (mm.work == "{" || mm.work == "}" || mm.work == ";" || mm.work == "," || mm.work == "." || mm.work == "!" || mm.work == "B" || mm.work == "E") then mm.tape[mm.cell] = mm.work # put mm.work += "*" mm.push(); break end #--------------- # format: "text" if (mm.work == "\"") then # save the start line number (for error messages) in case # there is no terminating quote character. mm.work = '' # clear mm.work += "line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ") " mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "\"" mm.until("\""); if (!mm.work.end_with?("\"")) then mm.work = '' # clear mm.work += "Unterminated quote character (\") starting at " mm.work += mm.tape[mm.cell] # get mm.work += " !\n" print mm.work # print exit end # check for empty quotes as arguments for escape etc # replace if mm.work.length > 0 then mm.work.gsub!("\#{", "\\\#{") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "quote*" mm.push(); break end #--------------- # format: 'text', single quotes are converted to double quotes # but we must escape embedded double quotes. if (mm.work == "'") then # save the start line number (for error messages) in case # there is no terminating quote character. mm.work = '' # clear mm.work += "line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ") " mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.until("'"); if (!mm.work.end_with?("'")) then mm.work = '' # clear mm.work += "Unterminated quote (') starting at " mm.work += mm.tape[mm.cell] # get mm.work += "!\n" print mm.work # print exit end if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end mm.work.gsub!("\"", "#{mm.escape}\"") # escape # #{ does string interpolation in ruby which is not # what we want. Also unescape \' # replace if mm.work.length > 0 then mm.work.gsub!("\#{", "\\\#{") end mm.work.gsub!(mm.escape+"'", "'") # escape mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "\"" mm.work += mm.tape[mm.cell] # get mm.work += "\"" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "quote*" mm.push(); break end #--------------- # formats: [:space:] [a-z] [abcd] [:alpha:] etc # should class tests really be multiline??! if (mm.work == "[") then # save the start line number (for error messages) in case # there is no terminating bracket character. mm.work = '' # clear mm.work += "line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ") " mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "[" mm.until("]"); if (mm.work == "[]") then mm.work = '' # clear mm.work += "pep script error at line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " empty character class [] \n" print mm.work # print exit end if (!mm.work.end_with?("]")) then mm.work = '' # clear mm.work += "Unterminated class text ([...]) starting at " mm.work += mm.tape[mm.cell] # get mm.work += "" mm.work += "\n class text can be used in tests or with the 'while' and " mm.work += "\n 'whilenot' commands. For example: " mm.work += "\n [:alpha:] { while [:alpha:]; print; clear; }" mm.work += "\n " print mm.work # print exit end # need to escape '/'? ruby uses /.../ to match mm.work.gsub!("/", "#{mm.escape}/") # escape # need to escape quotes? ruby uses /.../ to match mm.work.gsub!("\"", "#{mm.escape}\"") # escape # the caret is not a negation operator in pep scripts # replace if mm.work.length > 0 then mm.work.gsub!("^", "\\^") end # save the class on the tape mm.tape[mm.cell] = mm.work # put if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end if (!mm.work.start_with?("-")) then # not a range class, eg [a-z] so need to escape '-' chars mm.work = '' # clear mm.work += mm.tape[mm.cell] # get # replace if mm.work.length > 0 then mm.work.gsub!("-", "\\-") end mm.tape[mm.cell] = mm.work # put end if (mm.work.start_with?("-")) then # a range class, eg [a-z], check if it is correct if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if (mm.work != "-") then mm.work = '' # clear mm.work += "Error in pep script at line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " Incorrect character range class " mm.work += mm.tape[mm.cell] # get mm.work += "" mm.work += "\n For example:" mm.work += "\n [a-g] # correct" mm.work += "\n [f-gh] # error! \n" print mm.work # print mm.work = '' # clear exit end end mm.work = '' # clear mm.work += mm.tape[mm.cell] # get # restore class text if (mm.work.start_with?("[:") && !mm.work.end_with?(":]")) then mm.work = '' # clear mm.work += "malformed character class starting at " mm.work += mm.tape[mm.cell] # get mm.work += "!\n" print mm.work # print exit end # class in the form [:digit:] if (mm.work.start_with?("[:") && mm.work != "[:]") then if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end # unicode posix character classes # Also, abbreviations (not implemented in gh.c yet.) # this should not be tricky in ruby because posix is supported if (mm.work == "alnum" || mm.work == "N") then mm.work = '' # clear mm.work += "[[:alnum:]]" end if (mm.work == "alpha" || mm.work == "A") then mm.work = '' # clear mm.work += "[[:alpha:]]" end # ? can use s.ascii_only?() if (mm.work == "ascii" || mm.work == "I") then mm.work = '' # clear mm.work += "[[:ascii:]]" end # non-standard ruby posix class 'word' if (mm.work == "word" || mm.work == "W") then mm.work = '' # clear mm.work += "[[:word:]]" end if (mm.work == "blank" || mm.work == "B") then mm.work = '' # clear mm.work += "[[:blank:]]" end if (mm.work == "cntrl" || mm.work == "C") then mm.work = '' # clear mm.work += "[[:cntrl:]]" end if (mm.work == "digit" || mm.work == "D") then mm.work = '' # clear mm.work += "[[:digit:]]" end if (mm.work == "graph" || mm.work == "G") then mm.work = '' # clear mm.work += "[[:graph:]]" end if (mm.work == "lower" || mm.work == "L") then mm.work = '' # clear mm.work += "[[:lower:]]" end if (mm.work == "print" || mm.work == "P") then mm.work = '' # clear mm.work += "[[:print:]]" end if (mm.work == "punct" || mm.work == "T") then mm.work = '' # clear mm.work += "[[:punct:]]" end if (mm.work == "space" || mm.work == "S") then mm.work = '' # clear mm.work += "[[:space:]]" end if (mm.work == "upper" || mm.work == "U") then mm.work = '' # clear mm.work += "[[:upper:]]" end if (mm.work == "xdigit" || mm.work == "X") then mm.work = '' # clear mm.work += "[[:xdigit:]]" end if (!mm.work.start_with?("[[")) then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "pep script error at line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += "Unknown character class '" mm.work += mm.tape[mm.cell] # get mm.work += "'\n" print mm.work # print mm.work = '' # clear exit end end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "/^" mm.work += mm.tape[mm.cell] # get mm.work += "+$/" mm.tape[mm.cell] = mm.work # put mm.work = '' # 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) mm.work += "class*" mm.push(); break end #--------------- # formats: (eof) (EOF) (==) etc. if (mm.work == "(") then mm.work = '' # clear mm.until(")"); if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end mm.tape[mm.cell] = mm.work # put if (mm.work == "eof" || mm.work == "EOF") then mm.work = '' # clear mm.work += "eof*" mm.push(); break end if (mm.work == "==") then mm.work = '' # clear mm.work += "tapetest*" mm.push(); break end mm.work += " << unknown test near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script.\n" mm.work += " bracket () tests are \n" mm.work += " (eof) test if end of stream reached. \n" mm.work += " (==) test if workspace is same as current tape cell \n" print mm.work # print mm.work = '' # clear exit end #--------------- # multiline and single line comments, eg #... and #* ... *# if (mm.work == "#") then mm.work = '' # clear mm.read() # read if (mm.work == "\n") then mm.work = '' # clear break end # checking for multiline comments of the form "#* \n\n\n *#" # these are just ignored at the moment (deleted) if (mm.work == "*") then # save the line number for possible error message later mm.work = '' # clear mm.work += mm.linesRead.to_s # lines mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.until("*#"); if (mm.work.end_with?("*#")) then # convert to python comments (#), python doesnt have multiline # comments, as far as I know if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\n#") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear # create a "comment" parse token # comment-out this line to remove multiline comments from the # compiled python # add "comment*"; push; break end # make an unterminated multiline comment an error # to ease debugging of scripts. mm.work = '' # clear mm.work += "unterminated multiline comment #* ... *# \n" mm.work += "stating at line number " mm.work += mm.tape[mm.cell] # get mm.work += "\n" print mm.work # print mm.work = '' # clear exit end # single line comments. some will get lost. mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "#" mm.work += mm.tape[mm.cell] # get mm.until("\n"); if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear # comment out this below to remove single line comments # from the output mm.work += "comment*" mm.push(); break end #---------------------------------- # parse command words (and abbreviations) # legal characters for keywords (commands) if (!mm.work.match?(/^[abcdefghijklmnopqrstuvwxyzBEKGPRUWS+\-<>0\^]+$/)) then # error message about a misplaced character mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "!! Misplaced character '" mm.work += mm.tape[mm.cell] # get mm.work += "' in script near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ") \n" print mm.work # print mm.work = '' # clear exit end # my testclass implementation cannot handle complex lists # eg [a-z+-] this is why I have to write out the whole alphabet # while while /^[abcdefghijklmnopqrstuvwxyzBEOFKGPRUWS+\-<>0\^]+$/.match?(mm.peep) if mm.eof then break end mm.read() end #---------------------------------- # 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 if (mm.work == "ll") then mm.work = '' # clear mm.work += "lines" end if (mm.work == "cc") then mm.work = '' # clear mm.work += "chars" end # one letter command abbreviations if (mm.work == "a") then mm.work = '' # clear mm.work += "add" end if (mm.work == "k") then mm.work = '' # clear mm.work += "clip" end if (mm.work == "K") then mm.work = '' # clear mm.work += "clop" end if (mm.work == "D") then mm.work = '' # clear mm.work += "replace" end if (mm.work == "d") then mm.work = '' # clear mm.work += "clear" end if (mm.work == "t") then mm.work = '' # clear mm.work += "print" end if (mm.work == "p") then mm.work = '' # clear mm.work += "pop" end if (mm.work == "P") then mm.work = '' # clear mm.work += "push" end if (mm.work == "u") then mm.work = '' # clear mm.work += "unstack" end if (mm.work == "U") then mm.work = '' # clear mm.work += "stack" end if (mm.work == "G") then mm.work = '' # clear mm.work += "put" end if (mm.work == "g") then mm.work = '' # clear mm.work += "get" end if (mm.work == "x") then mm.work = '' # clear mm.work += "swap" end if (mm.work == ">") then mm.work = '' # clear mm.work += "++" end if (mm.work == "<") then mm.work = '' # clear mm.work += "--" end if (mm.work == "m") then mm.work = '' # clear mm.work += "mark" end if (mm.work == "M") then mm.work = '' # clear mm.work += "go" end if (mm.work == "r") then mm.work = '' # clear mm.work += "read" end if (mm.work == "R") then mm.work = '' # clear mm.work += "until" end if (mm.work == "w") then mm.work = '' # clear mm.work += "while" end if (mm.work == "W") then mm.work = '' # clear mm.work += "whilenot" end if (mm.work == "n") then mm.work = '' # clear mm.work += "count" end if (mm.work == "+") then mm.work = '' # clear mm.work += "a+" end if (mm.work == "-") then mm.work = '' # clear mm.work += "a-" end if (mm.work == "0") then mm.work = '' # clear mm.work += "zero" end if (mm.work == "c") then mm.work = '' # clear mm.work += "chars" end if (mm.work == "l") then mm.work = '' # clear mm.work += "lines" end if (mm.work == "^") then mm.work = '' # clear mm.work += "escape" end if (mm.work == "v") then mm.work = '' # clear mm.work += "unescape" end if (mm.work == "z") then mm.work = '' # clear mm.work += "delim" end if (mm.work == "S") then mm.work = '' # clear mm.work += "state" end if (mm.work == "q") then mm.work = '' # clear mm.work += "quit" end if (mm.work == "s") then mm.work = '' # clear mm.work += "write" end if (mm.work == "o") then mm.work = '' # clear mm.work += "nop" end if (mm.work == "rs") then mm.work = '' # clear mm.work += "restart" end if (mm.work == "rp") then mm.work = '' # clear mm.work += "reparse" end # some extra syntax for testeof and testtape if (mm.work == "" || mm.work == "") then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "eof*" mm.push(); break end if (mm.work == "<==>") then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "tapetest*" mm.push(); break end if (mm.work == "jump" || mm.work == "jumptrue" || mm.work == "jumpfalse" || mm.work == "testis" || mm.work == "testclass" || mm.work == "testbegins" || mm.work == "testends" || mm.work == "testeof" || mm.work == "testtape") then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "The instruction '" mm.work += mm.tape[mm.cell] # get mm.work += "' near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ")\n" mm.work += "can be used in pep assembly code but not scripts. \n" print mm.work # print mm.work = '' # clear exit end # show information if these "deprecated" commands are used if (mm.work == "Q" || mm.work == "bail") then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "The instruction '" mm.work += mm.tape[mm.cell] # get mm.work += "' near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ")\n" mm.work += "is no longer part of the pep language (july 2020). \n" mm.work += "use 'quit' instead of 'bail', and use 'unstack; print;' \n" mm.work += "instead of 'state'. \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "add" || mm.work == "clip" || mm.work == "clop" || mm.work == "replace" || mm.work == "upper" || mm.work == "lower" || mm.work == "cap" || mm.work == "clear" || mm.work == "print" || mm.work == "state" || mm.work == "pop" || mm.work == "push" || mm.work == "unstack" || mm.work == "stack" || mm.work == "put" || mm.work == "get" || mm.work == "swap" || mm.work == "++" || mm.work == "--" || mm.work == "mark" || mm.work == "go" || mm.work == "read" || mm.work == "until" || mm.work == "while" || mm.work == "whilenot" || mm.work == "count" || mm.work == "a+" || mm.work == "a-" || mm.work == "zero" || mm.work == "chars" || mm.work == "lines" || mm.work == "nochars" || mm.work == "nolines" || mm.work == "escape" || mm.work == "unescape" || mm.work == "delim" || mm.work == "quit" || mm.work == "write" || mm.work == "nop" || mm.work == "reparse" || mm.work == "restart") then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "word*" mm.push(); break end #------------ # 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> if (mm.work == "parse>") then mm.work = '' # clear mm.work += mm.counter.to_s # count if (mm.work != "0") then mm.work = '' # clear mm.work += "script error:\n" mm.work += " extra parse> label at line " mm.work += mm.linesRead.to_s # lines mm.work += ".\n" print mm.work # print exit end mm.work = '' # clear mm.work += "# parse> parse label" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "parse>*" mm.push(); # use accumulator to indicate after parse> label mm.counter += 1 # a+ break end # -------------------- # implement "begin-blocks", which are only executed # once, at the beginning of the script (similar to awk's BEGIN {} rules) if (mm.work == "begin") then mm.tape[mm.cell] = mm.work # put mm.work += "*" mm.push(); break end mm.work += " << unknown command on line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of source file. \n" print mm.work # print mm.work = '' # clear exit # ---------------------------------- # 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. break end if restart then restart = false; next; end # parse block while true #------------------------------------- # 2 tokens #------------------------------------- mm.pop(); mm.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: # begintext* endtext* quoteset* notclass*, !* ,* ;* 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 if (mm.work == "word*word*" || mm.work == "word*}*" || mm.work == "word*begintext*" || mm.work == "word*endtext*" || mm.work == "word*!*" || mm.work == "word*,*" || mm.work == "quote*word*" || mm.work == "quote*class*" || mm.work == "quote*state*" || mm.work == "quote*}*" || mm.work == "quote*begintext*" || mm.work == "quote*endtext*" || mm.work == "class*word*" || mm.work == "class*quote*" || mm.work == "class*class*" || mm.work == "class*state*" || mm.work == "class*}*" || mm.work == "class*begintext*" || mm.work == "class*endtext*" || mm.work == "class*!*" || mm.work == "notclass*word*" || mm.work == "notclass*quote*" || mm.work == "notclass*class*" || mm.work == "notclass*state*" || mm.work == "notclass*}*") then mm.work += " (Token stack) \nValue: \n" mm.work += mm.tape[mm.cell] # get mm.work += "\nValue: \n" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += "\n" mm.work += "Error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of pep script (missing semicolon?) \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "{*;*" || mm.work == ";*;*" || mm.work == "}*;*") then mm.push(); mm.push(); mm.work += "Error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of pep script: misplaced semi-colon? ; \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == ",*{*") then mm.push(); mm.push(); mm.work += "Error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: extra comma in list? \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "command*;*" || mm.work == "commandset*;*") then mm.push(); mm.push(); mm.work += "Error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: extra semi-colon? \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "!*!*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: \n double negation '!!' is not implemented \n" mm.work += " and probably won't be, because what would be the point? \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "!*{*" || mm.work == "!*;*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: misplaced negation operator (!)? \n" mm.work += " The negation operator precedes tests, for example: \n" mm.work += " !B'abc'{ ... } or !(eof),!'abc'{ ... } \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == ",*command*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: misplaced comma? \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "!*command*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (at char " mm.work += mm.charsRead.to_s # chars mm.work += ") \n" mm.work += " The negation operator (!) cannot precede a command \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == ";*{*" || mm.work == "command*{*" || mm.work == "commandset*{*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: no test for brace block? \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "{*}*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script: empty braces {}. \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "B*class*" || mm.work == "E*class*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script:\n classes ([a-z], [:space:] etc). \n" mm.work += " cannot use the 'begin' or 'end' modifiers (B/E) \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "comment*{*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script: comments cannot occur between \n" mm.work += " a test and a brace ({). \n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "}*command*") then mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script: extra closing brace '}' ?. \n" print mm.work # print mm.work = '' # clear exit end #------------ # 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. if (mm.work == ".*word*") then mm.work = '' # clear mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- if (mm.work == "restart") then mm.work = '' # clear mm.work += mm.counter.to_s # 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 if (mm.work == "0") then mm.work = '' # clear # use the comment '# restart' so we can replace # this with 'break' if the parse> label appears later mm.work += "restart = true; next # restart" end if (mm.work == "1") then mm.work = '' # clear mm.work += "break" end # after the parse> label mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "reparse") then mm.work = '' # clear mm.work += mm.counter.to_s # count # check accumulator to see if we are in the "lex" block # or the "parse" block and adjust the .reparse compilation # accordingly. if (mm.work == "0") then mm.work = '' # clear mm.work += "break" end if (mm.work == "1") then mm.work = '' # clear mm.work += "next" end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end mm.push(); mm.push(); mm.work += "error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script: \n" mm.work += " misplaced dot '.' (use for AND logic or in .reparse/.restart \n" print mm.work # print mm.work = '' # clear exit end #--------------------------------- # Compiling comments so as to transfer them to the java if (mm.work == "comment*command*" || mm.work == "command*comment*" || mm.work == "commandset*comment*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get mm.work += "\n" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "comment*comment*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get mm.work += "\n" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "comment*" mm.push(); next end # ----------------------- # 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; } if (mm.work == "!*quote*" || mm.work == "!*class*" || mm.work == "!*begintext*" || mm.work == "!*endtext*" || mm.work == "!*eof*" || mm.work == "!*tapetest*") then # a simplification: store the token name "quote*/class*/..." # in the tape cell corresponding to the "!*" token. # replace if mm.work.length > 0 then mm.work.gsub!("!*", "not") end mm.push(); # this was a bug?? a missing ++; ?? # now get the token-value mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.increment() # ++ mm.work = '' # clear next end #----------------------------------------- # format: E"text" or E'text' # This format is used to indicate a "workspace-ends-with" text before # a brace block. if (mm.work == "E*quote*") then mm.work = '' # clear mm.work += "endtext*" mm.push(); mm.work += mm.tape[mm.cell] # get if (mm.work == "\"\"") then # empty argument is an error mm.work = '' # clear mm.work += "pep script error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " empty argument for end-test (E\"\") \n" print mm.work # print exit end if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.increment() # ++ mm.work = '' # clear next end #----------------------------------------- # 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. if (mm.work == "B*quote*") then mm.work = '' # clear mm.work += "begintext*" mm.push(); mm.work += mm.tape[mm.cell] # get if (mm.work == "\"\"") then # empty argument is an error mm.work = '' # clear mm.work += "pep script error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " empty argument for begin-test (B\"\") \n" print mm.work # print exit end if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.increment() # ++ mm.work = '' # clear next end #-------------------------------------------- # ebnf: command := word, ';' ; # formats: "pop; push; clear; print; " etc # all commands need to end with a semi-colon except for # .reparse and .restart if (mm.work == "word*;*") then mm.work = '' # clear # check if command requires parameter mm.work += mm.tape[mm.cell] # get if (mm.work == "add" || mm.work == "while" || mm.work == "whilenot" || mm.work == "mark" || mm.work == "escape" || mm.work == "unescape" || mm.work == "delim" || mm.work == "replace") then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "'" mm.work += mm.tape[mm.cell] # get mm.work += "'" mm.work += " << command needs an argument, on line " mm.work += mm.linesRead.to_s # lines mm.work += " of script.\n" print mm.work # print mm.work = '' # clear exit end if (mm.work == "until") then mm.work = '' # clear mm.work += "mm.until(mm.tape[mm.cell]) # until (tape) \n" mm.tape[mm.cell] = mm.work # put end if (mm.work == "go") then mm.work = '' # clear mm.work += "mm.goToMark(mm.tape[mm.cell]) # go (tape) \n" mm.tape[mm.cell] = mm.work # put end if (mm.work == "clip") then mm.work = '' # clear mm.work += "if !mm.work.empty? then # clip \n" mm.work += " mm.work = mm.work[0..-2] # clip \n" mm.work += "end" mm.tape[mm.cell] = mm.work # put end if (mm.work == "clop") then mm.work = '' # clear mm.work += "if !mm.work.empty? then # clop \n" mm.work += " mm.work = mm.work[1..-1]; # clop \n" mm.work += "end" mm.tape[mm.cell] = mm.work # put end if (mm.work == "clear") then mm.work = '' # clear mm.work += "mm.work = '' # clear" mm.tape[mm.cell] = mm.work # put end if (mm.work == "upper") then mm.work = '' # clear mm.work += "mm.work.upcase! # upper" mm.tape[mm.cell] = mm.work # put end if (mm.work == "lower") then mm.work = '' # clear mm.work += "mm.work.downcase! # lower" mm.tape[mm.cell] = mm.work # put end if (mm.work == "cap") then mm.work = '' # clear mm.work += "mm.work.capitalize! # capital" mm.tape[mm.cell] = mm.work # put end if (mm.work == "print") then mm.work = '' # clear mm.work += "print mm.work # print" mm.tape[mm.cell] = mm.work # put end if (mm.work == "state") then mm.work = '' # clear mm.work += "mm.printState() # state" mm.tape[mm.cell] = mm.work # put end if (mm.work == "pop") then mm.work = '' # clear mm.work += "mm.pop();" mm.tape[mm.cell] = mm.work # put end if (mm.work == "push") then mm.work = '' # clear mm.work += "mm.push();" mm.tape[mm.cell] = mm.work # put end if (mm.work == "unstack") then mm.work = '' # clear mm.work += "while mm.pop() do next end # unstack " mm.tape[mm.cell] = mm.work # put end if (mm.work == "stack") then mm.work = '' # clear mm.work += "while mm.push() do next end # stack " mm.tape[mm.cell] = mm.work # put end if (mm.work == "put") then mm.work = '' # clear mm.work += "mm.tape[mm.cell] = mm.work # put " mm.tape[mm.cell] = mm.work # put end if (mm.work == "get") then mm.work = '' # clear mm.work += "mm.work += mm.tape[mm.cell] # get" mm.tape[mm.cell] = mm.work # put end if (mm.work == "swap") then mm.work = '' # clear mm.work += "mm.work, mm.tape[mm.cell] = mm.tape[mm.cell], mm.work # swap " mm.tape[mm.cell] = mm.work # put end if (mm.work == "++") then mm.work = '' # clear mm.work += "mm.increment() # ++" mm.tape[mm.cell] = mm.work # put end if (mm.work == "--") then mm.work = '' # clear mm.work += "if mm.cell > 0 then mm.cell -= 1; end # --" mm.tape[mm.cell] = mm.work # put end if (mm.work == "read") then mm.work = '' # clear mm.work += "mm.read() # read" mm.tape[mm.cell] = mm.work # put end if (mm.work == "count") then mm.work = '' # clear mm.work += "mm.work += mm.counter.to_s # count " mm.tape[mm.cell] = mm.work # put end if (mm.work == "a+") then mm.work = '' # clear mm.work += "mm.counter += 1 # a+ " mm.tape[mm.cell] = mm.work # put end if (mm.work == "a-") then mm.work = '' # clear mm.work += "mm.counter -= 1 # a- " mm.tape[mm.cell] = mm.work # put end if (mm.work == "zero") then mm.work = '' # clear mm.work += "mm.counter = 0 # zero " mm.tape[mm.cell] = mm.work # put end if (mm.work == "chars") then mm.work = '' # clear mm.work += "mm.work += mm.charsRead.to_s # chars " mm.tape[mm.cell] = mm.work # put end if (mm.work == "lines") then mm.work = '' # clear mm.work += "mm.work += mm.linesRead.to_s # lines " mm.tape[mm.cell] = mm.work # put end if (mm.work == "nochars") then mm.work = '' # clear mm.work += "mm.charsRead = 0 # nochars " mm.tape[mm.cell] = mm.work # put end if (mm.work == "nolines") then mm.work = '' # clear mm.work += "mm.linesRead = 0 # nolines " mm.tape[mm.cell] = mm.work # put end # use a labelled loop to quit script. if (mm.work == "quit") then mm.work = '' # clear mm.work += "exit" mm.tape[mm.cell] = mm.work # put end # inline this? if (mm.work == "write") then mm.work = '' # clear mm.work += "File.write('sav.pp', mm.work)" mm.tape[mm.cell] = mm.work # put end # File.open("sav.pp", 'w') { |file| file.write("your text") } # convert to "pass" which does nothing. if (mm.work == "nop") then mm.work = '' # clear mm.work += "# nop: no-operation" mm.tape[mm.cell] = mm.work # put end mm.work = '' # clear mm.work += "command*" mm.push(); next end #----------------------------------------- # ebnf: commandset := command , command ; if (mm.work == "command*command*" || mm.work == "commandset*command*") then mm.work = '' # clear mm.work += "commandset*" mm.push(); # format the tape attributes. Add the next command on a newline if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += mm.tape[mm.cell] # get mm.work += "\n" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.increment() # ++ mm.work = '' # clear next end #------------------- # here we begin to parse "test*" and "ortestset*" and "andtestset*" # #------------------- # eg: B"abc" {} or E"xyz" {} # transform and markup the different test types if (mm.work == "begintext*,*" || mm.work == "endtext*,*" || mm.work == "quote*,*" || mm.work == "class*,*" || mm.work == "eof*,*" || mm.work == "tapetest*,*" || mm.work == "begintext*.*" || mm.work == "endtext*.*" || mm.work == "quote*.*" || mm.work == "class*.*" || mm.work == "eof*.*" || mm.work == "tapetest*.*" || mm.work == "begintext*{*" || mm.work == "endtext*{*" || mm.work == "quote*{*" || mm.work == "class*{*" || mm.work == "eof*{*" || mm.work == "tapetest*{*") then if (mm.work.start_with?("begin")) then mm.work = '' # clear mm.work += "mm.work.start_with?(" end if (mm.work.start_with?("end")) then mm.work = '' # clear mm.work += "mm.work.end_with?(" end if (mm.work.start_with?("quote")) then mm.work = '' # clear mm.work += "mm.work == " end if (mm.work.start_with?("class")) then mm.work = '' # clear mm.work += "mm.work.match?(" # unicode categories are also regexs end # clear the tapecell for testeof and testtape because # they take no arguments. if (mm.work.start_with?("eof")) then mm.work = '' # clear mm.tape[mm.cell] = mm.work # put mm.work += "mm.eof" end if (mm.work.start_with?("tapetest")) then mm.work = '' # clear mm.tape[mm.cell] = mm.work # put mm.work += "mm.work == mm.tape[mm.cell]" end mm.work += mm.tape[mm.cell] # get # a hack #B"mm.work.match?" { add ')'; } if (!mm.work.start_with?("mm.eof") && !mm.work.start_with?("mm.work ==")) then mm.work += ")" end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "test*" mm.push(); # the trick below pushes the right token back on the stack. mm.work += mm.tape[mm.cell] # get mm.work += "*" mm.push(); next end #------------------- # negated tests # eg: !B"xyz {} !(eof) {} !(==) {} # !E"xyz" {} # !"abc" {} # ![a-z] {} if (mm.work == "notbegintext*,*" || mm.work == "notendtext*,*" || mm.work == "notquote*,*" || mm.work == "notclass*,*" || mm.work == "noteof*,*" || mm.work == "nottapetest*,*" || mm.work == "notbegintext*.*" || mm.work == "notendtext*.*" || mm.work == "notquote*.*" || mm.work == "notclass*.*" || mm.work == "noteof*.*" || mm.work == "nottapetest*.*" || mm.work == "notbegintext*{*" || mm.work == "notendtext*{*" || mm.work == "notquote*{*" || mm.work == "notclass*{*" || mm.work == "noteof*{*" || mm.work == "nottapetest*{*") then if (mm.work.start_with?("notbegin")) then mm.work = '' # clear mm.work += "!mm.work.start_with?(" end if (mm.work.start_with?("notend")) then mm.work = '' # clear mm.work += "!mm.work.end_with?(" end if (mm.work.start_with?("notquote")) then mm.work = '' # clear mm.work += "mm.work != " end if (mm.work.start_with?("notclass")) then mm.work = '' # clear mm.work += "!mm.work.match?(" # ruby unicode categories are regexs end # clear the tapecell for testeof and testtape because # they take no arguments. if (mm.work.start_with?("noteof")) then mm.work = '' # clear mm.tape[mm.cell] = mm.work # put mm.work += "!mm.eof" end if (mm.work.start_with?("nottapetest")) then mm.work = '' # clear mm.tape[mm.cell] = mm.work # put mm.work += "mm.work != mm.tape[mm.cell]" end mm.work += mm.tape[mm.cell] # get if (!mm.work.start_with?("!mm.eof") && !mm.work.start_with?("mm.work !=")) then mm.work += ")" end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "test*" mm.push(); # the trick below pushes the right token back on the stack. mm.work += mm.tape[mm.cell] # get mm.work += "*" mm.push(); next end #------------------- # 3 tokens #------------------- mm.pop(); #----------------------------- # some 3 token errors!!! # not a comprehensive list of 3 token errors if (mm.work == "{*quote*;*" || mm.work == "{*begintext*;*" || mm.work == "{*endtext*;*" || mm.work == "{*class*;*" || mm.work == "commandset*quote*;*" || mm.work == "command*quote*;*") then mm.push(); mm.push(); mm.push(); mm.work += "[pep error]\n invalid syntax near line " mm.work += mm.linesRead.to_s # lines mm.work += " (char " mm.work += mm.charsRead.to_s # chars mm.work += ")" mm.work += " of script (misplaced semicolon?) \n" print mm.work # print mm.work = '' # clear exit end # to simplify subsequent tests, transmogrify a single command # to a commandset (multiple commands). if (mm.work == "{*command*}*") then mm.work = '' # clear mm.work += "{*commandset*}*" mm.push(); mm.push(); mm.push(); next end # errors! mixing AND and OR concatenation if (mm.work == ",*andtestset*{*" || mm.work == ".*ortestset*{*") then # push the tokens back to make debugging easier mm.push(); mm.push(); mm.push(); mm.work += " error: mixing AND (.) and OR (,) concatenation in \n" mm.work += " in pep script near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += ") \n" mm.work += " " mm.work += "\n For example:" mm.work += "\n B\".\".!E\"/\".[abcd./] { print; } # Correct!" mm.work += "\n B\".\".!E\"/\",[abcd./] { print; } # Error! \n" print mm.work # print mm.work = '' # clear exit end #-------------------------------------------- # ebnf: command := keyword , quoted-text , ";" ; # format: add "text"; if (mm.work == "word*quote*;*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get if (mm.work == "replace") then # error mm.work += "< command requires 2 parameters, not 1 \n" mm.work += "near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script. \n" print mm.work # print mm.work = '' # clear exit end # 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 if (mm.work == "escape" || mm.work == "unescape" || mm.work == "while" || mm.work == "whilenot") then # This is trickier than I thought it would be. mm.work = '' # clear mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- # check that arg not empty, (but an empty quote is ok # for the second arg of 'replace' if (mm.work == "\"\"") then mm.work = '' # clear mm.work += "[pep error] near line " mm.work += mm.linesRead.to_s # lines mm.work += " (or char " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " command '" mm.work += mm.tape[mm.cell] # get mm.work += "' cannot have an empty argument (\"\") \n" print mm.work # print exit end # quoted text has the quotes still around it. # also handle escape characters like \n \r etc # this needs to be better if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end # B "\\" { clip; } if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if (mm.work != "") then mm.work = '' # clear mm.work += "Pep script error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " command '" mm.work += mm.tape[mm.cell] # get mm.work += "' takes only a single character argument. \n" print mm.work # print exit end mm.work = '' # clear mm.work += mm.tape[mm.cell] # get end if (mm.work == "mark") then mm.work = '' # clear mm.work += "mm.marks[mm.cell] = " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += " # mark" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "go") then mm.work = '' # clear mm.work += "mm.goToMark(" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += ") # go" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "delim") then mm.work = '' # clear # the delimiter should be a single character, no? mm.work += "mm.delimiter = " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += " # delim " mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "add") then mm.work = '' # clear mm.work += "mm.work += " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- # handle multiline text # check this! \\n or \n # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\"\nmm.work += \"\\n") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "while") then mm.work = '' # clear mm.work += "while mm.peep == " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += "; # while \n" mm.work += " if mm.eof then break end\n mm.read()\n" mm.work += "end" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "whilenot") then mm.work = '' # clear mm.work += "while mm.peep != " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += "; # whilenot \n" mm.work += " if mm.eof then break end\n mm.read()\nend" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "until") then mm.work = '' # clear mm.work += "mm.until(" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- # error until cannot have empty argument if (mm.work == "mm.until(\"\"") then mm.work = '' # clear mm.work += "Pep script error near line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " empty argument for 'until' \n" mm.work += " " mm.work += "\n For example:" mm.work += "\n until '.txt'; until \">\"; # correct " mm.work += "\n until ''; until \"\"; # errors! \n" print mm.work # print exit end # handle multiline argument # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\\n") end mm.work += ");" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "escape") then mm.work = '' # clear mm.increment() # ++ # argument still has quotes around it # it should be a single character since this has been previously # checked. mm.work += mm.tape[mm.cell] # get if !mm.work.empty? then # clip mm.work = mm.work[0..-2] # clip end if !mm.work.empty? then # clop mm.work = mm.work[1..-1]; # clop end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear # A strange ruby fact: gsub("'", "\\'") doesnt work # but gsub("'", "\\\\'") does. Hence the hack below mm.work += "mm.work.gsub!(\"" mm.work += mm.tape[mm.cell] # get mm.work += "\", \"" # a hack, for the quirk above, but this hack will not work # if mm.escape != '\\' if (mm.work.end_with?("\"'\", \"")) then mm.work += "\#{mm.escape}" end mm.work += "\#{mm.escape}" mm.work += mm.tape[mm.cell] # get mm.work += "\") # escape" if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end # replace \n with n for example (only 1 character) if (mm.work == "unescape") then mm.work = '' # clear mm.increment() # ++ # unescape is not trivial, need to walk the string # hence the method rather than one-liner #add 'mm.unescapeChar('; get; add ') # unescape'; mm.work += "mm.work.gsub!(mm.escape+" mm.work += mm.tape[mm.cell] # get mm.work += ", " mm.work += mm.tape[mm.cell] # get mm.work += ") # escape" if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end # error, superfluous argument mm.work += ": command does not take an argument \n" mm.work += "near line " mm.work += mm.linesRead.to_s # lines mm.work += " of script. \n" print mm.work # print mm.work = '' # clear #state exit end #---------------------------------- # format: "while [:alpha:] ;" or whilenot [a-z] ; if (mm.work == "word*class*;*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get if (mm.work == "while") then mm.work = '' # clear mm.work += "# while \n" # the ruby pat.match? method should be faster than others mm.work += "while " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += ".match?(mm.peep)\n" mm.work += " if mm.eof then break end\n mm.read()\nend" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end if (mm.work == "whilenot") then mm.work = '' # clear mm.work += "# whilenot \n" mm.work += "while !" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += ".match?(mm.peep)\n" mm.work += " if mm.eof then break end\n" mm.work += " mm.read()\nend" mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end # error mm.work += " < command cannot have a class argument \n" mm.work += "line " mm.work += mm.linesRead.to_s # lines mm.work += ": error in script \n" print mm.work # print mm.work = '' # clear exit end # arrange the parse> label loops # also, change .restart code before the parse label if (mm.eof) then if (mm.work == "commandset*parse>*commandset*" || mm.work == "command*parse>*commandset*" || mm.work == "commandset*parse>*command*" || mm.work == "command*parse>*command*") then mm.work = '' # clear # indent both code blocks mm.work += " " mm.work += mm.tape[mm.cell] # get # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\n ") end # change .restart code before parse> label # replace if mm.work.length > 0 then mm.work.gsub!("next # restart", "break # restart") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.increment() # ++ mm.increment() # ++ mm.work += " " mm.work += mm.tape[mm.cell] # get # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\n ") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- # add a block so that .reparse works before the parse> label. mm.work += "\n# lex block \n" mm.work += "while true \n" mm.work += mm.tape[mm.cell] # get mm.work += "\n break \nend\n" mm.increment() # ++ mm.increment() # ++ mm.work += "if restart then restart = false; next; end\n" # indent code block # add " "; get; replace "\n" "\n "; put; clear; # ruby doesnt support labelled loops (but swift does, and go?) # add "parse: \n"; mm.work += "\n# parse block \n" mm.work += "while true \n" mm.work += mm.tape[mm.cell] # get mm.work += "\n break \nend # parse\n" if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "commandset*" mm.push(); next end end # ------------------------------- # 4 tokens # ------------------------------- mm.pop(); #------------------------------------- # bnf: command := replace , quote , quote , ";" ; # example: replace "and" "AND" ; if (mm.work == "word*quote*quote*;*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get if (mm.work == "replace") then #--------------------------- # a command plus 2 arguments, eg replace "this" "that" mm.work = '' # clear mm.work += "# replace \n" mm.work += "if mm.work.length > 0 then \n" mm.work += " mm.work.gsub!(" mm.increment() # ++ mm.work += mm.tape[mm.cell] # get mm.work += ", " mm.increment() # ++ mm.work += mm.tape[mm.cell] # get mm.work += ")\n end\n" if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); next end mm.work += "Pep script error on line " mm.work += mm.linesRead.to_s # lines mm.work += " (character " mm.work += mm.charsRead.to_s # chars mm.work += "): \n" mm.work += " command does not take 2 quoted arguments. \n" print mm.work # print exit end #------------------------------------- # 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*}*", if (mm.work == "begin*{*commandset*}*") then mm.work = '' # clear mm.increment() # ++ mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "beginblock*" mm.push(); next end # ------------- # parses and compiles concatenated tests # eg: 'a',B'b',E'c',[def],[:space:],[g-k] { ... # these 2 tests should be all that is necessary if (mm.work == "test*,*ortestset*{*" || mm.work == "test*,*test*{*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get mm.work += " || " mm.increment() # ++ mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "ortestset*{*" mm.push(); mm.push(); next end # dont mix AND and OR concatenations # ------------- # AND logic # parses and compiles concatenated AND tests # eg: 'a',B'b',E'c',[def],[:space:],[g-k] { ... # it is possible to elide this block with the negated block # for compactness but maybe readability is not as good. # negated tests can be chained with non negated tests. # eg: B'http' . !E'.txt' { ... } if (mm.work == "test*.*andtestset*{*" || mm.work == "test*.*test*{*") then mm.work = '' # clear mm.work += mm.tape[mm.cell] # get mm.work += " && " mm.increment() # ++ mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "andtestset*{*" mm.push(); mm.push(); next end #------------------------------------- # we should not have to check for the {*command*}* pattern # because that has already been transformed to {*commandset*}* if (mm.work == "test*{*commandset*}*" || mm.work == "andtestset*{*commandset*}*" || mm.work == "ortestset*{*commandset*}*") then mm.work = '' # clear # indent the code for readability mm.increment() # ++ mm.increment() # ++ mm.work += " " mm.work += mm.tape[mm.cell] # get # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\n ") end mm.tape[mm.cell] = mm.work # put if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.work = '' # clear mm.work += "if (" mm.work += mm.tape[mm.cell] # get mm.work += ") then\n" mm.increment() # ++ mm.increment() # ++ mm.work += mm.tape[mm.cell] # get # block end required mm.work += "\nend" if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "command*" mm.push(); # always reparse/compile next end # ------------- # multi-token end-of-stream errors # not a comprehensive list of errors... if (mm.eof) then if (mm.work.end_with?("begintext*") || mm.work.end_with?("endtext*") || mm.work.end_with?("test*") || mm.work.end_with?("ortestset*") || mm.work.end_with?("andtestset*")) then mm.work += " Error near end of script at line " mm.work += mm.linesRead.to_s # lines mm.work += ". Test with no brace block? \n" print mm.work # print mm.work = '' # clear exit end if (mm.work.end_with?("quote*") || mm.work.end_with?("class*") || mm.work.end_with?("word*")) then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "Error at end of pep script near line " mm.work += mm.linesRead.to_s # lines mm.work += ": missing semi-colon? \n" mm.work += "Parse stack: " mm.work += mm.tape[mm.cell] # get mm.work += "\n" print mm.work # print mm.work = '' # clear exit end if (mm.work.end_with?("{*") || mm.work.end_with?("}*") || mm.work.end_with?(";*") || mm.work.end_with?(",*") || mm.work.end_with?(".*") || mm.work.end_with?("!*") || mm.work.end_with?("B*") || mm.work.end_with?("E*")) then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "Error: misplaced terminal character at end of script! (line " mm.work += mm.linesRead.to_s # lines mm.work += "). \n" mm.work += "Parse stack: " mm.work += mm.tape[mm.cell] # get mm.work += "\n" print mm.work # print mm.work = '' # clear exit end end # put the 4 (or less) tokens back on the stack mm.push(); mm.push(); mm.push(); mm.push(); if (mm.eof) then print mm.work # print mm.work = '' # clear # create the virtual machine object code and save it # somewhere on the tape. mm.work += "#!/usr/bin/ruby" mm.work += "\n" mm.work += "\n# code generated by \"translate.ruby.pss\" a pep script" mm.work += "\n# http://bumble.sf.net/books/pars/tr/" mm.work += "\n# require 'something'" mm.work += "\n" mm.work += "\nclass Machine " mm.work += "\n # make a new machine " mm.work += "\n attr_accessor :work, :charsRead, :linesRead, :escape, :delimiter, " mm.work += "\n :counter, :stack, :tape, :cell, :marks, :eof, :peep" mm.work += "\n " mm.work += "\n def initialize() " mm.work += "\n @size = 300 # how many elements in stack/tape/marks" mm.work += "\n @eof = false # end of stream reached?" mm.work += "\n @charsRead = 0 # how many chars already read" mm.work += "\n @linesRead = 1 # how many lines already read" mm.work += "\n @escape = \"\\\\\"" mm.work += "\n @delimiter = \"*\" # push/pop delimiter (default \"*\")" mm.work += "\n @counter = 0 # a counter for anything" mm.work += "\n @work = \"\" # the workspace" mm.work += "\n @stack = [] # stack for parse tokens " mm.work += "\n @cell = 0 # current tape cell" mm.work += "\n @tape = Array.new(@size) {String.new} # a list of attribute for tokens " mm.work += "\n @marks = Array.new(@size) {String.new} # marked tape cells " mm.work += "\n # or dont initialse peep until \"parse()\" calls \"setInput()\"" mm.work += "\n @peep = ARGF.readchar" mm.work += "\n end" mm.work += "\n" mm.work += "\n def setInput(newInput) " mm.work += "\n puts \"to be implemented\"" mm.work += "\n end" mm.work += "\n" mm.work += "\n # read one character from the input stream and " mm.work += "\n # update the machine." mm.work += "\n def read " mm.work += "\n if @eof then exit end" mm.work += "\n @charsRead += 1;" mm.work += "\n # increment lines" mm.work += "\n if @peep == \"\\n\" then @linesRead += 1 end" mm.work += "\n @work += @peep" mm.work += "\n @peep = ARGF.readchar" mm.work += "\n if @peep.nil? then @eof = true; end" mm.work += "\n end" mm.work += "\n" mm.work += "\n # test if all chars in workspace are in unicode category" mm.work += "\n def isInCategory(cat) " mm.work += "\n #for ch in @work" mm.work += "\n # if not category(ch).start_with?(cat) then return false end" mm.work += "\n #return True" mm.work += "\n end" mm.work += "\n" mm.work += "\n # this needs to actually walk the string" mm.work += "\n # eg \"ab\cab\\cab\c\"" mm.work += "\n # not trivial" mm.work += "\n def unescapeChar(c)" mm.work += "\n @work.gsub!(\"\#{@escape}\#{c}\", c)" mm.work += "\n end" mm.work += "\n" mm.work += "\n # add escape character : trivial?" mm.work += "\n def escapeChar(c)" mm.work += "\n @work.gsub!(c, @escape+c)" mm.work += "\n end" mm.work += "\n" mm.work += "\n # a helper for the multiescape until bug" mm.work += "\n def countEscaped(suffix) " mm.work += "\n count = 0" mm.work += "\n #s = @work.sub(/\#{suffix}$/, \"\")" mm.work += "\n s = @work.delete_suffix(suffix)" mm.work += "\n while s.end_with?(@escape) " mm.work += "\n count += 1" mm.work += "\n s.delete_suffix!(@escape)" mm.work += "\n end" mm.work += "\n # puts \"count=\#{count}\"" mm.work += "\n return count" mm.work += "\n end" mm.work += "\n" mm.work += "\n # reads the input stream until the workspace end with text " mm.work += "\n def until(suffix) " mm.work += "\n # read at least one character" mm.work += "\n if @eof then return end" mm.work += "\n self.read()" mm.work += "\n while true do" mm.work += "\n if @eof then return end" mm.work += "\n # need to count the @escape chars preceding suffix" mm.work += "\n # if odd, keep reading, if even, stop" mm.work += "\n if @work.end_with?(suffix) then " mm.work += "\n if (self.countEscaped(suffix).even?) then return end" mm.work += "\n end" mm.work += "\n self.read()" mm.work += "\n end" mm.work += "\n end " mm.work += "\n" mm.work += "\n # this implements the ++ command incrementing the tape pointer" mm.work += "\n # and growing the tape and marks arrays if required" mm.work += "\n def increment()" mm.work += "\n @cell += 1" mm.work += "\n if @cell >= @size then " mm.work += "\n @tape.append(\"\")" mm.work += "\n @marks.append(\"\")" mm.work += "\n @size += 1" mm.work += "\n end" mm.work += "\n end" mm.work += "\n" mm.work += "\n # pop the first token from the stack into the workspace */" mm.work += "\n def pop() " mm.work += "\n if @stack.length == 0 then return false end" mm.work += "\n @work = @stack.pop() + @work" mm.work += "\n if @cell > 0 then @cell -= 1 end" mm.work += "\n return true" mm.work += "\n end" mm.work += "\n" mm.work += "\n # push the first token from the workspace to the stack " mm.work += "\n def push() " mm.work += "\n # dont increment the tape pointer on an empty push" mm.work += "\n if @work == \"\" then return false end" mm.work += "\n # need to get this from the delimiter." mm.work += "\n iFirst = @work.index(@delimiter)" mm.work += "\n if iFirst.nil?" mm.work += "\n @stack.push(@work); @work = \"\"; return true" mm.work += "\n # also @stack << @work" mm.work += "\n end" mm.work += "\n # s[i..j] means all chars from i to j" mm.work += "\n # s[i,n] means n chars from i" mm.work += "\n @stack.push(@work[0..iFirst])" mm.work += "\n @work = @work[iFirst+1..-1]" mm.work += "\n self.increment()" mm.work += "\n return true" mm.work += "\n end" mm.work += "\n" mm.work += "\n def printState() " mm.work += "\n puts \"Stack[\#{@stack.join(', ')}] Work[\#{@work}] Peep[\#{@peep}]\"" mm.work += "\n puts \"Acc:\#{@counter} Esc:\#{@escape} Delim:\#{@delimiter} Chars:\#{@charsRead}\" +" mm.work += "\n \" Lines:\#{@linesRead} Cell:\#{@cell}\"" mm.work += "\n end" mm.work += "\n" mm.work += "\n def goToMark(mark) " mm.work += "\n ii = @marks.find_index(mark)" mm.work += "\n if !ii.nil? then " mm.work += "\n @cell = ii " mm.work += "\n else" mm.work += "\n print(\"bad mark '\" + mark + \"'!\") " mm.work += "\n exit" mm.work += "\n end" mm.work += "\n end" mm.work += "\n" mm.work += "\n # this is where the actual parsing/compiling code should go" mm.work += "\n # so that it can be used by other ruby classes/objects. Also" mm.work += "\n # should have a stream argument." mm.work += "\n def parse(s) " mm.work += "\n # a reset or \"setinput()\" method would be useful to parse a " mm.work += "\n # different string/file/stream, without creating a new" mm.work += "\n # machine object." mm.work += "\n # could use code like this to check if input is string or file" mm.work += "\n #if isinstance(s, file)" mm.work += "\n print(\"\")" mm.work += "\n # @reset(s)" mm.work += "\n # @reader = s" mm.work += "\n #elseif isinstance(s, string)" mm.work += "\n #f = StringIO.StringIO(\"test\")" mm.work += "\n #for line in f print(line)" mm.work += "\n #else" mm.work += "\n # f = STDIN " mm.work += "\n #end" mm.work += "\n #puts \"not implemented\"" mm.work += "\n end" mm.work += "\n" mm.work += "\nend" mm.work += "\n" mm.work += "\n# end of Machine class definition" mm.work += "\n" mm.work += "\n# will become:" mm.work += "\n# mm.parse(sys.stdin) or " mm.work += "\n# mm.parse(\"abcdef\") or" mm.work += "\n# open f; mm.parse(f)" mm.work += "\n" mm.work += "\n# the restart flag, which allows .restart to work before the " mm.work += "\n# parse label, in languages (like ruby) that dont have " mm.work += "\n# labelled loops" mm.work += "\nrestart = false" mm.work += "\nmm = Machine.new \n" # save the code in the current tape cell mm.tape[mm.cell] = mm.work # put mm.work = '' # clear #--------------------- # check if the script correctly parsed (there should only # be one token on the stack, namely "commandset*" or "command*"). mm.pop(); mm.pop(); if (mm.work == "commandset*" || mm.work == "command*") then mm.work = '' # clear # indent generated code (6 spaces) for readability. mm.work += " " mm.work += mm.tape[mm.cell] # get # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\n ") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear # restore the java preamble from the tape mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- #add 'script: \n'; mm.work += "while !mm.eof do \n" mm.work += mm.tape[mm.cell] # get # end block marker required in ruby mm.work += "\nend\n" mm.work += "\n\n# end of generated code\n" # put a copy of the final compilation into the tapecell # so it can be inspected interactively. mm.tape[mm.cell] = mm.work # put print mm.work # print mm.work = '' # clear exit end if (mm.work == "beginblock*commandset*" || mm.work == "beginblock*command*") then mm.work = '' # clear # indentation not needed here #add ""; get; #replace "\n" "\n"; put; clear; # indent main code for readability. mm.increment() # ++ mm.work += " " mm.work += mm.tape[mm.cell] # get # replace if mm.work.length > 0 then mm.work.gsub!("\n", "\n ") end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear if mm.cell > 0 then mm.cell -= 1; end # -- # get java preamble from tape mm.increment() # ++ mm.increment() # ++ mm.work += mm.tape[mm.cell] # get if mm.cell > 0 then mm.cell -= 1; end # -- if mm.cell > 0 then mm.cell -= 1; end # -- mm.work += mm.tape[mm.cell] # get mm.work += "\n" mm.increment() # ++ # a labelled loop for "quit" (but quit can just exit?) #add "script: \n"; mm.work += "while !mm.eof do \n" mm.work += mm.tape[mm.cell] # get # end block marker required in ruby mm.work += "\nend\n" mm.work += "\n\n# end of generated code\n" # put a copy of the final compilation into the tapecell # for interactive debugging. mm.tape[mm.cell] = mm.work # put print mm.work # print mm.work = '' # clear exit end mm.push(); mm.push(); # try to explain some more errors while mm.pop() do next end # unstack if (mm.work.start_with?("parse>")) then mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "[error] pep syntax error:\n" mm.work += " The parse> label cannot be the 1st item \n" mm.work += " of a script \n" print mm.work # print exit end mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work = '' # clear mm.work += "After compiling with 'translate.ruby.pss' (at EOF): \n " mm.work += " parse error in input script. \n " print mm.work # print mm.work = '' # clear while mm.pop() do next end # unstack mm.tape[mm.cell] = mm.work # put mm.work = '' # clear mm.work += "Parse stack: " mm.work += mm.tape[mm.cell] # get mm.work += "\n" mm.work += " * debug script " mm.work += " >> pep -If script -i 'some input' \n " mm.work += " * debug compilation. \n " mm.work += " >> pep -Ia asm.pp script' \n " print mm.work # print mm.work = '' # clear exit end # not eof # there is an implicit .restart command here (jump start) break end # parse end # end of generated code