diff --git a/generators/generator.py b/generators/generator.py new file mode 100644 index 0000000..9208b1e --- /dev/null +++ b/generators/generator.py @@ -0,0 +1,26 @@ +from ground_ast import RootNode + + +class Generator: + def __init__(self, ast: RootNode, code: str, output_path: str): + self.ast = ast + self.lines: list[str] = [] + self.code = code + self.output_path = output_path + + def init(self): + pass + + def generate_node(self, node): + self.lines.append(f"; {node}\n\t") + node_type = str(type(node))[19:-2] + if not hasattr(self, f"generate_{node_type}"): + raise Exception(f"Generator has no generate method for {node_type}.") + getattr(self, f"generate_{node_type}")(node) + + def generate(self): + for statement in self.ast.statements: + self.generate_node(statement) + + with open(self.output_path + ".asm", "w") as f: + f.writelines(self.lines) \ No newline at end of file diff --git a/generators/x86_64.py b/generators/x86_64.py new file mode 100644 index 0000000..e152fbd --- /dev/null +++ b/generators/x86_64.py @@ -0,0 +1,22 @@ +from generators.generator import Generator +from ground_ast import * +from error import traceback + +class X86_64Generator(Generator): + def init(self): + self.lines.append("global _start\n\n") + self.lines.append("_start:\n\t") + self.generate() + + def generate_InstructionNode(self, node: InstructionNode): + if node.instruction == "end": + if len(node.arguments) == 0: + traceback(self.code, "TypeError", "end expects atleast 1 argument.") + elif len(node.arguments) > 1: + traceback(self.code, "TypeError", "end expects only 1 argument.") + if not isinstance(node.arguments[0], NumberNode): + traceback(self.code, "TypeError", f"end expects an integer, not {type(node.arguments[0])}") + + self.lines.append("mov rax, 60\n\t") + self.lines.append("mov rdi, " + str(node.arguments[0].value) + "\n\t") + self.lines.append("syscall\n\n\t") \ No newline at end of file diff --git a/ground_ast.py b/ground_ast.py index aeaf139..79641ab 100644 --- a/ground_ast.py +++ b/ground_ast.py @@ -52,6 +52,9 @@ class LabelRefNode: @dataclass class LineRefNode: line: int +@dataclass +class BoolNode: + value: bool def generate_ast(tokens: list[Token], code: str) -> RootNode: root_node = RootNode([]) @@ -97,6 +100,12 @@ def generate_ast(tokens: list[Token], code: str) -> RootNode: else: traceback(code, "SyntaxError", "Expected instruction, not string.") + elif token.type == TokenType.BOOL: + if current_node_type == "inst": + current_node.arguments.append(BoolNode(token.value)) + else: + traceback(code, "SyntaxError", "Expected instruction, not string.") + elif token.type == TokenType.INTEGER or token.type == TokenType.FLOAT: if current_node_type == "inst": current_node.arguments.append(NumberNode(token.value)) @@ -154,6 +163,9 @@ def generate_ast(tokens: list[Token], code: str) -> RootNode: elif token.type == TokenType.LABEL_DECLERATION: scope.statements.append(LabelDecNode(token.value)) + + elif token.type == TokenType.COMMENT: + continue elif token.type == TokenType.EOF: root_node.statements.append(current_node) diff --git a/main.py b/main.py index 6de7d9f..9b32859 100644 --- a/main.py +++ b/main.py @@ -2,19 +2,39 @@ from tokenizer import tokenize from ground_ast import generate_ast from rich import print from time import time +from generators import x86_64 +from os import system, remove +from error import traceback def main(): + in_path = "test2.grnd" + out_path = "out" + arch = "x86_64" + start = time() - file = open("test2.grnd", "r") + file = open(in_path, "r") code = file.read() file.close() tokens = tokenize(code) ast = generate_ast(tokens, code) + generator = None + + if arch == "x86_64": + generator = x86_64.X86_64Generator(ast, code, out_path) + else: + traceback(code, "fatal error", f"unkown architecture \"{arch}\"") + + generator.init() + + system(f"nasm -felf64 {out_path}.asm") + system(f"ld -o {out_path} {out_path}.o -m elf_{arch}") + remove(out_path + ".o") + remove(out_path + ".asm") + compile_time = time()-start - print(ast) - print(f"Compiled in {compile_time} seconds.") + print(f"Compiled in {round(compile_time, 1)} seconds.") if __name__ == "__main__": diff --git a/out b/out new file mode 100644 index 0000000..e63e2e0 Binary files /dev/null and b/out differ diff --git a/test2.grnd b/test2.grnd index 8f002a5..53e4707 100644 --- a/test2.grnd +++ b/test2.grnd @@ -1,12 +1 @@ -set &myName "Nathaniel" -set &myAge 10 - -fun -list !split -string &str -string &determiner -set &x 2 -set &y 5 -add $x $y &x -return $x -endfun - -@myLabel -jump %myLabel \ No newline at end of file +end 123 \ No newline at end of file diff --git a/tokenizer.py b/tokenizer.py index 1ceac55..5e82777 100644 --- a/tokenizer.py +++ b/tokenizer.py @@ -19,7 +19,8 @@ class TokenType(Enum): COMMENT = 11 # example: # hi there LINE_REFERENCE = 12 # example: %12 LABEL_REFERENCE = 13 # example: %myLabel - EOF = 14 + BOOL = 14 # example: true + EOF = 15 @dataclass class Token: @@ -397,6 +398,11 @@ def tokenize(input_string: str): TokenType.INSTRUCTION, value=current_token )) + elif current_token in ["true", "false"]: + tokens.append(Token( + TokenType.BOOL, + value=current_token == "true" + )) else: traceback(input_string, "SyntaxError", f"\"{current_token}\" isn't a valid instruction.", line, start_col, column)