From d70666abc2db430300d48691046ada2395b2f0d5 Mon Sep 17 00:00:00 2001 From: David Kerkeslager Date: Sun, 22 Aug 2021 20:35:05 -0400 Subject: [PATCH] Start working on the interpeter --- integration_tests.py | 61 ++++++-------- interpreter.py | 191 +++++++++++++++++++++++++++++++++++++++++++ main.py | 1 + requirements.txt | 2 +- 4 files changed, 220 insertions(+), 35 deletions(-) create mode 100644 interpreter.py diff --git a/integration_tests.py b/integration_tests.py index b5ad321..7cc6d1a 100644 --- a/integration_tests.py +++ b/integration_tests.py @@ -11,45 +11,38 @@ class InterpreterOutputTests(unittest.TestCase): def add_example_interpreter_output_test(filename): def test(self): - try: - p = subprocess.Popen( - 'python main.py interpret {}'.format( - os.path.join('examples', filename), - ), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - actual_stdout, actual_stderr = p.communicate() + p = subprocess.Popen( + ( + 'python', + 'main.py', + 'interpret', + os.path.join('examples', filename), + ), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) - expected_stdout_path = os.path.join('examples', filename + '.stdout.txt') - - if os.path.isfile(expected_stdout_path): - with open(expected_stdout_path, 'rb') as f: - expected_stdout = f.read() - else: - expected_stdout = b'' + actual_stdout, actual_stderr = p.communicate() - expected_stderr_path = os.path.join('examples', filename + '.stderr.txt') + expected_stdout_path = os.path.join('examples', filename + '.stdout.txt') - if os.path.isfile(expected_stderr_path): - with open(expected_stderr_path, 'rb') as f: - expected_stderr = f.read() - else: - expected_stderr = b'' + if os.path.isfile(expected_stdout_path): + with open(expected_stdout_path, 'rb') as f: + expected_stdout = f.read() + else: + expected_stdout = b'' - self.assertEqual(expected_stderr, actual_stderr) + expected_stderr_path = os.path.join('examples', filename + '.stderr.txt') - # We don't clean up the C file in the finally clause because it can be useful to have in case of errors - os.remove(os.path.join('examples', filename + '.c')) + if os.path.isfile(expected_stderr_path): + with open(expected_stderr_path, 'rb') as f: + expected_stderr = f.read() + else: + expected_stderr = b'' - finally: - try: - os.remove('a.out') - except OSError: - pass + self.assertEqual(expected_stderr, actual_stderr) - setattr(InterpreterOutputTests, 'test_' + filename, test) + setattr(InterpreterOutputTests, 'test_' + filename[:-4], test) class CompilerOutputTests(unittest.TestCase): pass @@ -105,7 +98,7 @@ def add_example_compiler_output_test(filename): except OSError: pass - setattr(CompilerOutputTests, 'test_' + filename, test) + setattr(CompilerOutputTests, 'test_' + filename[:-4], test) class MemoryLeakTests(unittest.TestCase): pass @@ -160,7 +153,7 @@ def add_example_memory_leak_test(filename): except OSError: pass - setattr(MemoryLeakTests, 'test_' + filename, test) + setattr(MemoryLeakTests, 'test_' + filename[:-4], test) filenames = ( entry.name diff --git a/interpreter.py b/interpreter.py new file mode 100644 index 0000000..5f71e3b --- /dev/null +++ b/interpreter.py @@ -0,0 +1,191 @@ +import sys + +import crossplatform_ir_generation + +class Environment(object): + def __init__(self, parent): + self.symbols = {} + self.parent = parent + + def __getitem__(self, symbol): + if symbol in self.symbols: + return self.symbols[symbol] + + if self.parent is None: + raise Exception('Symbol "{}" not found'.format(symbol)) + + return self.parent[symbol] + + def __setitem__(self, symbol, value): + self.symbols[symbol] = value + +def builtin_print(*outputs): + outputs = list(outputs) + + for i in range(len(outputs)): + output = outputs[i] + + if isinstance(output, bool): + outputs[i] = str(output).lower() + + print(*outputs, end='') + +BUILTIN_ENVIRONMENT = Environment(None) +BUILTIN_ENVIRONMENT.symbols = { + 'false': False, + 'pow': lambda base, exponent: pow(base, exponent), + 'print': builtin_print, + 'true': True, +} + +def interpret(program): + environment = Environment(BUILTIN_ENVIRONMENT) + + label_indices = {} + + for i in range(len(program.entry_list)): + if isinstance(program.entry_list[i], crossplatform_ir_generation.CIRLabel): + if program.entry_list[i].label in label_indices: + raise Exception('Label already in labels') + + label_indices[program.entry_list[i].label] = i + + program_counter = label_indices['__main__'] + 1 + stack = [] + + while True: + while isinstance(program.entry_list[program_counter], crossplatform_ir_generation.CIRLabel): + program_counter += 1 + + instruction = program.entry_list[program_counter].instruction + argument = program.entry_list[program_counter].argument + + if instruction in ('add', 'idiv', 'mod', 'mul', 'sub'): + assert argument == 2 + right = stack.pop() + left = stack.pop() + assert isinstance(left, int) + assert isinstance(right, int) + + if instruction == 'add': + result = left + right + elif instruction == 'idiv': + result = left // right + elif instruction == 'mod': + result = left % right + elif instruction == 'mul': + result = left * right + elif instruction == 'sub': + result = left - right + + stack.append(result) + + elif instruction in ('gt', 'gte', 'lt', 'lte'): + assert argument == 2 + right = stack.pop() + left = stack.pop() + assert isinstance(left, int) + assert isinstance(right, int) + + if instruction == 'gt': + result = left > right + elif instruction == 'gte': + result = left >= right + elif instruction == 'lt': + result = left < right + elif instruction == 'lte': + result = left <= right + + stack.append(result) + + elif instruction in ('eq', 'neq'): + assert argument == 2 + right = stack.pop() + left = stack.pop() + + if instruction == 'eq': + result = left == right + elif instruction == 'neq': + result = left != right + + stack.append(result) + + elif instruction == 'call': + assert isinstance(argument, int) + args = [] + + f = stack.pop() + + for i in range(argument): + args.append(stack.pop()) + + args = list(reversed(args)) + + stack.append(f(*args)) + + elif instruction == 'concat': + assert argument == 2 + right = stack.pop() + left = stack.pop() + assert isinstance(left, str) + assert isinstance(right, str) + + stack.append(left + right) + + elif instruction == 'drop': + assert argument is None + stack.pop() + + elif instruction == 'end': + assert argument is None + sys.exit(0) + + elif instruction == 'get': + index = stack.pop() + assert isinstance(argument, int) + xs = stack.pop() + assert isinstance(xs, list) + stack.append(xs[index]) + + elif instruction == 'jump_if_false': + program_counter = label_indices[argument] + + elif instruction == 'list': + assert isinstance(argument, int) + + result = [] + + for i in range(argument): + result.append(stack.pop()) + + stack.append(list(reversed(result))) + + elif instruction == 'neg': + stack.append(-stack.pop()) + + elif instruction == 'pop': + assert argument.startswith('sym(') + assert argument.endswith(')') + environment[argument[4:-1]] = stack.pop() + + elif instruction == 'push': + assert argument.startswith('sym(') + assert argument.endswith(')') + stack.append(environment[argument[4:-1]]) + + elif instruction == 'push_integer': + assert isinstance(argument, int) + stack.append(argument) + + elif instruction == 'push_string': + assert argument.startswith('"') + assert argument.endswith('"') + stack.append(argument[1:-1].encode('utf-8').decode('unicode_escape')) + + else: + raise Exception('Instruction "{}" not supported (argument {}).'.format( + instruction, + argument, + )) + + program_counter += 1 diff --git a/main.py b/main.py index 44dbbc6..7b31d2c 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import conversion import crossplatform_ir_generation import desugaring import c_generation +import interpreter import normalization import optimization import parsing diff --git a/requirements.txt b/requirements.txt index 81694ce..194572c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -Jinja2==2.10.1 +Jinja2==3.0.1 -- 2.20.1