started work on assignment operators

This commit is contained in:
SpookyDervish
2025-10-17 06:21:33 +11:00
parent 1d6c3db5e4
commit 24fcbb3fb7
10 changed files with 229 additions and 137 deletions

20
AST.py
View File

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

View File

@@ -1,3 +1,71 @@
# Plasma
The Plasma programming language.
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;
```

View File

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

View File

@@ -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"
}
}
]
}
}
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}