diff --git a/AST.py b/AST.py index 8600dbb..94a0084 100644 --- a/AST.py +++ b/AST.py @@ -21,6 +21,7 @@ class NodeType(Enum): # Expressions InfixExpression = "InfixExpression" CallExpression = "CallExpression" + PrefixExpression = "PrefixExpression" # Literals IntegerLiteral = "IntegerLiteral" @@ -221,8 +222,9 @@ class FunctionStatement(Statement): } class ReassignStatement(Statement): - def __init__(self, ident: Expression = None, right_value: Expression = None) -> None: + def __init__(self, ident: Expression = None, operator: str = "", right_value: Expression = None) -> None: self.ident = ident + self.operator = operator self.right_value = right_value def type(self) -> NodeType: @@ -232,6 +234,7 @@ class ReassignStatement(Statement): return { "type": self.type().value, "ident": self.ident.json(), + "operator": self.operator, "right_value": self.right_value.json() } @@ -342,4 +345,19 @@ class CallExpression(Expression): "function": self.function.json(), "arguments": [arg.json() for arg in self.arguments] } + +class PrefixExpression(Expression): + def __init__(self, operator: str, right_node: Expression = None) -> None: + self.operator = operator + self.right_node = right_node + + def type(self) -> NodeType: + return NodeType.PrefixExpression + + def json(self) -> dict: + return { + "type": self.type().value, + "operator": self.operator, + "right_node": self.right_node.json() + } # endregion \ No newline at end of file diff --git a/README.md b/README.md index d7962a0..33a544e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,71 @@ # Plasma -The Plasma programming language. \ No newline at end of file +The Plasma programming language. + +# Syntax Guide +## Functions +All your logic must be contained in the "main" function, which looks like this: +```cpp +main = Func(): Int { + +} +``` +This is how you define functions. In this example, "main" is the name of the function, and `Int` is the return type. My function takes no arguments. +If I wanted to make a function that takes arguments, it might look like this: +```cpp +add = Func(number1: Int, number2: Int): Int { + return number1 + number2 +} +``` +This is a function that adds two numbers together, here's the same function in Python: +```py +def add(number1: int, number2: int) -> int: + return number1 + number2 +``` + +### Calling Functions +To call a function, you type a dollar sign (the $ symbol), then the name of the function, and then you pass any of your arguments inbetween paranthesis, like this: +```cpp +add = Func(number1: Int, number2: Int): Int { + return number1 + number2 +} +... +later in my code... +... +$add(9, 10); +``` + +Here's a "Hello, World!" program in Plasma: +```cpp +main = Func(): Int { + $print("Hello, World!\n"); +} +``` +`print` is a builtin function, it's defined in every piece of code you write. It has the same formatting as the `printf` function in C, so if I wanted to print an integer I could do it like so: +```cpp +$print("%i\n", 123); +``` + + +## Variables +To create a variable, it's as simple as: +```cpp +name: Type = value; +``` + +There are several types in Plasma, which are: `Int`, `Float`, `Bool`, `String` + +Here's another example: +```cpp +name: String = "bob"; +age: Int = 23; +``` + +To change the value of an already defined variable, you just have to provide the name, and then write an equals sign, like this: +```cpp +-- defining the variable +age: Int = 23; + +-- changing it later +age = age + 1; +``` \ No newline at end of file diff --git a/compiler.py b/compiler.py index 6716ce3..9086bcd 100644 --- a/compiler.py +++ b/compiler.py @@ -3,7 +3,7 @@ from llvmlite import ir from AST import Node, NodeType, Program, Expression from AST import ExpressionStatement, AssignmentStatement, BlockStatement, ReturnStatement, FunctionStatement, ReassignStatement, IfStatement from AST import WhileStatement, BreakStatement, ContinueStatement, ForStatement -from AST import InfixExpression, CallExpression +from AST import InfixExpression, CallExpression, PrefixExpression from AST import IntegerLiteral, FloatLiteral, IdentifierLiteral, BooleanLiteral, StringLiteral from AST import FunctionParameter @@ -189,15 +189,56 @@ class Compiler: def __visit_reassign_statement(self, node: ReassignStatement) -> None: name: str = node.ident.value + operator: str = node.operator value: Expression = node.right_value - value, Type = self.__resolve_value(value) + if self.environment.lookup(name) is None: self.errors.append(f"Identifier {name} has not been declared before it was re-assigned.") - else: - ptr, _ = self.environment.lookup(name) - self.builder.store(value, ptr) + return + + right_value, right_type = self.__resolve_value(value) + + var_ptr, _ = self.environment.lookup(name) + orig_value = self.builder.load(var_ptr) + + if isinstance(orig_value.type, ir.IntType) and isinstance(right_type, ir.FloatType): + orig_value = self.builder.sitofp(orig_value, ir.FloatType()) + + if isinstance(orig_value.type, ir.FloatType) and isinstance(right_type, ir.IntType): + right_value = self.builder.sitofp(right_value, ir.FloatType()) + + value = None + Type = None + match operator: + case "=": + value = right_value + case "+=": + if isinstance(orig_value.type, ir.IntType) and isinstance(right_type, ir.IntType): + value = self.builder.add(orig_value, right_value) + else: + value = self.builder.fadd(orig_value, right_value) + case "-=": + if isinstance(orig_value.type, ir.IntType) and isinstance(right_type, ir.IntType): + value = self.builder.sub(orig_value, right_value) + else: + value = self.builder.fsub(orig_value, right_value) + case "*=": + if isinstance(orig_value.type, ir.IntType) and isinstance(right_type, ir.IntType): + value = self.builder.mul(orig_value, right_value) + else: + value = self.builder.fmul(orig_value, right_value) + case "/=": + if isinstance(orig_value.type, ir.IntType) and isinstance(right_type, ir.IntType): + value = self.builder.sdiv(orig_value, right_value) + else: + value = self.builder.fdiv(orig_value, right_value) + case _: + print("Unsupported assignment operator.") + + ptr, _ = self.environment.lookup(name) + self.builder.store(value, ptr) def __visit_if_statement(self, node: IfStatement) -> None: condition = node.condition @@ -388,6 +429,32 @@ class Compiler: ret = self.builder.call(func, args) return ret, ret_type + + def __visit_prefix_expression(self, node: PrefixExpression) -> tuple[ir.Value, ir.Type]: + operator: str = node.operator + right_node: Expression = node.right_node + + right_value, right_type = self.__resolve_value(right_node) + + Type = None + value = None + + if isinstance(right_type, ir.FloatType): + Type = ir.FloatType + match operator: + case "-": + value = self.builder.fmul(right_value, ir.Constant(ir.FloatType(), -1.0)) + case "!": + value = ir.Constant(ir.IntType(1), 0) + elif isinstance(right_type, ir.IntType): + Type = ir.IntType(32) + match operator: + case "-": + value = self.builder.mul(right_value, ir.Constant(ir.IntType(32), -1)) + case "!": + value = self.builder.not_(right_value) + + return value, Type # endregion # endregion @@ -420,6 +487,8 @@ class Compiler: return self.__visit_infix_expression(node) case NodeType.CallExpression: return self.__visit_call_expression(node) + case NodeType.PrefixExpression: + return self.__visit_prefix_expression(node) def __convert_string(self, string: str) -> tuple[ir.Constant, ir.ArrayType]: string = string.replace("\\n", "\n\0") diff --git a/debug/ast.json b/debug/ast.json deleted file mode 100644 index 315ee80..0000000 --- a/debug/ast.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "type": "Program", - "statements": [ - { - "FunctionStatement": { - "type": "FunctionStatement", - "name": { - "type": "IdentifierLiteral", - "value": "main" - }, - "return_type": "Int", - "parameters": [], - "body": { - "type": "BlockStatement", - "statements": [ - { - "type": "ForStatement", - "var_declaration": { - "type": "AssignmentStatement", - "name": { - "type": "IdentifierLiteral", - "value": "x" - }, - "value": { - "type": "IntegerLiteral", - "value": 1 - }, - "value_type": "Int" - }, - "condition": { - "type": "InfixExpression", - "left_node": { - "type": "IdentifierLiteral", - "value": "x" - }, - "operator": "<=", - "right_node": { - "type": "IntegerLiteral", - "value": 20 - } - }, - "body": { - "type": "BlockStatement", - "statements": [ - { - "type": "ExpressionStatement", - "expr": { - "type": "CallExpression", - "function": { - "type": "IdentifierLiteral", - "value": "print" - }, - "arguments": [ - { - "type": "StringLiteral", - "value": "i = %i\\n" - }, - { - "type": "IdentifierLiteral", - "value": "x" - } - ] - } - } - ] - } - }, - { - "type": "ReturnStatement", - "return_value": { - "type": "IdentifierLiteral", - "value": "x" - } - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/debug/ir.ll b/debug/ir.ll deleted file mode 100644 index f92aa4b..0000000 --- a/debug/ir.ll +++ /dev/null @@ -1,34 +0,0 @@ -; ModuleID = "main" -target triple = "x86_64-pc-windows-msvc" -target datalayout = "" - -declare i32 @"printf"(i8* %".1", ...) - -@"true" = constant i1 1 -@"false" = constant i1 0 -define i32 @"main"() -{ -main_entry: - %".2" = alloca i32 - store i32 0, i32* %".2" - %".4" = load i32, i32* %".2" - %".5" = icmp slt i32 %".4", 10 - br i1 %".5", label %"while_loop_entry_1", label %"while_loop_otherwise_1" -while_loop_entry_1: - %".7" = load i32, i32* %".2" - %".8" = alloca [9 x i8]* - store [9 x i8]* @"__str_2", [9 x i8]** %".8" - %".10" = bitcast [9 x i8]* @"__str_2" to i8* - %".11" = call i32 (i8*, ...) @"printf"(i8* %".10", i32 %".7") - %".12" = load i32, i32* %".2" - %".13" = add i32 %".12", 1 - store i32 %".13", i32* %".2" - %".15" = load i32, i32* %".2" - %".16" = icmp slt i32 %".15", 10 - br i1 %".16", label %"while_loop_entry_1", label %"while_loop_otherwise_1" -while_loop_otherwise_1: - %".18" = load i32, i32* %".2" - ret i32 %".18" -} - -@"__str_2" = internal constant [9 x i8] c"a = %i\0a\00\00" \ No newline at end of file diff --git a/lexer.py b/lexer.py index 950e4ca..0c2ba95 100644 --- a/lexer.py +++ b/lexer.py @@ -84,13 +84,33 @@ class Lexer: match self.current_char: case "+": - tok = self.__new_token(TokenType.PLUS, self.current_char) + if self.__peek_char() == "=": + ch = self.current_char + self.__read_char() + tok = self.__new_token(TokenType.PLUS_EQ, ch + self.current_char) + else: + tok = self.__new_token(TokenType.PLUS, self.current_char) case "-": - tok = self.__new_token(TokenType.MINUS, self.current_char) + if self.__peek_char() == "=": + ch = self.current_char + self.__read_char() + tok = self.__new_token(TokenType.MINUS_EQ, ch + self.current_char) + else: + tok = self.__new_token(TokenType.MINUS, self.current_char) case "*": - tok = self.__new_token(TokenType.ASTERISK, self.current_char) + if self.__peek_char() == "=": + ch = self.current_char + self.__read_char() + tok = self.__new_token(TokenType.MUL_EQ, ch + self.current_char) + else: + tok = self.__new_token(TokenType.ASTERISK, self.current_char) case "/": - tok = self.__new_token(TokenType.SLASH, self.current_char) + if self.__peek_char() == "=": + ch = self.current_char + self.__read_char() + tok = self.__new_token(TokenType.DIV_EQ, ch + self.current_char) + else: + tok = self.__new_token(TokenType.SLASH, self.current_char) case "^": tok = self.__new_token(TokenType.POW, self.current_char) case "%": @@ -126,8 +146,7 @@ class Lexer: self.__read_char() tok = self.__new_token(TokenType.NOT_EQ, ch + self.current_char) else: - # TODO: handle BANG - tok = self.__new_token(TokenType.ILLEGAL, self.current_char) + tok = self.__new_token(TokenType.BANG, self.current_char) case "(": tok = self.__new_token(TokenType.LPAREN, self.current_char) case ")": diff --git a/lexer_token.py b/lexer_token.py index 764022c..5c62976 100644 --- a/lexer_token.py +++ b/lexer_token.py @@ -23,6 +23,10 @@ class TokenType(Enum): # Assignment symbols EQ = "EQ" + PLUS_EQ = "PLUS_EQ" + MINUS_EQ = "MINUS_EQ" + MUL_EQ = "MUL_EQ" + DIV_EQ = "DIV_EQ" # Comparison symbols LT = "<" @@ -43,6 +47,7 @@ class TokenType(Enum): SEMICOLON = "SEMICOLON" COMMA = "COMMA" DOLLARSIGN = "DOLLARSIGN" + BANG = "BANG" # Keywords RETURN = "RETURN" diff --git a/main.py b/main.py index aaec4f1..1b1791c 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ import llvmlite.binding as llvm from ctypes import CFUNCTYPE, c_int, c_float LEXER_DEBUG: bool = False -PARSER_DEBUG: bool = False +PARSER_DEBUG: bool = True COMPILER_DEBUG: bool = False RUN_CODE: bool = True diff --git a/plasma_parser.py b/plasma_parser.py index a5cad8c..d912ed4 100644 --- a/plasma_parser.py +++ b/plasma_parser.py @@ -5,7 +5,8 @@ from enum import Enum, auto from AST import Statement, Expression, Program from AST import ExpressionStatement, AssignmentStatement, FunctionStatement, ReturnStatement, BlockStatement, ReassignStatement, IfStatement, WhileStatement -from AST import InfixExpression, CallExpression, BreakStatement, ContinueStatement, ForStatement +from AST import InfixExpression, CallExpression, PrefixExpression +from AST import BreakStatement, ContinueStatement, ForStatement from AST import IntegerLiteral, FloatLiteral, IdentifierLiteral, BooleanLiteral, StringLiteral from AST import FunctionParameter @@ -57,7 +58,10 @@ class Parser: TokenType.STRING: self.__parse_string_literal, - TokenType.DOLLARSIGN: self.__parse_call_expression + TokenType.DOLLARSIGN: self.__parse_call_expression, + + TokenType.MINUS: self.__parse_prefix_expression, + TokenType.BANG: self.__parse_prefix_expression, } self.infix_parse_functions: dict[Token, Callable] = { # 5 + 5 TokenType.PLUS: self.__parse_infix_expression, @@ -89,6 +93,16 @@ class Parser: def __peek_token_is(self, tt: TokenType) -> bool: return self.peek_token.type == tt + def __peek_token_is_assignment(self) -> bool: + assignment_operators: list[TokenType] = [ + TokenType.EQ, + TokenType.PLUS_EQ, + TokenType.MINUS_EQ, + TokenType.MUL_EQ, + TokenType.DIV_EQ, + ] + return self.peek_token.type in assignment_operators + def __expect_peek(self, tt: TokenType) -> bool: if self.__peek_token_is(tt): self.__next_token() @@ -161,12 +175,13 @@ class Parser: # x: Int = 10; stmt: AssignmentStatement = AssignmentStatement(name=IdentifierLiteral(self.current_token.literal)) - if self.__peek_token_is(TokenType.EQ): # function definition + if self.__peek_token_is_assignment(): # function definition # x = Func(): Int { return 10; } + self.__next_token() - if self.__peek_token_is(TokenType.TYPE): + if self.__current_token_is(TokenType.EQ) and self.__peek_token_is(TokenType.TYPE): func_stmt: FunctionStatement = FunctionStatement(name=stmt.name) if self.peek_token.literal != "Func": @@ -197,9 +212,12 @@ class Parser: else: # reassignment statement assign_stmt: ReassignStatement = ReassignStatement() + + assign_stmt.operator = self.current_token.literal + assign_stmt.ident = stmt.name + self.__next_token() - assign_stmt.ident = stmt.name assign_stmt.right_value = self.__parse_expression(PrecedenceType.P_LOWEST) @@ -430,6 +448,15 @@ class Parser: return None return e_list + + def __parse_prefix_expression(self) -> PrefixExpression: + prefix_expr: PrefixExpression = PrefixExpression(operator=self.current_token.literal) + + self.__next_token() + + prefix_expr.right_node = self.__parse_expression(PrecedenceType.P_PREFIX) + + return prefix_expr # endregion # region Prefix Methods diff --git a/tests/test.pla b/tests/test.pla index 7ad864a..584041b 100644 --- a/tests/test.pla +++ b/tests/test.pla @@ -1,7 +1,7 @@ main = Func(): Int { - for (x: Int = 1; x <= 20; x = x + 1;) { - $print("i = %i\n", x) - } + a: Int = 10; - return x; + a += 1; + + return a; } \ No newline at end of file