Call senderDoMessage := method(m, sender doMessage(m, sender)) Token := Object clone do( newSlot("name") newSlot("string") asSimpleString := method(string) asString := getSlot("asSimpleString") isNumber := false isIdent := false ) NumberToken := Token clone do( setName("number") newSlot("value") asSimpleString := method(name .. "(" .. value .. ")") asString := getSlot("asSimpleString") isNumber := true ) IdentToken := Token clone do( setName("ident") newSlot("ident") asSimpleString := method(name .. "(" .. ident .. ")") asString := getSlot("asSimpleString") isIdent := true ) Tokenizer := Object clone do( easyTokens := Map clone tokens := list currentToken := nil ) Tokenizer newToken := method(name, string, Token setSlot(name, Token clone setName(name) setString(string ifNilEval(name))) ) Tokenizer newEasyToken := method(name, string, t := newToken(name, string) easyTokens atPut(string ifNilEval(name), t) t ) Tokenizer nextToken := method(string, currentToken = tokens removeFirst ) Tokenizer tokenize := method(string, words := list string splitNoEmpties foreach(word, # Dodgy dodgy hack... if(word endsWithSeq(";"), words append(word slice(0, -1), ";") continue ) if(word endsWithSeq(","), words append(word slice(0, -1), ",") continue ) if(word endsWithSeq("."), words append(word slice(0, -1), ".") continue ) words append(word) ) tokens := list words foreach(word, if(t := easyTokens at(word asLowercase), tokens append(t clone) continue ) if(number := word asNumber, if(number asString != word, error /*TODO: raise an error here!*/) tokens append(NumberToken clone setValue(number)) continue ) tokens append(IdentToken clone setIdent(word)) ) self tokens = tokens self ) ParseException := Exception clone Parser := Object clone do( tokenizer := nil currentToken := method(tokenizer currentToken) ) Parser accept := method(name, if(currentToken name == name, call evalArgAt(1) tokenizer nextToken return true ) false ) Parser tSwitch := method(c, m := c argAt(0) while(m, nameMsg := m argAt(0) if(m argAt(0) name == "unexpectedToken", return c senderDoMessage(m argAt(1)) ) name := c senderDoMessage(m argAt(0)) if(currentToken name == name, r := c senderDoMessage(m argAt(1)) tokenizer nextToken if(m arguments size >= 3, r = c senderDoMessage(m argAt(2)) ) return r ) m = m nextMessage ) nil ) Parser acceptSwitch := method(tSwitch(call)) Parser expectSwitch := method( r := tSwitch(call) if(r == nil, ParseException raise("syntax error") ) r ) Parser expect := method(name, if(accept(name, r := call evalArgAt(1)), return r ) ParseException raise("expect: unexpected symbol. Expected: '#io{name}', found: '#io{currentToken name}'." interpolate) ) Parser parse := method(tokenizer, self tokenizer = tokenizer self tokenizer nextToken main self ) # PL/0 Specifics PL0Tokenizer := Tokenizer clone do( newEasyToken("dot", ".") newEasyToken("assignment", ":=") newEasyToken("equals", "=") newEasyToken("comma", ",") newEasyToken("semicolon", ";") newEasyToken("hash", "#") newEasyToken("less", "<") newEasyToken("greater", ">") newEasyToken("lessEquals", "<=") newEasyToken("greaterEquals", ">=") newEasyToken("plus", "+") newEasyToken("minus", "-") newEasyToken("multiply", "*") newEasyToken("divide", "/") newEasyToken("lparen", "(") newEasyToken("rparen", ")") "const var procedure call begin end odd" split foreach(name, newEasyToken(name)) "if then while do writeln" split foreach(name, newEasyToken(name .. "_", name)) ) PL0Parser := Parser clone do( newSlot("program") init := method( program = Object clone ) ) PL0Parser factor := method( expectSwitch( ("ident", Message clone setName(ident)) ("number", Message clone setName(number asString) setCachedResult(number)) ("lparen", r := expression expect("rparen") r ) (unexpectedToken, ParseException raise("factor: syntax error")) ) ) PL0Parser term := method( root := tail := factor loop( op := acceptSwitch( ("multiply", "*") ("divide", "/") ) if(op == nil, break) n := Message clone setName(op) setArguments(list(factor)) tail setAttachedMessage(n) tail := n ) root ) PL0Parser expression := method( op := acceptSwitch( ("plus", method(m, m)) ("minus", method(m, Message clone setName("-") setArguments(list(m)))) (unexpectedToken, method(m, m)) ) root := tail := op(term) loop( op := acceptSwitch( ("plus", "+") ("minus", "-") ) if(op == nil, break) t := term n := Message clone setName(op) setArguments(list(t)) tail setAttachedMessage(n) tail := n ) root ) PL0Parser condition := method( r := if (accept("odd"), expression setAttachedMessage(Message clone setName("isOdd")) , root := expression tail := root; while(tail attachedMessage, tail = tail attachedMessage) op := acceptSwitch( ("equals", "==") ("hash", "!=") ("less", "<") ("lessEquals", "<=") ("greater", ">") ("greaterEquals", ">=") (unexpectedToken, ParseException raise("condition: invalid operator")) ) rhs := expression tail setAttachedMessage(Message clone setName(op) setArguments(list(rhs))) root ) r ) PL0Parser statement := method( acceptSwitch( ("ident", name := Message clone setName("\"" .. ident .. "\"") setCachedResult(ident) , expect("assignment") e := expression Message clone setName("updateSlot") setArguments(list(name, e)) ) ("call", nil, expect("ident", Message clone setName(ident)) ) ("writeln_", nil, expect("ident", Message clone setName(ident)) setAttachedMessage(Message clone setName("println")) ) ("begin", nil, root := nil tail := nil loop( s := statement if(tail, tail setNextMessage(s) tail = s , root = tail = s ) if(accept("semicolon") not, break) ) expect("end") if(root == nil, Message clone setName("nil") , root ) ) ("if_", nil, c := condition expect("then_") s := statement Message clone setName("if") setArguments(list(c, s)) ) ("while_", nil, c := condition expect("do_") s := statement Message clone setName("while") setArguments(list(c, s)) ) ) ) PL0Parser ident := method(currentToken ident) PL0Parser number := method(currentToken value) PL0Parser block := method( if (accept("const"), loop( expect("ident", name := ident) expect("equals") expect("number", value := number) program setSlot(name, value) if (accept("comma") not, break) ) expect("semicolon") ) if (accept("var"), loop( expect("ident", program setSlot(ident, nil)) if (accept("comma") not, break) ) expect("semicolon") ) procName := "main" while (accept("procedure"), expect("ident", procName := ident) expect("semicolon") body := self block program setSlot(procName, Object getSlot("Block") clone setMessage(body)) expect("semicolon") ) s := statement s ) PL0Parser main := method( body := self block program setSlot("main", Object getSlot("Block") clone setMessage(body)) expect("dot") ) nil showStack := method if(args size < 2, writeln("Error: Provide a PL/0 file to run on the command line.") System exit ) e := try( tokenizer := PL0Tokenizer clone tokenize(File clone setPath(args at(1)) contents) parser := PL0Parser clone parse(tokenizer) program := parser program writeln("Before: ", program) program main writeln writeln("After: ", program) ) e showStack