From de0377397f6adecb9e3ebc20d0d0e3a8c458be1a Mon Sep 17 00:00:00 2001 From: Nikolaj Gade Date: Mon, 19 Feb 2024 08:29:15 +0100 Subject: [PATCH] :tada: --- .gitignore | 9 ++ documentation/plthy.pdf | Bin 0 -> 13939 bytes documentation/plthy.tex | 35 +++++++ plthy | 43 ++++++++ plthy_impl/ast_nodes.py | 224 ++++++++++++++++++++++++++++++++++++++++ plthy_impl/lexer.py | 74 +++++++++++++ plthy_impl/parser.py | 118 +++++++++++++++++++++ tests/fib.plthy | 12 +++ tests/function.plthy | 4 + tests/if.plthy | 5 + tests/math.plthy | 10 ++ tests/maybe.plthy | 5 + tests/none.plthy | 2 + tests/precedence.plthy | 6 ++ tests/scope.plthy | 6 ++ tests/variable.plthy | 4 + 16 files changed, 557 insertions(+) create mode 100644 .gitignore create mode 100644 documentation/plthy.pdf create mode 100644 documentation/plthy.tex create mode 100755 plthy create mode 100644 plthy_impl/ast_nodes.py create mode 100644 plthy_impl/lexer.py create mode 100644 plthy_impl/parser.py create mode 100644 tests/fib.plthy create mode 100644 tests/function.plthy create mode 100644 tests/if.plthy create mode 100644 tests/math.plthy create mode 100644 tests/maybe.plthy create mode 100644 tests/none.plthy create mode 100644 tests/precedence.plthy create mode 100644 tests/scope.plthy create mode 100644 tests/variable.plthy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c40d955 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +**/.vscode/ +**/__pycache__/ +**/*.aux +**/*.fdb_latexmk +**/*.fls +**/*.log +**/*.out +**/*.synctex.gz +**/*.xdv \ No newline at end of file diff --git a/documentation/plthy.pdf b/documentation/plthy.pdf new file mode 100644 index 0000000000000000000000000000000000000000..abb02c20092dac4129bae721373ef53ed21ce9f1 GIT binary patch literal 13939 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 literal 0 HcmV?d00001 diff --git a/documentation/plthy.tex b/documentation/plthy.tex new file mode 100644 index 0000000..05d9301 --- /dev/null +++ b/documentation/plthy.tex @@ -0,0 +1,35 @@ +\documentclass[a4paper]{paper} +\usepackage[margin=2.5cm]{geometry} + +\begin{document} + \begin{center} + \begin{tabular}{|lcl|} + \hline + \textit{program} & $\rightarrow$ & \texttt{hello} \texttt{|} \textit{statements} \texttt{goodbye} \texttt{|} \\ \hline \hline + \textit{statements} & $\rightarrow$ & \\ \hline + \textit{statements} & $\rightarrow$ & \textit{statement} \texttt{|} \textit{statements} \\ \hline\hline + \textit{statement} & $\rightarrow$ & \\ \hline + \textit{statement} & $\rightarrow$ & \texttt{maybe} \textit{statement}\\ \hline + \textit{statement} & $\rightarrow$ & \texttt{do} \textit{command}\\ \hline + \textit{statement} & $\rightarrow$ & \texttt{[} \textit{statements} \texttt{]}\\ \hline + \textit{statement} & $\rightarrow$ & \textit{statement} \texttt{if} \textit{expression} \\ \hline + \textit{statement} & $\rightarrow$ & \textit{statement} \texttt{because} \textit{expression} \\ \hline + \textit{statement} & $\rightarrow$ & \texttt{until} \textit{expression} \textit{statement} \\ \hline + \textit{statement} & $\rightarrow$ & \textit{expression} \texttt{->} \textbf{id} \\ \hline + \textit{statement} & $\rightarrow$ & \texttt{define} \textbf{function} \texttt{<} \textbf{numeral} \texttt{>} \texttt{as} \textit{statement} \\ \hline + \textit{statement} & $\rightarrow$ & \texttt{return} \textit{expression} \\ \hline\hline + \textit{command} & $\rightarrow$ & \textbf{builtin} \texttt{<} \textit{expressions} \texttt{>} \\ \hline + \textit{command} & $\rightarrow$ & \texttt{"} \textbf{function} \texttt{" <} \textit{expressions} \texttt{>} \\ \hline\hline + \textit{expressions} & $\rightarrow$ & \\ \hline + \textit{expressions} & $\rightarrow$ & \textit{expression} \texttt{;} \textit{expressions} \\ \hline\hline + \textit{expression} & $\rightarrow$ & \textbf{string} \\ \hline + \textit{expression} & $\rightarrow$ & \textbf{numeral} \\ \hline + \textit{expression} & $\rightarrow$ & \textbf{boolean} \\ \hline + \textit{expression} & $\rightarrow$ & \texttt{\{} \textit{expressions} \texttt{\}} \\ \hline + \textit{expression} & $\rightarrow$ & \texttt{(} \textit{expression} \texttt{)} \\ \hline + \textit{expression} & $\rightarrow$ & \textit{expression} \textbf{binop} \textit{expression} \\ \hline + \textit{expression} & $\rightarrow$ & \texttt{variable} \textbf{id}\\ \hline + \textit{expression} & $\rightarrow$ & \textit{statement} \\ \hline + \end{tabular} + \end{center} +\end{document} \ No newline at end of file diff --git a/plthy b/plthy new file mode 100755 index 0000000..d036cea --- /dev/null +++ b/plthy @@ -0,0 +1,43 @@ +#! /home/nikolaj/.pyenv/shims/python +""" +Usage: + plthy (-h| --help) + plthy (-i| -c) FILE + +Options: + -h --help Print this help screen + -i Run the interpreter + -c Run the compiler + FILE The file to compile/interpret +""" +from docopt import docopt + +from plthy_impl.lexer import Lexer +from plthy_impl.parser import Parser +from plthy_impl.ast_nodes import Program + +def main(): + args = docopt(__doc__) + file_path = args["FILE"] + with open(file_path, "r", encoding="utf-8") as file_pointer: + program_text = file_pointer.read() + "\n" + + lexer = Lexer().get_lexer() + parser = Parser() + + tokens = lexer.lex(program_text) + # for t in tokens: + # print(t) + + program = parser.parse(tokens) + + if isinstance(program, Program): + if args["-i"]: + program.eval() + else: + raise Exception("Compiler not implemented") + else: + raise Exception("Output not of type 'Program'", type(program)) + +if __name__ == "__main__": + main() diff --git a/plthy_impl/ast_nodes.py b/plthy_impl/ast_nodes.py new file mode 100644 index 0000000..acc2a45 --- /dev/null +++ b/plthy_impl/ast_nodes.py @@ -0,0 +1,224 @@ +import random + +from rply.token import BaseBox + +def rep_join(l): + format_string = ',\n'.join( + [repr(i) if not isinstance(i, str) else i for i in l] + ).replace('\n', '\n ') + + if format_string != "": + format_string = f"\n {format_string}\n" + + return format_string + +class Exp(BaseBox): + def eval(self, vtable, ftable): + return vtable, ftable, None + +class ExpNumeral(Exp): + def __init__(self, value: int): + self.value = value + + def eval(self, vtable, ftable): + return vtable, ftable, self.value + +class ExpString(Exp): + def __init__(self, value: str): + self.value = value + + def eval(self, vtable, ftable): + return vtable, ftable, self.value + +class ExpVariable(Exp): + def __init__(self, variable_id: str): + self.variable_name = variable_id + + def eval(self, vtable, ftable): + return vtable, ftable, vtable[self.variable_name] + +class ExpABinop(Exp): + def __init__(self,op: str,exp1: Exp, exp2: Exp): + self.op = op + self.exp1 = exp1 + self.exp2 = exp2 + + def eval(self, vtable, ftable): + if self.op == "+": + vtable, ftable, r1 = self.exp1.eval(vtable, ftable) + vtable, ftable, r2 = self.exp2.eval(vtable, ftable) + return vtable, ftable, r1+r2 + elif self.op == "-": + vtable, ftable, r1 = self.exp1.eval(vtable, ftable) + vtable, ftable, r2 = self.exp2.eval(vtable, ftable) + return vtable, ftable, r1-r2 + elif self.op == "*": + vtable, ftable, r1 = self.exp1.eval(vtable, ftable) + vtable, ftable, r2 = self.exp2.eval(vtable, ftable) + return vtable, ftable, r1*r2 + elif self.op == "/": + vtable, ftable, r1 = self.exp1.eval(vtable, ftable) + vtable, ftable, r2 = self.exp2.eval(vtable, ftable) + return vtable, ftable, r1/r2 + elif self.op == "=": + vtable, ftable, r1 = self.exp1.eval(vtable, ftable) + vtable, ftable, r2 = self.exp2.eval(vtable, ftable) + return vtable, ftable, r1 == r2 + else: + raise Exception(f"Binop {self.op} not implemented") + +class ExpArg(Exp): + def __init__(self, arg: int): + self.arg = arg + + def eval(self, vtable, ftable): + n = vtable["#ARGS"] - self.arg + + return vtable, ftable, vtable[f"#{n}"] + +class Command(BaseBox): + def eval(self, vtable, ftable): + return vtable, ftable, None + +class Builtin(Command): + def __init__(self, builtin: str, args: list[Exp]): + self.builtin = builtin + self.args = args + + def eval(self, vtable, ftable): + if self.builtin == "print": + prints = [] + for arg in self.args: + vtable, ftable, result = arg.eval(vtable, ftable) + prints.append(str(result)) + + print(" ".join(prints)) + + return vtable, ftable, None + else: + raise Exception(f"Unknown builtin {self.builtin}") + +class Do(BaseBox): + def __init__(self, command: Command): + self.command = command + + def eval(self, vtable, ftable): + return self.command.eval(vtable, ftable) + +class Statement(BaseBox): + def eval(self, vtable, ftable): + return vtable, ftable, None + +class StatementSet(Statement): + def __init__(self, expression: Exp, variable: str): + self.expression = expression + self.variable_name = variable + + def eval(self, vtable, ftable): + vtable, ftable, result = self.expression.eval( + vtable, ftable + ) + vtable[self.variable_name] = result + return vtable, ftable, None + +class StatementDefine(Statement): + def __init__(self, function_name : str, parameters: int, statement: Statement): + self.function_name = function_name + self.statement = statement + self.parameters = parameters + + def eval(self, vtable, ftable): + ftable[self.function_name] = (self.parameters, self.statement) + return vtable, ftable, None + +class StatementReturn(Statement): + def __init__(self, expression: Exp): + self.value = expression + + def eval(self, vtable, ftable): + vtable, ftable, result = self.value.eval(vtable, ftable) + vtable["#return"] = result + return vtable, ftable, result + +class Scope(Statement): + def __init__(self, statements: list[Statement]): + self.statements = statements + + def eval(self, vtable, ftable): + result = None + for statement in self.statements: + vtable, ftable, _ = statement.eval(vtable, ftable) + if "#return" in vtable: + result = vtable["#return"] + del vtable["#return"] + break + + return vtable, ftable, result + +class Call(Statement): + def __init__(self, function_name: str, arguments: list[Exp]): + self.function_name = function_name + self.arguments = arguments + + def eval(self, vtable, ftable): + assert len(self.arguments) == ftable[self.function_name][0] + + args = [] + for argument in self.arguments: + vtable, ftable, result = argument.eval(vtable,ftable) + args.append(result) + + n = vtable["#ARGS"]-1 + + for i, arg in enumerate(args): + vtable[f"#{n+len(args)-i}"] = arg + + vtable["#ARGS"] += len(args) + + vtable, ftable, result = ftable[self.function_name][1].eval(vtable, ftable) + + if "#return" in vtable: + del vtable["#return"] + + vtable["#ARGS"] -= len(args) + + return vtable, ftable, result + +class StatementIf(Statement): + def __init__(self, statement: Statement, condition): + self.statement = statement + self.condition = condition + + def eval(self, vtable, ftable): + vtable, ftable, result = self.condition.eval(vtable, ftable) + if result: + return self.statement.eval(vtable, ftable) + else: + return vtable, ftable, None + +class Maybe(BaseBox): + def __init__(self, statement : Statement): + self.statement = statement + + def eval(self, vtable, ftable): + if random.randint(0,1) == 1: + return self.statement.eval(vtable,ftable) + else: + return vtable, ftable, None + +class Program(BaseBox): + def __init__(self, statements: list[Statement]) -> None: + self.statements = statements + + def __repr__(self) -> str: + statements_string = f"statements([{rep_join(self.statements)}])" + return statements_string + + def eval(self, *_): + vtable = {"#ARGS": 0} + ftable = {} + + for statement in self.statements: + vtable, ftable, _ = statement.eval(vtable, ftable) + if "#return" in vtable: + break diff --git a/plthy_impl/lexer.py b/plthy_impl/lexer.py new file mode 100644 index 0000000..e68666e --- /dev/null +++ b/plthy_impl/lexer.py @@ -0,0 +1,74 @@ +from string import ascii_letters, digits +from rply import LexerGenerator + +VALID_CHARACTERS = ascii_letters+"_"+digits + +KEYWORD_TOKENS = [("KEYWORD_"+i.upper(), i) for i in [ + "hello", + "goodbye", + "maybe", + "do", + "if", + "because", + "until", + "define", + "as", + "variable", + "return", + "argument" +]] + +BUILTIN_TOKENS = [("BUILTIN", i) for i in [ + "print" +]] + +DATA_TOKENS = [ + ("DATA_STRING", r"\'.*?\'"), + ("DATA_NUMERAL", r"\d+(\.\d+)?") +] + +SYMBOL_TOKENS = [ + ("SYMBOL_SET", r"\-\>"), + ("SYMBOL_LPARENS", r"\("), + ("SYMBOL_RPARENS", r"\)"), + ("SYMBOL_LBRACKET", r"\["), + ("SYMBOL_RBRACKET", r"\]"), + ("SYMBOL_LCURL", r"\{"), + ("SYMBOL_RCURL", r"\}"), + ("SYMBOL_PLUS", r"\+"), + ("SYMBOL_MINUS", r"\-"), + ("SYMBOL_TIMES", r"\*"), + ("SYMBOL_DIVIDE", r"\/"), + ("SYMBOL_COMMA", r"\,"), + ("SYMBOL_COLON", r"\:"), + ("SYMBOL_SEMICOLON", r"\;"), + ("SYMBOL_PIPE", r"\|"), + ("SYMBOL_QUOTE", r"\""), + ("SYMBOL_LT", r"\<"), + ("SYMBOL_GT", r"\>"), + ("SYMBOL_EQUALS", r"\="), + ("SYMBOL_DOLLAR", r"\$") +] + +ALL_TOKENS = ( + KEYWORD_TOKENS + + BUILTIN_TOKENS + + DATA_TOKENS + + SYMBOL_TOKENS + + [("ARG", r"\#\d+")] + + [("ID", f"[{VALID_CHARACTERS}]+")] +) + + +class Lexer(): + def __init__(self): + self.lexer = LexerGenerator() + + def _add_tokens(self): + for token in ALL_TOKENS: + self.lexer.add(*token) + self.lexer.ignore(r"[\s\n]+|//.*\n") + + def get_lexer(self): + self._add_tokens() + return self.lexer.build() diff --git a/plthy_impl/parser.py b/plthy_impl/parser.py new file mode 100644 index 0000000..908c6db --- /dev/null +++ b/plthy_impl/parser.py @@ -0,0 +1,118 @@ +from rply import ParserGenerator + +from plthy_impl.lexer import ALL_TOKENS +from plthy_impl import ast_nodes + +class Parser(): + def __init__(self): + self.pg = ParserGenerator( + [i[0] for i in ALL_TOKENS], + precedence=[ + ('left', ["KEYWORD_MAYBE", "KEYWORD_RETURN"]), + ('left', ["KEYWORD_IF", "KEYWORD_DEFINE", "KEYWORD_AS"]), + ('left', ["KEYWORD_DO", "BUILTIN"]), + ('left', ["SYMBOL_EQUALS", "SYMBOL_SET"]), + ('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]), + ('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]) + ] + ) + + def parse(self, token_input) -> ast_nodes.BaseBox: + # Top-level program stuff + @self.pg.production('program : KEYWORD_HELLO SYMBOL_PIPE statements KEYWORD_GOODBYE SYMBOL_PIPE') + def program(tokens): + return ast_nodes.Program(tokens[2]) + + ## statements ## + @self.pg.production('statements : ') + def statements_none(_): + return [] + + @self.pg.production('statements : statement SYMBOL_PIPE statements') + def statements(tokens): + return [tokens[0]] + tokens[2] + + ## statement ## + @self.pg.production('statement : SYMBOL_DOLLAR expression SYMBOL_SET ID', precedence="SYMBOL_SET") + def statement_set(tokens): + return ast_nodes.StatementSet(tokens[1], tokens[3].value) + + @self.pg.production('statement : KEYWORD_DO command') + def statement_do(tokens): + return ast_nodes.Do(tokens[1]) + + @self.pg.production('statement : KEYWORD_MAYBE statement') + def statement_maybe(tokens): + return ast_nodes.Maybe(tokens[1]) + + @self.pg.production('statement : statement KEYWORD_IF expression') + def statement_if(tokens): + return ast_nodes.StatementIf(tokens[0], tokens[2]) + + @self.pg.production('statement : KEYWORD_DEFINE ID SYMBOL_LT DATA_NUMERAL SYMBOL_GT KEYWORD_AS statement', precedence="KEYWORD_DEFINE") + def statement_define(tokens): + return ast_nodes.StatementDefine(tokens[1].value, int(tokens[3].value), tokens[6]) + + @self.pg.production('statement : KEYWORD_RETURN expression') + def statement_return(tokens): + return ast_nodes.StatementReturn(tokens[1]) + + @self.pg.production('statement : SYMBOL_LBRACKET statements SYMBOL_RBRACKET') + def statement_scope(tokens): + return ast_nodes.Scope(tokens[1]) + + ## command ## + @self.pg.production('command : BUILTIN SYMBOL_LT expressions SYMBOL_GT') + def command_builtin(tokens): + return ast_nodes.Builtin(tokens[0].value, tokens[2]) + + @self.pg.production('command : SYMBOL_QUOTE ID SYMBOL_QUOTE SYMBOL_LT expressions SYMBOL_GT') + def command_call(tokens): + return ast_nodes.Call(tokens[1].value,tokens[4]) + + ## expressions ## + @self.pg.production('expressions : ') + def expressions_none(_): + return [] + + @self.pg.production('expressions : expression SYMBOL_SEMICOLON expressions ') + def expressions(tokens): + return [tokens[0]] + tokens[2] + + ## expression ## + @self.pg.production('expression : DATA_NUMERAL') + def exp_numeral(tokens): + return ast_nodes.ExpNumeral(float(tokens[0].value)) + + @self.pg.production('expression : DATA_STRING') + def exp_string(tokens): + return ast_nodes.ExpString(tokens[0].value[1:-1]) + + @self.pg.production('expression : statement', precedence="KEYWORD_IF") + def exp_statement(tokens): + return(tokens[0]) + + @self.pg.production('expression : KEYWORD_VARIABLE ID') + def exp_variable(tokens): + return ast_nodes.ExpVariable(tokens[1].value) + + @self.pg.production('expression : expression SYMBOL_PLUS expression') + @self.pg.production('expression : expression SYMBOL_MINUS expression') + @self.pg.production('expression : expression SYMBOL_TIMES expression') + @self.pg.production('expression : expression SYMBOL_DIVIDE expression') + @self.pg.production('expression : expression SYMBOL_EQUALS expression') + def exp_a_binop(tokens): + return ast_nodes.ExpABinop(tokens[1].value,tokens[0],tokens[2]) + + @self.pg.production('expression : KEYWORD_ARGUMENT ARG') + def exp_arg(tokens): + return ast_nodes.ExpArg(int(tokens[1].value[1:])) + + ## Error Handling ## + @self.pg.error + def error_handle(token): + raise Exception(token.name, token.value, token.source_pos) + + ## Finish ## + parser = self.pg.build() + return parser.parse(token_input) diff --git a/tests/fib.plthy b/tests/fib.plthy new file mode 100644 index 0000000..3fd522a --- /dev/null +++ b/tests/fib.plthy @@ -0,0 +1,12 @@ +hello| +define fib<1> as [ + return 1 if argument #1 = 1| + return 1 if argument #1 = 2| + return do "fib" + do "fib"| +]| +do print;>| +do print;>| +do print;>| +do print;>| +do print;>| +goodbye| \ No newline at end of file diff --git a/tests/function.plthy b/tests/function.plthy new file mode 100644 index 0000000..405d73e --- /dev/null +++ b/tests/function.plthy @@ -0,0 +1,4 @@ +hello| +define five<0> as return 5| +do print;>| +goodbye| \ No newline at end of file diff --git a/tests/if.plthy b/tests/if.plthy new file mode 100644 index 0000000..b826fe4 --- /dev/null +++ b/tests/if.plthy @@ -0,0 +1,5 @@ +hello| +$2 -> x| +do print<'a';> if variable x = 1| +do print<'b';> if variable x = 2| +goodbye| \ No newline at end of file diff --git a/tests/math.plthy b/tests/math.plthy new file mode 100644 index 0000000..2e1cd5a --- /dev/null +++ b/tests/math.plthy @@ -0,0 +1,10 @@ +hello| +$1 -> x| +$2 -> y| +$5 -> z| +$variable x+variable y -> z| // 3 +$5-variable z -> z| // 2 +$2*variable z -> x| // 4 +$variable x/variable y -> y| // 2 +do print| +goodbye| \ No newline at end of file diff --git a/tests/maybe.plthy b/tests/maybe.plthy new file mode 100644 index 0000000..ab547bd --- /dev/null +++ b/tests/maybe.plthy @@ -0,0 +1,5 @@ +hello| +$1 -> x| +maybe $variable x + 1 -> x| +do print| +goodbye| \ No newline at end of file diff --git a/tests/none.plthy b/tests/none.plthy new file mode 100644 index 0000000..f749478 --- /dev/null +++ b/tests/none.plthy @@ -0,0 +1,2 @@ +hello| +goodbye| \ No newline at end of file diff --git a/tests/precedence.plthy b/tests/precedence.plthy new file mode 100644 index 0000000..34c6ade --- /dev/null +++ b/tests/precedence.plthy @@ -0,0 +1,6 @@ +hello| +$ 1 + 2 * 3 -> x| +do print| +$ 5 -> y if variable x = 7| +do print| +goodbye| \ No newline at end of file diff --git a/tests/scope.plthy b/tests/scope.plthy new file mode 100644 index 0000000..a17f1a2 --- /dev/null +++ b/tests/scope.plthy @@ -0,0 +1,6 @@ +hello| +[ + $5 -> x| + do print| +]| +goodbye| \ No newline at end of file diff --git a/tests/variable.plthy b/tests/variable.plthy new file mode 100644 index 0000000..b4e1676 --- /dev/null +++ b/tests/variable.plthy @@ -0,0 +1,4 @@ +hello| +$2 -> x| +do print| +goodbye| \ No newline at end of file