Start working on the interpeter
authorDavid Kerkeslager <kerkeslager@gmail.com>
Mon, 23 Aug 2021 00:35:05 +0000 (20:35 -0400)
committerDavid Kerkeslager <kerkeslager@gmail.com>
Mon, 23 Aug 2021 00:35:05 +0000 (20:35 -0400)
integration_tests.py
interpreter.py [new file with mode: 0644]
main.py
requirements.txt

index b5ad321..7cc6d1a 100644 (file)
@@ -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 (file)
index 0000000..5f71e3b
--- /dev/null
@@ -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 (file)
--- 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
index 81694ce..194572c 100644 (file)
@@ -1 +1 @@
-Jinja2==2.10.1
+Jinja2==3.0.1