Welcome, guest | Sign In | My Account | Store | Cart
class Expression:

    def __init__(self):
        raise NotImplementedError()
    
    def Evaluate(self, dictionary):
        raise NotImplementedError()

    # NEW code
    def __repr__(self):
        klass = self.__class__.__name__
        private = '_{0}__'.format(klass)
        args = []
        for name in self.__dict__:
            if name.startswith(private):
                value = self.__dict__[name]
                name = name[len(private):]
                args.append('{0}={1}'.format(name, repr(value)))
        return '{0}({1})'.format(klass, ', '.join(args))
    # END code

class Constant(Expression):

    def __init__(self, value):
        self.__value = value

    def Evaluate(self, dictionary):
        return self.__value

class Variable(Expression):

    def __init__(self, name):
        self.__name = name

    def Evaluate(self, dictionary):
        if self.__name not in dictionary:
            raise Exception('Unknown variable: ' + self.__name)
        return dictionary[self.__name]

class Operation(Expression):

    def __init__(self, left, op, right):
        self.__left = left
        self.__op = op
        self.__right = right

    def Evaluate(self, dictionary):
        # NEW code
        if self.__op == '=':
            assert isinstance(self.__left, Variable), 'Must Assign to Variable'
            name = self.__left._Variable__name
            value = self.__right.Evaluate(dictionary)
            dictionary[name] = value
            return value
        # END code
        x = self.__left.Evaluate(dictionary)
        y = self.__right.Evaluate(dictionary)
        if self.__op == '+':
            return x + y
        if self.__op == '-':
            return x - y
        if self.__op == '*':
            return x * y
        if self.__op == '/':
            return x / y
        # NEW code
        if self.__op == '//':
            return x // y
        if self.__op == '\\':
            return y / x
        if self.__op == '%':
            return x % y
        if self.__op in ('**', '^'):
            return x ** y
        if self.__op in ('and', '&&', '&'):
            return x and y
        if self.__op in ('or', '||', '|'):
            return x or y
        if self.__op == '==':
            return float(x == y)
        if self.__op == '!=':
            return float(x != y)
        if self.__op == '>':
            return float(x > y)
        if self.__op == '<':
            return float(x < y)
        if self.__op in ('>=', '=>'):
            return float(x >= y)
        if self.__op in ('<=', '=<'):
            return float(x <= y)
        # END code
        raise Exception('Unknown operator: ' + self.__op)

# NEW code
class Print(Expression):

    def __init__(self, expression):
        self.__expression = expression

    def Evaluate(self, dictionary):
        value = self.__expression.Evaluate(dictionary)
        print(value)
        return value
# END code

################################################################################

def run(string, local):
    # fix string for compatibility on all computer platforms
    string = string.replace('\r\n', '\n').replace('\r', '\n')
    lines = tokenize(string)
    build_operations(lines)
    evaluate(lines, local)

def tokenize(string):
    lines = []
    # replace ';' with line separators
    string = string.replace(';', '\n')
    # the string will be evaluate line-by-line
    for line in string.split('\n'):
        tokens = []
        # ignore empty lines and comments
        if not line or line[0] == '#':
            continue
        # tokens are separated by white-space
        for token in line.split():
            # operations are processed later
            if token in ('=', '+', '-', '*', '/', '//', '\\', '%',
                         '**', '^', 'and', '&&', '&', 'or', '||', '|',
                         '==', '!=', '>', '<', '>=', '=>', '<=', '=<'):
                tokens.append(token)
            else:
                try:
                    # the token is a constant if it can be converted to a float
                    tokens.append(Constant(float(token)))
                except:
                    # ... otherwise we assume that it is a variable
                    tokens.append(Variable(token))
        lines.append(tokens)
    return lines

def build_operations(lines):
    # now we work on sorting through operations
    for line_index, line in enumerate(lines):
        # assignment is optional on a line
        if '=' in line:
            # split on '=' so each section can be processed
            tokens = split(line)
            # single variables must be on the left of '='
            for section in tokens[:-1]:
                assert len(section) == 1, 'Must Have Single Token'
                assert isinstance(section[0], Variable), 'Must Assign to Variable'
            # construct an operation from the last tokens
            tokens[-1] = flatten(tokens[-1])
            # create as many assignment operations as needed
            op = Operation(tokens[-2][0], '=', tokens[-1])
            for token_index in range(len(tokens) - 3, -1, -1):
                op = Operation(tokens[token_index][0], '=', op)
            # replace the line with the final operation
            lines[line_index] = op
        else:
            # no assignment? assume evaluation and printing
            op = flatten(line)
            lines[line_index] = Print(op)
            
def split(line):
    # split the tokens in the line on '='
    tokens = []
    while '=' in line:
        index = line.index('=')
        tokens.append(line[:index])
        line = line[index+1:]
    return tokens + [line]

def flatten(tokens):
    # check for odd number of tokens
    assert len(tokens) % 2 == 1, 'Must Have Odd Number of Tokens'
    toggle = True
    # check the token construction sequence
    for token in tokens:
        if toggle:
            assert isinstance(token, (Constant, Variable)), 'Must Have Constant or Variable'
        else:
            assert isinstance(token, str), 'Must Have Operation'
        toggle = not toggle
    # if there is only one token, it does not need to be flattened
    if len(tokens) == 1:
        return tokens[0]
    # construct the needed operations starting from the beginning
    op = Operation(*tokens[:3])
    for index in range(3, len(tokens), 2):
        op = Operation(op, tokens[index], tokens[index+1])
    return op

def evaluate(lines, local):
    # evaluate the lines in order with the local dictionary
    for line in lines:
        local['_'] = line.Evaluate(local)

################################################################################

def test():
    import sys
    local = {}
    while True:
        try:
            run(input('>>> '), local)
        except EOFError:
            return
        except Exception as err:
            sys.stderr.write(err.args[0] + '\n')

if __name__ == '__main__':
    test()

History

  • revision 4 (14 years ago)
  • previous revisions are not available