testing all of this in python

This commit is contained in:
Andrew Segavac
2021-02-20 19:58:56 -07:00
parent 9db464a726
commit facead092f
6 changed files with 278 additions and 0 deletions

3
Dockerfile-python Normal file
View File

@@ -0,0 +1,3 @@
FROM python:3.9
RUN pip install lark

5
boring-test.bl Normal file
View File

@@ -0,0 +1,5 @@
fn add(a, b) { a + b }
fn subtract(a, b) { a - b }
fn main() {
add(10, subtract(9, 1))
}

75
boring/interpret.py Normal file
View File

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

177
boring/parse.py Normal file
View File

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

View File

@@ -4,3 +4,9 @@ services:
build: .
volumes:
- .:/code/
boring-python:
build:
context: .
dockerfile: Dockerfile-python
volumes:
- .:/code/

12
setup.py Normal file
View File

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