From facead092f56d42357276758931e6c9c361215e0 Mon Sep 17 00:00:00 2001 From: Andrew Segavac Date: Sat, 20 Feb 2021 19:58:56 -0700 Subject: [PATCH] testing all of this in python --- Dockerfile-python | 3 + boring-test.bl | 5 ++ boring/interpret.py | 75 +++++++++++++++++++ boring/parse.py | 177 ++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 6 ++ setup.py | 12 +++ 6 files changed, 278 insertions(+) create mode 100644 Dockerfile-python create mode 100644 boring-test.bl create mode 100644 boring/interpret.py create mode 100644 boring/parse.py create mode 100644 setup.py diff --git a/Dockerfile-python b/Dockerfile-python new file mode 100644 index 0000000..6ff2c5a --- /dev/null +++ b/Dockerfile-python @@ -0,0 +1,3 @@ +FROM python:3.9 + +RUN pip install lark diff --git a/boring-test.bl b/boring-test.bl new file mode 100644 index 0000000..2131498 --- /dev/null +++ b/boring-test.bl @@ -0,0 +1,5 @@ +fn add(a, b) { a + b } +fn subtract(a, b) { a - b } +fn main() { + add(10, subtract(9, 1)) +} diff --git a/boring/interpret.py b/boring/interpret.py new file mode 100644 index 0000000..8204571 --- /dev/null +++ b/boring/interpret.py @@ -0,0 +1,75 @@ +import sys +import copy +from boring import parse +from dataclasses import dataclass + + +@dataclass +class Environment: + identifiers: dict + + +class Interpreter: + def handle_identifier(self, env, identifier): + return env.identifiers[identifier.name] + + def handle_literal_int(self, env, literal_int): + return literal_int.value + + def handle_function_call(self, env, function_call): + new_env = copy.deepcopy(env) + function_definition = new_env.identifiers[function_call.name.name] + assert len(function_definition.arguments) == len(function_call.arguments) + + for i, argument in enumerate(function_definition.arguments): + new_env.identifiers[argument.name.name] = self.handle_expression(env, function_call.arguments[i]) + return self.handle_block(new_env, function_definition.block) + + + def handle_operation(self, env, operation): + if operation.op == parse.Operator.plus: + return self.handle_expression(env, operation.left) + self.handle_expression(env, operation.right) + elif operation.op == parse.Operator.minus: + return self.handle_expression(env, operation.left) - self.handle_expression(env, operation.right) + elif operation.op == parse.Operator.mult: + return self.handle_expression(env, operation.left) * self.handle_expression(env, operation.right) + elif operation.op == parse.Operator.div: + return self.handle_expression(env, operation.left) / self.handle_expression(env, operation.right) + + def handle_expression(self, env, expression): + if type(expression.expression) == parse.LiteralInt: + return self.handle_literal_int(env, expression.expression) + elif type(expression.expression) == parse.FunctionCall: + return self.handle_function_call(env, expression.expression) + elif type(expression.expression) == parse.Identifier: + return self.handle_identifier(env, expression.expression) + elif type(expression.expression) == parse.Operation: + return self.handle_operation(env, expression.expression) + elif type(expression.expression) == parse.Expression: + return self.handle_expression(env, expression.expression) + else: + raise Exception(f"unexpected type: {type(expression.expression)}") + + def handle_block(self, env, block): + return self.handle_expression(env, block.expression) + + def run(self, module): + env = Environment(identifiers={}) + for function in module.functions: + env.identifiers[function.name.name] = function + + if 'main' not in env.identifiers: + raise Exception("must have main function") + + return self.handle_function_call(env, parse.FunctionCall(name=parse.Identifier("main"), arguments=[])) + + +if __name__ == '__main__': + with open(sys.argv[1]) as f: + tree = parse.boring_parser.parse(f.read()) + # print(tree) + ast = parse.TreeToBoring().transform(tree) + print(ast) + result = Interpreter().run(ast) + print(result) + exit(result) diff --git a/boring/parse.py b/boring/parse.py new file mode 100644 index 0000000..74f95fb --- /dev/null +++ b/boring/parse.py @@ -0,0 +1,177 @@ +import sys +import enum +from typing import Union +from dataclasses import dataclass, field +from lark import Lark, Transformer + + +@dataclass +class Identifier: + name: str + + +class Operator(enum.Enum): + mult = "mult" + div = "div" + plus = "plus" + minus = "minus" + + +@dataclass +class LiteralInt: + value: int + + +@dataclass +class FunctionCall: + name: Identifier + arguments: list['Expression'] = field(default_factory=list) + + +@dataclass +class Operation: + left: 'Expression' + op: Operator + right: 'Expression' + + +@dataclass +class Expression: + expression: Union[LiteralInt,FunctionCall,Identifier,Operation,'Expression'] + + +@dataclass +class Block: + expression: Expression + + +@dataclass +class VariableDeclaration: + name: Identifier + + +@dataclass +class Function: + name: Identifier + arguments: list[VariableDeclaration] + block: Block + + +@dataclass +class Module: + functions: Function + + + +boring_grammar = r""" + plus : "+" + minus : "-" + mult : "*" + div : "/" + + literal_int: SIGNED_NUMBER + identifier : NAME + function_call : identifier "(" [expression ("," expression)*] ")" + + add_expression : expression plus factor + sub_expression : expression minus factor + mult_expression : expression mult term + div_expression : expression div term + + expression : add_expression + | sub_expression + | factor + + factor : mult_expression + | div_expression + | term + + term : literal_int + | identifier + | function_call + | "(" expression ")" + + block : "{" expression "}" + + variable_declaration : identifier + + function : "fn" identifier "(" [variable_declaration ("," variable_declaration)*] ")" block + + module : (function)* + + %import common.CNAME -> NAME + %import common.SIGNED_NUMBER + %import common.WS + %ignore WS + """ + +class TreeToBoring(Transformer): + def plus(self, p): + return Operator.plus + + def minus(self, m): + return Operator.minus + + def mult(self, m): + return Operator.mult + + def div(self, d): + return Operator.div + + def literal_int(self, n): + (n,) = n + return LiteralInt(value=int(n)) + + def identifier(self, i): + (i,) = i + return Identifier(name=str(i)) + + def function_call(self, call): + return FunctionCall(name=call[0], arguments=call[1:]) + + def add_expression(self, ae): + return Operation(left=ae[0], op=ae[1], right=ae[2]) + + def sub_expression(self, se): + return Operation(left=se[0], op=se[1], right=se[2]) + + def mult_expression(self, se): + return Operation(left=se[0], op=se[1], right=se[2]) + + def div_expression(self, se): + return Operation(left=se[0], op=se[1], right=se[2]) + + def expression(self, exp): + (exp,) = exp + return Expression(expression=exp) + + def factor(self, factor): + (factor,) = factor + return Expression(factor) + + def term(self, term): + (term,) = term + return Expression(term) + + def block(self, block): + (block,) = block + return Block(expression=block) + + def variable_declaration(self, identifier): + (identifier,) = identifier + return VariableDeclaration(name=identifier) + + def function(self, function): + return Function(name=function[0], arguments=function[1:-1], block=function[-1]) + + def module(self, functions): + return Module(functions=functions) + + +boring_parser = Lark(boring_grammar, start='module', lexer='standard') + +if __name__ == '__main__': + with open(sys.argv[1]) as f: + tree = boring_parser.parse(f.read()) + print(tree) + print(TreeToBoring().transform(tree)) diff --git a/docker-compose.yml b/docker-compose.yml index 74077fd..a929423 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,3 +4,9 @@ services: build: . volumes: - .:/code/ + boring-python: + build: + context: . + dockerfile: Dockerfile-python + volumes: + - .:/code/ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..284449b --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +from distutils.core import setup +from setuptools import find_packages + +setup(name='boring-lang', + version='0.0.1', + description='Boring programming language', + author='Andrew Segavac', + author_email='andrew@eunomia.io', + packages=find_packages(), + )