diff --git a/src/xoomonk.py b/src/xoomonk.py index f3c06ed..2d51433 100755 --- a/src/xoomonk.py +++ b/src/xoomonk.py @@ -25,6 +25,269 @@ if self.value is None: return 'AST(%r,%r)' % (self.type, self.children) return 'AST(%r,value=%r)' % (self.type, self.value) + + +class Scanner(object): + """A Scanner provides facilities for extracting successive + Xoomonk tokens from a string. + + >>> a = Scanner(" {:= } foo ") + >>> a.token + '{' + >>> a.type + 'operator' + >>> a.scan() + >>> a.on(":=") + True + >>> a.on_type('operator') + True + >>> a.check_type('identifier') + Traceback (most recent call last): + ... + SyntaxError: Expected identifier, but found operator (':=') + >>> a.scan() + >>> a.consume(".") + False + >>> a.consume("}") + True + >>> a.expect("foo") + >>> a.type + 'EOF' + >>> a.expect("bar") + Traceback (most recent call last): + ... + SyntaxError: Expected 'bar', but found 'None' + + """ + def __init__(self, text): + self.text = text + self.token = None + self.type = None + self.scan() + + def scan_pattern(self, pattern, type, token_group=1, rest_group=2): + pattern = r'^(' + pattern + r')(.*?)$' + match = re.match(pattern, self.text, re.DOTALL) + if not match: + return False + else: + self.type = type + self.token = match.group(token_group) + self.text = match.group(rest_group) + #print >>sys.stderr, "(%r/%s->%r)" % (self.token, self.type, self.text) + return True + + def scan(self): + self.scan_pattern(r'[ \t\n\r]*', 'whitespace') + if not self.text: + self.token = None + self.type = 'EOF' + return + if self.scan_pattern(r':=|\;|\{|\}|\*|\.|\^|\$', 'operator'): + return + if self.scan_pattern(r'\d+', 'integer literal'): + return + if self.scan_pattern(r'\"(.*?)\"', 'string literal', + token_group=2, rest_group=3): + return + if self.scan_pattern(r'\w+', 'identifier'): + return + if self.scan_pattern(r'.', 'unknown character'): + return + else: + raise ValueError, "this should never happen, self.text=(%s)" % self.text + + def expect(self, token): + if self.token == token: + self.scan() + else: + raise SyntaxError("Expected '%s', but found '%s'" % + (token, self.token)) + + def on(self, token): + return self.token == token + + def on_type(self, type): + return self.type == type + + def check_type(self, type): + if not self.type == type: + raise SyntaxError("Expected %s, but found %s ('%s')" % + (type, self.type, self.token)) + + def consume(self, token): + if self.token == token: + self.scan() + return True + else: + return False + + +# Parser + +class Parser(object): + """A Parser provides facilities for recognizing various + parts of a Xoomonk program based on Xoomonk's grammar. + + >>> a = Parser("123") + >>> a.expr() + AST('IntLit',value=123) + >>> a = Parser("{ a := 123 }") + >>> a.expr() + AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=123)])]) + + >>> a = Parser("a:=5 c:=4") + >>> a.program() + AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=5)]), AST('Assignment',[AST('Ref',[AST('Identifier',value='c')]), AST('IntLit',value=4)])]) + + >>> a = Parser("a := { b := 1 }") + >>> a.program() + AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='b')]), AST('IntLit',value=1)])])])]) + + """ + def __init__(self, text): + self.scanner = Scanner(text) + + def program(self): + p = AST('Program') + while self.scanner.type != 'EOF': + p.add_child(self.stmt()) + return p + + def stmt(self): + if self.scanner.on("print"): + return self.print_stmt() + else: + return self.assign() + + def assign(self): + r = self.ref() + self.scanner.expect(":=") + e = self.expr() + return AST('Assignment', [r, e]) + + def print_stmt(self): + s = None + self.scanner.expect("print") + if self.scanner.consume("string"): + self.scanner.check_type("string literal") + st = self.scanner.token + self.scanner.scan() + s = AST('PrintString', value=st) + elif self.scanner.consume("char"): + e = self.expr() + s = AST('PrintChar', [e]) + else: + e = self.expr() + s = AST('Print', [e]) + newline = True + if self.scanner.consume(";"): + newline = False + if newline: + s = AST('Newline', [s]) + return s + + def expr(self): + v = None + if self.scanner.on("{"): + v = self.block() + elif self.scanner.on_type('integer literal'): + v = AST('IntLit', value=int(self.scanner.token)) + self.scanner.scan() + else: + v = self.ref() + if self.scanner.consume("*"): + v = AST('CopyOf', [v]) + return v + + def block(self): + b = AST('Block') + self.scanner.expect("{") + while not self.scanner.on("}"): + b.add_child(self.stmt()) + self.scanner.expect("}") + return b + + def ref(self): + r = AST('Ref') + r.add_child(self.name()) + while self.scanner.consume("."): + r.add_child(self.name()) + return r + + def name(self): + if self.scanner.consume("^"): + return AST('Upvalue') + elif self.scanner.consume("$"): + self.scanner.check_type("identifier") + id = self.scanner.token + self.scanner.scan() + return AST('Dollar', value=id) + else: + self.scanner.check_type("identifier") + id = self.scanner.token + self.scanner.scan() + return AST('Identifier', value=id) + + +# Runtime support for Xoomonk. + +def demo(store): + print "demo!" + +class MalingeringStore(object): + """ + >>> a = MalingeringStore(['a','b'], [], demo) + demo! + >>> a['a'] = 7 + >>> print a['a'] + 7 + >>> a['c'] = 7 + Traceback (most recent call last): + ... + ValueError: Attempt to assign undefined variable c + >>> a = MalingeringStore(['a','b'], ['a'], demo) + >>> a['a'] = 7 + demo! + >>> a = MalingeringStore(['a','b'], ['a'], demo) + >>> a['b'] = 7 + Traceback (most recent call last): + ... + ValueError: Attempt to assign unresolved variable b + + """ + def __init__(self, variables, unassigned, fun): + self.dict = {} + self.variables = variables + for variable in self.variables: + self.dict[variable] = 0 + self.unassigned = unassigned + self.fun = fun + if not self.unassigned: + self.run() + + def run(self): + self.fun(self) + + def __getitem__(self, name): + if name not in self.variables: + raise ValueError("Attempt to access undefined variable %s" % name) + # check to see if it is unassigned or derived + return self.dict[name] + + def __setitem__(self, name, value): + if name not in self.variables: + raise ValueError("Attempt to assign undefined variable %s" % name) + if name in self.unassigned: + self.dict[name] = value + self.unassigned.remove(name) + if not self.unassigned: + self.run() + elif self.unassigned: + raise ValueError("Attempt to assign unresolved variable %s" % name) + else: + # the store is saturated, do what you want + self.dict[name] = value def eval_xoomonk(ast, state): @@ -64,269 +327,6 @@ raise NotImplementedError, "not an AST type I know: %s" % type -class Scanner(object): - """A Scanner provides facilities for extracting successive - Xoomonk tokens from a string. - - >>> a = Scanner(" {:= } foo ") - >>> a.token - '{' - >>> a.type - 'operator' - >>> a.scan() - >>> a.on(":=") - True - >>> a.on_type('operator') - True - >>> a.check_type('identifier') - Traceback (most recent call last): - ... - SyntaxError: Expected identifier, but found operator (':=') - >>> a.scan() - >>> a.consume(".") - False - >>> a.consume("}") - True - >>> a.expect("foo") - >>> a.type - 'EOF' - >>> a.expect("bar") - Traceback (most recent call last): - ... - SyntaxError: Expected 'bar', but found 'None' - - """ - def __init__(self, text): - self.text = text - self.token = None - self.type = None - self.scan() - - def scan_pattern(self, pattern, type, token_group=1, rest_group=2): - pattern = r'^(' + pattern + r')(.*?)$' - match = re.match(pattern, self.text, re.DOTALL) - if not match: - return False - else: - self.type = type - self.token = match.group(token_group) - self.text = match.group(rest_group) - #print >>sys.stderr, "(%r/%s->%r)" % (self.token, self.type, self.text) - return True - - def scan(self): - self.scan_pattern(r'[ \t\n\r]*', 'whitespace') - if not self.text: - self.token = None - self.type = 'EOF' - return - if self.scan_pattern(r':=|\;|\{|\}|\*|\.|\^|\$', 'operator'): - return - if self.scan_pattern(r'\d+', 'integer literal'): - return - if self.scan_pattern(r'\"(.*?)\"', 'string literal', - token_group=2, rest_group=3): - return - if self.scan_pattern(r'\w+', 'identifier'): - return - if self.scan_pattern(r'.', 'unknown character'): - return - else: - raise ValueError, "this should never happen, self.text=(%s)" % self.text - - def expect(self, token): - if self.token == token: - self.scan() - else: - raise SyntaxError("Expected '%s', but found '%s'" % - (token, self.token)) - - def on(self, token): - return self.token == token - - def on_type(self, type): - return self.type == type - - def check_type(self, type): - if not self.type == type: - raise SyntaxError("Expected %s, but found %s ('%s')" % - (type, self.type, self.token)) - - def consume(self, token): - if self.token == token: - self.scan() - return True - else: - return False - - -# Parser - -class Parser(object): - """A Parser provides facilities for recognizing various - parts of a Xoomonk program based on Xoomonk's grammar. - - >>> a = Parser("123") - >>> a.expr() - AST('IntLit',value=123) - >>> a = Parser("{ a := 123 }") - >>> a.expr() - AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=123)])]) - - >>> a = Parser("a:=5 c:=4") - >>> a.program() - AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=5)]), AST('Assignment',[AST('Ref',[AST('Identifier',value='c')]), AST('IntLit',value=4)])]) - - >>> a = Parser("a := { b := 1 }") - >>> a.program() - AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='b')]), AST('IntLit',value=1)])])])]) - - """ - def __init__(self, text): - self.scanner = Scanner(text) - - def program(self): - p = AST('Program') - while self.scanner.type != 'EOF': - p.add_child(self.stmt()) - return p - - def stmt(self): - if self.scanner.on("print"): - return self.print_stmt() - else: - return self.assign() - - def assign(self): - r = self.ref() - self.scanner.expect(":=") - e = self.expr() - return AST('Assignment', [r, e]) - - def print_stmt(self): - s = None - self.scanner.expect("print") - if self.scanner.consume("string"): - self.scanner.check_type("string literal") - st = self.scanner.token - self.scanner.scan() - s = AST('PrintString', value=st) - elif self.scanner.consume("char"): - e = self.expr() - s = AST('PrintChar', [e]) - else: - e = self.expr() - s = AST('Print', [e]) - newline = True - if self.scanner.consume(";"): - newline = False - if newline: - s = AST('Newline', [s]) - return s - - def expr(self): - v = None - if self.scanner.on("{"): - v = self.block() - elif self.scanner.on_type('integer literal'): - v = AST('IntLit', value=int(self.scanner.token)) - self.scanner.scan() - else: - v = self.ref() - if self.scanner.consume("*"): - v = AST('CopyOf', [v]) - return v - - def block(self): - b = AST('Block') - self.scanner.expect("{") - while not self.scanner.on("}"): - b.add_child(self.stmt()) - self.scanner.expect("}") - return b - - def ref(self): - r = AST('Ref') - r.add_child(self.name()) - while self.scanner.consume("."): - r.add_child(self.name()) - return r - - def name(self): - if self.scanner.consume("^"): - return AST('Upvalue') - elif self.scanner.consume("$"): - self.scanner.check_type("identifier") - id = self.scanner.token - self.scanner.scan() - return AST('Dollar', value=id) - else: - self.scanner.check_type("identifier") - id = self.scanner.token - self.scanner.scan() - return AST('Identifier', value=id) - - -# Runtime support for Xoomonk. - -def demo(store): - print "demo!" - -class MalingeringStore(object): - """ - >>> a = MalingeringStore(['a','b'], [], demo) - demo! - >>> a['a'] = 7 - >>> print a['a'] - 7 - >>> a['c'] = 7 - Traceback (most recent call last): - ... - ValueError: Attempt to assign undefined variable c - >>> a = MalingeringStore(['a','b'], ['a'], demo) - >>> a['a'] = 7 - demo! - >>> a = MalingeringStore(['a','b'], ['a'], demo) - >>> a['b'] = 7 - Traceback (most recent call last): - ... - ValueError: Attempt to assign unresolved variable b - - """ - def __init__(self, variables, unassigned, fun): - self.dict = {} - self.variables = variables - for variable in self.variables: - self.dict[variable] = 0 - self.unassigned = unassigned - self.fun = fun - if not self.unassigned: - self.run() - - def run(self): - self.fun(self) - - def __getitem__(self, name): - if name not in self.variables: - raise ValueError("Attempt to access undefined variable %s" % name) - # check to see if it is unassigned or derived - return self.dict[name] - - def __setitem__(self, name, value): - if name not in self.variables: - raise ValueError("Attempt to assign undefined variable %s" % name) - if name in self.unassigned: - self.dict[name] = value - self.unassigned.remove(name) - if not self.unassigned: - self.run() - elif self.unassigned: - raise ValueError("Attempt to assign unresolved variable %s" % name) - else: - # the store is saturated, do what you want - self.dict[name] = value - - def main(argv): optparser = OptionParser(__doc__) optparser.add_option("-a", "--show-ast",