diff --git a/README.md b/README.md index 1c0cd5d..e1e8625 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,5 @@ IMProved aims to build an interpreter for IMP (based on [this](http://jayconrod.com/posts/37/a-simple-interpreter-from-scratch-in-python-part-1)) that is fully documented, occassionally improved and embeddable into Python. + +As you may notice, I am bad at documentation. diff --git a/improved/ast.py b/improved/ast.py new file mode 100644 index 0000000..428fd7c --- /dev/null +++ b/improved/ast.py @@ -0,0 +1,422 @@ +"""The AST definition""" +import operator + +class Equality(object): + """ + A baseclass for all AST nodes. + It implements basic checks for equality. + """ + def __eq__(self, o): + """ + Checks for equality. Returns True when the + class dictionaries are the same and the + classes are based on the same baseclass. + Otherwise returns False. + + o -- the other object + + returns -- a Boolean + """ + return isinstance(o, self.__class__) and self.__dict__ == o.__dict__ + + def __ne__(self, o): + """ + Checks for inequality. + Simply negates the equality check. + + o -- the other object + + returns -- a Boolean + """ + return not self.__eq__(o) + +class Statement(Equality): + """The baseclass of all statements.""" + pass + +class ArithmeticExp(Equality): + """The baseclass of all arithmetic expressions.""" + pass + +class BooleanExp(Equality): + """The baseclass of all boolean expressions.""" + pass + +class IntArithmeticExp(ArithmeticExp): + """The AST node for integer arithmetic expressions.""" + def __init__(self, i): + """ + The initialization method. + + i -- the integer value + """ + self.i = i + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "IntArithmeticStatement(i)" + """ + return 'IntArithmeticExp(%d)' % self.i + + def eval(self, env): + """ + Evaluates the node. Returns the value. + + env -- the environment in which to evaluate the node + + returns -- an Integer + """ + return self.i + +class VarArithmeticExp(ArithmeticExp): + """The AST node for all variable arithmetic expressions.""" + def __init__(self, name): + """ + The initialization method. + + name -- the variable name + """ + self.name = name + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "VarArithmeticStatement(name)" + """ + return 'VarArithmeticExp(%s)' % self.name + + def eval(self, env): + """ + Evaluates the node. Returns the variable's value. + + env -- the environment in which to evaluate the node + + returns -- the variable's value + """ + return env.get(self.name, 0) + +class BinArithmeticExp(ArithmeticExp): + """The AST node for all binary arithmetic expressions.""" + + operators = { + '+': operator.add, + '-': operator.sub, + '*': operator.mul, + '/': operator.truediv, + '**': operator.pow, + } + + def __init__(self, op, left, right): + """ + The initialization method. + + op -- the operation to perform + left -- the left operand + right -- the right operand + """ + self.op = op + self.left = left + self.right = right + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form + "BinArithmeticStatement(operation, left, right)" + """ + return 'BinArithmeticExp(%s, %s, %s)' % (self.op, self.left, self.right) + + def eval(self, env): + """ + Evaluates the node. Evaluates the left and right operands + and performs the arithmetic epxression on them. + + env -- the environment in which to evaluate the node + + returns -- the evaluated value + """ + left_value = self.left.eval(env) + right_value = self.right.eval(env) + try: + return operators[self.op](left_value, right_value) + except KeyError: + raise RuntimeError('unknown operator: ' + self.op) + +class RelationExp(BooleanExp): + """The AST node for relational boolean expressions.""" + + operators = { + '<': operator.gt, + '<=': operator.ge, + '>': operator.lt, + '>=': operator.le, + '=': operator.eq, + '!=': operator.ne, + } + + def __init__(self, op, left, right): + """ + The initialization method. + + op -- the operation to perform + left -- the left operand + right -- the right operand + """ + self.op = op + self.left = left + self.right = right + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form + "RelationExp(operation, left, right)" + """ + return 'RelationExp(%s, %s, %s)' % (self.op, self.left, self.right) + + def eval(self, env): + """ + Evaluates the node. Evaluates the left and right operands + and performs the relational epxression on them. + + env -- the environment in which to evaluate the node + + returns -- the evaluated value + """ + left_value = self.left.eval(env) + right_value = self.right.eval(env) + try: + return operators[self.op](left_value, right_value) + except KeyError: + raise RuntimeError('unknown operator: ' + self.op) + +class AndExp(BooleanExp): + """The AST node for and expressions""" + def __init__(self, left, right): + """ + The initializer method. + + left -- the left operand + right -- the right operand + """ + self.left = left + self.right = right + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "AndExp(left, right)" + """ + return 'AndExp(%s, %s)' % (self.left, self.right) + + def eval(self, env): + """ + Evaluates the node. Evaluates the left and right operands + and performs a logical and on them. + Supports short circuiting of logic. + + env -- the environment in which to evaluate the node + + returns -- the evaluated value + """ + return self.left.eval(env) and self.right.eval(env) + +class OrExp(BooleanExp): + """The AST node for or expressions""" + def __init__(self, left, right): + """ + The initializer method. + + left -- the left operand + right -- the right operand + """ + self.left = left + self.right = right + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "OrExp(left, right)" + """ + return 'OrExp(%s, %s)' % (self.left, self.right) + + def eval(self, env): + """ + Evaluates the node. Evaluates the left and right operands + and performs a logical or on them. + Supports short circuiting of logic. + + env -- the environment in which to evaluate the node + + returns -- the evaluated value + """ + return self.left.eval(env) or self.right.eval(env) + +class NotExp(BooleanExp): + """The AST node for not expressions""" + def __init__(self, exp): + """ + The initializer method. + + exp -- the operand to negate + """ + self.exp = exp + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "NotExp(operand)" + """ + return 'NotExp(%s)' % self.exp + + def eval(self, env): + """ + Evaluates the node. Evaluates the operand and performs a + logical not on it. + + env -- the environment in which to evaluate the node + + returns -- the evaluated value + """ + return not self.exp.eval(env) + +class AssignStatement(Statement): + """The AST node for assignments""" + def __init__(self, name, rexp): + """ + The initialization method. + + name -- the variable name + rexp -- the expression that evaluates to the name + """ + self.name = name + self.rexp = rexp + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "AssignStatement(name, expression)" + """ + return 'AssignStatement(%s, %s)' % (self.name, self.rexp) + + def eval(self, env): + """ + Evaluates the node. Evaluates the statement + for the expression and saves it within the + environment (under the given name). + + env -- the environment in which to evaluate the node + """ + env[self.name] = self.rexp.eval(env) + + +class CompoundStatement(Statement): + """The AST node for compound statements""" + def __init__(self, first, second): + """ + The initialization method. + + first -- the first statement + second -- the second statement + """ + self.first = first + self.second = second + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "CompoundStatement(first, second)" + """ + return 'CompoundStatement(%s, %s)' % (self.first, self.second) + + def eval(self, env): + """ + Evaluates the node. Evaluates the statements + one after the other. + + env -- the environment in which to evaluate the node + """ + self.first.eval(env) + self.second.eval(env) + + +class IfStatement(Statement): + """The AST node for if statements""" + def __init__(self, condition, on_true, on_false): + """ + The initialization method. + + condition -- the condition that needs to be evaluated + on_true -- the statement that is to be executed when + the condition is met + on_false -- the statement that is to be executed when + the condition is not met + """ + self.condition = condition + self.on_true = on_true + self.on_false = on_false + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form "IfStatement(condition, on_true, on_false)" + """ + return 'IfStatement(%s, %s, %s)' % (self.condition, self.on_true, self.on_false) + + def eval(self, env): + """ + Evaluates the node. Evaluates the condition. + If it is met, the on_true block is executed. + If not and the on_false block exists, it is executed. + + env -- the environment in which to evaluate the node + """ + if self.condition.eval(env): + self.on_true.eval(env) + else: + if self.on_false: + self.on_false.eval(env) + + +class WhileStatement(Statement): + """The AST node for while statements""" + def __init__(self, condition, body): + """ + The initialization method. + + condition -- the condition that needs to be evaluated + body -- the statement that is to be executed while + the condition is met + """ + self.condition = condition + self.body = body + + def __repr__(self): + """ + A representation of the node for debug purposes. + + returns -- a String of the form + "WhileStatement(condition, body)" + """ + return 'WhileStatement(%s, %s)' % (self.condition, self.body) + + def eval(self, env): + """ + Evaluates the node. Evaluates the condition. + While it is met, the body block is executed. + + env -- the environment in which to evaluate the node + """ + while self.condition.eval(env): + self.body.eval(env) diff --git a/improved/parser.py b/improved/parser.py new file mode 100644 index 0000000..b4f29c1 --- /dev/null +++ b/improved/parser.py @@ -0,0 +1,130 @@ +"""The parser""" + +from functools import reduce + +from .ast import * +from .parser_combinator import * +from .tokenize import * + +ARITHMETIC_PRECEDENCE = [ + ['*', '/'], + ['+', '-'], +] + +BOOLEAN_PRECEDENCE = [ + ['and'], + ['or'], +] + +#Helper +def precedence(value_parser, precedence_levels, combine): + def op_parser(precedence_level): + return any_op_in_list(precedence_level) ^ combine + parser = value_parser * op_parser(precedence_levels[0]) + for precedence_level in precedence_levels[1:]: + parser = parser * op_parser(precedence_level) + return parser + +def process_binop(op): + return lambda l, r: BinArithmeticExp(op, l, r) + +def process_relop(parsed): + ((left, op), right) = parsed + return RelationExp(op, left, right) + +def process_logic(op): + if op == 'and': + return lambda l, r: AndExp(l, r) + elif op == 'or': + return lambda l, r: OrExp(l, r) + else: + raise RuntimeError('unknown logic operator: ' + op) + +def process_group(parsed): + ((_, p), _) = parsed + return p + +def any_op_in_list(ops): + op_parsers = [keyword(op) for op in ops] + parser = reduce(lambda l, r: l | r, op_parsers) + return parser + +#Parser +num = Tag(INT) ^ (lambda i: int(i)) +imp_id = Tag(ID) + +def keyword(kw): + return Reserved(kw, RESERVED) + +def arithmetic_group(): + return keyword('(') + Lazy(arithmetic_exp) + keyword(')') ^ process_group + +def arithmetic_value(): + return ((num ^ (lambda i: IntArithmeticExp(i))) | + (id ^ (lambda v: VarArithmeticExp(v)))) + +def arithmetic_term(): + return arithmetic_value() | arithmetic_group() + +def arithmetic_exp(): + return precedence(arithmetic_term(), + ARITHMETIC_PRECEDENCE, + process_binop) + +def boolean_not(): + return keyword('not') + Lazy(boolean_term) ^ (lambda parsed: NotExp(parsed[1])) + +def boolean_relop(): + relops = ['<', '<=', '>', '>=', '=', '!='] + return arithmetic_exp() + any_op_in_list(relops) + arithmetic_exp() ^ process_relop + +def boolean_group(): + return keyword('(') + Lazy(boolean_exp) + keyword(')') ^ process_group + +def boolean_term(): + return boolean_not() | boolean_relop() | boolean_group() + +def boolean_exp(): + return precedence(boolean_term(), + BOOLEAN_PRECEDENCE, + process_logic) + +def assign_statement(): + def internal(parsed): + ((name, _), exp) = parsed + return AssignStatement(name, exp) + return imp_id + keyword(':=') + arithmetic_exp() ^ internal + +def if_statement(): + def internal(parsed): + (((((_, condition), _), on_true), false_parsed), _) = parsed + if false_parsed: + (_, on_false) = false_parsed + else: + on_false = None + return IfStatement(condition, on_true, on_false) + return (keyword('if') + boolean_exp() + + keyword('then') + Lazy(statements) + + Opt(keyword('else') + Lazy(statements)) + + keyword('end') ^ internal) + +def while_statement(): + def internal(parsed): + ((((_, condition), _), body), _) = parsed + return WhileStatement(condition, body) + return (keyword('while') + boolean_exp() + + keyword('do') + Lazy(statements) + + keyword('end') ^ internal) + +def statement(): + return assign_statement() | if_statement() | while_statement() + +def statements(): + sep = keyword(';') ^ (lambda x: lambda l, r: CompoundStatement(l, r)) + return Exp(statement(), sep) + +def parser(): + return Phrase(statements()) + +def imp_parser(tokens): + return parser()(tokens, 0) diff --git a/improved/parser_combinator.py b/improved/parser_combinator.py index becb4a4..65899ad 100644 --- a/improved/parser_combinator.py +++ b/improved/parser_combinator.py @@ -155,7 +155,7 @@ class Concat(Parser): return Result(combined_value, right_result.pos) return None - class Alternate(Parser): +class Alternate(Parser): """The alternate combinator. Parses using either of two parsers.""" def __init__(self, left, right): """