added ast definition and parser

This commit is contained in:
hellerve
2015-07-08 13:07:02 +02:00
parent ed5ea34114
commit a96a5fe1f7
4 changed files with 555 additions and 1 deletions

View File

@@ -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)) [this](http://jayconrod.com/posts/37/a-simple-interpreter-from-scratch-in-python-part-1))
that is fully documented, occassionally improved and that is fully documented, occassionally improved and
embeddable into Python. embeddable into Python.
As you may notice, I am bad at documentation.

422
improved/ast.py Normal file
View File

@@ -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)

130
improved/parser.py Normal file
View File

@@ -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)

View File

@@ -155,7 +155,7 @@ class Concat(Parser):
return Result(combined_value, right_result.pos) return Result(combined_value, right_result.pos)
return None return None
class Alternate(Parser): class Alternate(Parser):
"""The alternate combinator. Parses using either of two parsers.""" """The alternate combinator. Parses using either of two parsers."""
def __init__(self, left, right): def __init__(self, left, right):
""" """