Răsfoiți Sursa

implemented lexer, begin parser/ast implementation

Johan Rong 1 lună în urmă
părinte
comite
ea7557fd19

+ 2 - 1
.gitignore

@@ -1 +1,2 @@
-.direnv
+.direnv/
+__pycache__/

+ 4 - 4
flake.lock

@@ -2,16 +2,16 @@
   "nodes": {
     "nixpkgs": {
       "locked": {
-        "lastModified": 1790578696,
-        "narHash": "sha256-ZoxIApko70jCdbH3l20HWXOBaT2HZd87orzd2yJ9dVE=",
+        "lastModified": 1790533781,
+        "narHash": "sha256-F+b8u43BVF5DlM0pz7JQSbtbwKy5PrHIT5zQUekXsD0=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "7a0f122f5090cf4c2ade2a13a0e229d4e19ba71f",
+        "rev": "cf5e76507c6e23b59f7e0ffcc7baa2a39ddd8442",
         "type": "github"
       },
       "original": {
         "owner": "nixos",
-        "ref": "nixos-unstable",
+        "ref": "nixos-26.05",
         "repo": "nixpkgs",
         "type": "github"
       }

+ 2 - 2
flake.nix

@@ -1,8 +1,8 @@
 {
-  description = "Jupyter Notebooks development environment";
+  description = "Python development environment";
 
   inputs = {
-    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+    nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
   };
 
   outputs = { self, nixpkgs }:

+ 26 - 0
src/zelkelc/__main__.py

@@ -0,0 +1,26 @@
+from src.zelkelc.lexer.tokenizer import lex
+from src.zelkelc.ast.parser import Parser
+
+source = """
+//test
+class MyClass {
+    val immutable_value: String = "sdf"
+    var mutable_value: i64 = 128
+    
+    fn function() -> void {
+        return
+    }
+}
+
+static fn main() -> i64 {
+    println("Hello, world!")
+    return 0
+}
+"""
+
+if __name__ == "__main__":
+    tokens = lex(source)
+    print(tokens)
+    parser = Parser(tokens, 0)
+    ast = parser.parse()
+    print(ast)

+ 42 - 0
src/zelkelc/ast/nodes.py

@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+import src.zelkelc.ast.values as values
+
+@dataclass
+class ClassDeclaration:
+    name: str
+    members: list[ValueDeclaration]
+    methods: list[FunctionDeclaration]
+
+@dataclass
+class ValueDeclaration:
+    name: str
+    typ: values.Value
+    expr: Expression
+    
+@dataclass
+class FunctionDeclaration:
+    name: str
+    args: dict[str, values.Value]
+    typ: values.Value
+    body: list[Node]
+
+Node = ClassDeclaration | ValueDeclaration | FunctionDeclaration
+
+@dataclass
+class BinaryExpression:
+    left: Expression
+    right: Expression
+    op: str
+
+@dataclass
+class UnaryExpression:
+    expr: Expression
+    sign: str
+
+@dataclass
+class PrimaryExpression:
+    value: values.Value
+
+Expression = BinaryExpression | UnaryExpression | PrimaryExpression

+ 55 - 0
src/zelkelc/ast/parser.py

@@ -0,0 +1,55 @@
+import src.zelkelc.lexer.token as token
+import src.zelkelc.ast.nodes as nodes
+
+class Parser:
+    tokens: list[token.Token]
+    cursor: int
+    
+    def __init__(self, tokens: list[token.Token], cursor: int):
+        self.tokens = tokens
+        self.cursor = cursor
+    
+    def expect(self, tok: token.Token): 
+        if not self.tokens[self.cursor] == tok:
+            print(f"Expected {tok}, but found {self.tokens[self.cursor]}")
+            exit(1)
+        self.cursor += 1
+            
+    def expect_type(self, tok_type: token.Token) -> token.Token:
+        if not type(self.tokens[self.cursor]) == tok_type:
+            print(f"Expected {tok_type}, but found {type(self.tokens[self.cursor])}")
+            exit(1)
+        self.cursor += 1
+        return self.tokens[self.cursor - 1]
+                
+
+    def parse(self):
+        ast: list[nodes.Node] = []
+        
+        while self.cursor < len(self.tokens):
+            t = self.tokens[self.cursor]
+            
+            match t:
+                case token.Identifier():
+                    match t.value:
+                        case "class":
+                            ast.append(self.parse_class_declaration())
+                        case "static":
+                            print("TODO: static keyword not implemented")
+                            exit(1)
+        
+        return ast
+
+    def parse_class_declaration(self) -> nodes.ClassDeclaration:
+        self.expect(token.Identifier("class"))
+        name = self.expect_type(token.Identifier)
+        self.expect(token.LBrace())
+        
+        self.expect(token.RBrace())
+        
+        return nodes.ClassDeclaration(
+            name,
+            members = [],
+            methods = [],
+        )
+        

+ 11 - 0
src/zelkelc/ast/values.py

@@ -0,0 +1,11 @@
+from dataclasses import dataclass
+
+@dataclass
+class Integer:
+    value: int
+
+@dataclass
+class String:
+    value: str
+
+Value = Integer | String

+ 68 - 0
src/zelkelc/lexer/token.py

@@ -0,0 +1,68 @@
+from dataclasses import dataclass
+
+@dataclass
+class Identifier:
+    value: str
+
+@dataclass
+class String:
+    value: str
+
+@dataclass
+class Integer:
+    value: int
+
+@dataclass
+class LParen:
+    pass
+
+@dataclass
+class RParen:
+    pass
+
+@dataclass
+class LBrace:
+    pass
+
+@dataclass
+class RBrace:
+    pass
+
+@dataclass
+class Period:
+    pass
+
+@dataclass
+class Comma:
+    pass
+
+@dataclass
+class Arrow:
+    pass
+
+@dataclass
+class Colon:
+    pass
+
+@dataclass
+class Equals:
+    pass
+
+@dataclass
+class Plus:
+    pass
+
+@dataclass
+class Minus:
+    pass
+
+@dataclass
+class Star:
+    pass
+
+@dataclass
+class Slash:
+    pass
+
+
+Token = Identifier | String | Integer | LParen | RParen | LBrace | RBrace | Period | Comma | Arrow | Colon | Equals | Plus | Minus | Star | Slash

+ 96 - 0
src/zelkelc/lexer/tokenizer.py

@@ -0,0 +1,96 @@
+import src.zelkelc.lexer.token as token
+
+def lex(source: str) -> list[token.Token]:
+    tokens: list[token.Token] = []
+    
+    i = 0
+    while i < len(source):
+        c = source[i]
+        if c == '/' and source[i + 1] == '/':
+            i += 2
+            for _, comment_c in enumerate(source[i:]):
+                i += 1                
+                if comment_c == '\n':
+                    break
+        elif c.isnumeric():
+            i += 1
+            
+            integer = int(c)
+            for _, integer_c in enumerate(source[i:]):
+                if not integer_c.isnumeric():
+                    break
+                i += 1
+                integer += int(integer_c)
+            tokens.append(token.Integer(integer))
+        elif c.isalpha() or c == '_':
+            i += 1
+            
+            identifier = c
+            for _, identifier_c in enumerate(source[i:]):
+                if not (identifier_c.isalnum() or identifier_c == '_'):
+                    break
+                i += 1
+                identifier += identifier_c
+            tokens.append(token.Identifier(identifier))
+        elif c == '"':
+            i += 1
+            
+            string = ""
+            for _, string_c in enumerate(source[i:]):
+                if string_c == '"':
+                    break
+                i += 1
+                string += string_c
+            if i > len(source) or source[i] != '"':
+                print("No '\"' to close string")
+                break
+            i += 1
+            tokens.append(token.String(string))
+        elif c == '-' and source[i + 1] == '>':
+            tokens.append(token.Arrow())
+            i += 2
+        else:
+            match c:
+                case ' ' | '\n':
+                    i += 1
+                case '(':
+                    tokens.append(token.LParen())
+                    i += 1
+                case ')':
+                    tokens.append(token.RParen())
+                    i += 1
+                case '{':
+                    tokens.append(token.LBrace())
+                    i += 1
+                case '}':
+                    tokens.append(token.RBrace())
+                    i += 1
+                case '.':
+                    tokens.append(token.Period())
+                    i += 1
+                case ',':
+                    tokens.append(token.Comma())
+                    i += 1
+                case ':':
+                    tokens.append(token.Colon())
+                    i += 1
+                case '=':
+                    tokens.append(token.Equals())
+                    i += 1
+                case '+':
+                    tokens.append(token.Plus())
+                    i += 1
+                case '-':
+                    tokens.append(token.Minus())
+                    i += 1
+                case '*':
+                    tokens.append(token.Star())
+                    i += 1
+                case '/':
+                    tokens.append(token.Slash())
+                    i += 1
+                case _:
+                    print(f"Unknown character found during lexing: '{c}' at {i}")
+                    break
+    
+    return tokens