diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index e1bb2af..bdac33d 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -23,6 +23,7 @@ import strformat import algorithm import parseutils import sequtils +import strutils export ast @@ -119,6 +120,8 @@ proc expression(self: Compiler, node: ASTNode) proc statement(self: Compiler, node: ASTNode) proc declaration(self: Compiler, node: ASTNode) proc peek(self: Compiler, distance: int = 0): ASTNode +proc identifier(self: Compiler, node: IdentExpr) +proc varDecl(self: Compiler, node: VarDecl) ## End of forward declarations ## Public getters for nicer error formatting @@ -475,32 +478,24 @@ proc binary(self: Compiler, node: BinaryExpr) = self.error(&"invalid AST node of kind {node.kind} at binary(): {node} (This is an internal error and most likely a bug)") -proc identifier(self: Compiler, node: IdentExpr) - proc declareName(self: Compiler, node: ASTNode) = - ## Compiles all name declarations (constants, static, - ## and dynamic) + ## Compiles all name declarations case node.kind: - of varDecl: + of NodeKind.varDecl: var node = VarDecl(node) - if not node.isStatic: - # This emits code for dynamically-resolved variables (i.e. globals declared as dynamic and unresolvable names) - self.emitByte(DeclareName) - self.emitBytes(self.identifierConstant(IdentExpr(node.name))) - else: - # Statically resolved variable here. Creates a new Name entry - # so that self.identifier emits the proper stack offset - if self.names.high() > 16777215: - # If someone ever hits this limit in real-world scenarios, I swear I'll - # slap myself 100 times with a sign saying "I'm dumb". Mark my words - self.error("cannot declare more than 16777215 static variables at a time") - self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name), - isPrivate: node.isPrivate, - owner: "", - isConst: node.isConst)) - self.emitByte(StoreFast) - self.emitBytes(self.names.high().toTriple()) + # Statically resolved variable here. Creates a new Name entry + # so that self.identifier emits the proper stack offset + if self.names.high() > 16777215: + # If someone ever hits this limit in real-world scenarios, I swear I'll + # slap myself 100 times with a sign saying "I'm dumb". Mark my words + self.error("cannot declare more than 16777215 static variables at a time") + self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name), + isPrivate: node.isPrivate, + owner: "", + isConst: node.isConst)) + self.emitByte(StoreVar) + self.emitBytes(self.names.high().toTriple()) of funDecl: var node = FunDecl(node) # Declares the function's name in the @@ -515,15 +510,13 @@ proc declareName(self: Compiler, node: ASTNode) = if self.names.high() > 16777215: self.error("cannot declare more than 16777215 static variables at a time") self.names.add(Name(depth: self.scopeDepth + 1, isPrivate: true, owner: self.currentModule, isConst: false, name: IdentExpr(argument.name))) - self.emitByte(LoadFast) + self.emitByte(LoadVar) self.emitBytes(self.names.high().toTriple()) self.scopeDepth -= 1 # TODO: Default arguments and unpacking else: discard # TODO: Classes -proc varDecl(self: Compiler, node: VarDecl) - proc resolveStatic(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): Name = @@ -571,7 +564,7 @@ proc identifier(self: Compiler, node: IdentExpr) = let index = self.getStaticIndex(node) if index != -1: if index >= 0: - self.emitByte(LoadFast) # Static name resolution, loads value at index in the stack. Very fast. Much wow. + self.emitByte(LoadVar) # Static name resolution, loads value at index in the stack. Very fast. Much wow. self.emitBytes(index.toTriple()) else: if self.closedOver.len() == 0: @@ -581,8 +574,7 @@ proc identifier(self: Compiler, node: IdentExpr) = self.emitByte(LoadHeap) # Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics self.emitBytes(self.closedOver.high().toTriple()) else: - self.emitByte(LoadName) # Resolves by name, at runtime, in a global hashmap. Slowest method - self.emitBytes(self.identifierConstant(node)) + self.error(&"reference to undeclared name '{node.token.lexeme}'") proc assignment(self: Compiler, node: ASTNode) = @@ -631,15 +623,10 @@ proc assignment(self: Compiler, node: ASTNode) = # but that requires variants for stack, # heap, and closure variables and I cba if index != -1: - self.emitByte(StoreFast) + self.emitByte(StoreVar) self.emitBytes(index.toTriple()) else: - # Assignment only encompasses variable assignments, - # so we can ensure the name is a constant (i.e. an - # IdentExpr) instead of an object (which would be - # the case with setItemExpr) - self.emitByte(StoreName) - self.emitBytes(self.makeConstant(name)) + self.error(&"reference to undeclared name '{node.token.lexeme}'") of setItemExpr: discard # TODO @@ -749,8 +736,89 @@ proc whileStmt(self: Compiler, node: WhileStmt) = self.emitLoop(start) +proc inferValueType(self: Compiler, node: ASTNode): ASTNode = + ## Infers the type of a given literal expression + case node.kind: + of listExpr: + return ListExpr(node).valueType + of dictExpr: + # It's not important that we don't use + # valueType here, we just need to return + # a non-nil value so we don't error out + return DictExpr(node).keyType + of intExpr: + var node = IntExpr(node) + var size = node.token.lexeme.split("'") + if len(size) notin 1..2: + self.error("invalid state: inferValueType -> invalid size specifier for int") + elif size.len() == 1: + return newIdentExpr(Token(lexeme: "int")) + elif size[1] in ["u64", "i64", "u32", "i32", "f64", "f32", "i32", "u32", "u8", "i8"]: + if size[1].startsWith("u"): + size[1] = size[1].strip(true, false, {'u'}) + size[1] = &"uint{size[1]}" + elif size[1].startsWith("i"): + size[1] = size[1].strip(true, false, {'i'}) + size[1] = &"int{size[1]}" + elif size[1].startsWith("f"): + size[1] = size[1].strip(true, false, {'f'}) + size[1] = &"float{size[1]}" + return newIdentExpr(Token(lexeme: size[1])) + else: + self.error(&"invalid type specifier '{size[1]}' for '{size[0]}'") + return newIdentExpr(Token(lexeme: "int")) + else: + discard # TODO + + +proc inferExprType(self: Compiler, node: ASTNode): ASTNode = + ## Infers the type of a given expression and + ## returns it + case node.kind: + of unaryExpr: + return self.inferValueType(UnaryExpr(node).a) + of binaryExpr: + var node = BinaryExpr(node) + var a = self.inferValueType(node.a) + var b = self.inferValueType(node.b) + # This is obviously not correct, but + # this function is only useful as a + # first type checking step anyway + if a == nil: + return b + return a + of {intExpr, hexExpr, binExpr, octExpr, + strExpr, falseExpr, trueExpr, infExpr, + nanExpr, floatExpr, nilExpr, listExpr, + dictExpr, setExpr, tupleExpr + }: + return self.inferValueType(node) + else: + discard # Unreachable + + +proc inferDeclType(self: Compiler, node: Declaration): ASTNode = + ## Infers the type of a given declaration if it's + ## not already defined and returns it + case node.kind: + of funDecl: + var node = FunDecl(node) + if node.returnType != nil: + return node.returnType + of NodeKind.varDecl: + var node = VarDecl(node) + if node.valueType != nil: + return node.valueType + else: + return self.inferExprType(node.value) + else: + return # Unreachable + + proc expression(self: Compiler, node: ASTNode) = ## Compiles all expressions + if self.inferExprType(node) == nil: + self.error("expression has no type") case node.kind: of getItemExpr: discard # TODO @@ -874,7 +942,8 @@ proc statement(self: Compiler, node: ASTNode) = ## Compiles all statements case node.kind: of exprStmt: - self.expression(ExprStmt(node).expression) + var expression = ExprStmt(node).expression + self.expression(expression) self.emitByte(Pop) # Expression statements discard their value. Their main use case is side effects in function calls of NodeKind.ifStmt: self.ifStmt(IfStmt(node)) @@ -915,42 +984,6 @@ proc statement(self: Compiler, node: ASTNode) = self.expression(node) -proc inferValueType(self: Compiler, node: ASTNode): ASTNode = - ## Infers the type of a given literal expression - case node.kind: - of listExpr: - return ListExpr(node).valueType - of dictExpr: - # It's not important that we don't use - # valueType here, we just need to return - # a non-nil value so we don't error out - return DictExpr(node).keyType - else: - discard # TODO - -proc inferExprType(self: Compiler, node: Expression): ASTNode = - ## Infers the type of a given expression and - ## returns it - # TODO - - - -proc inferDeclType(self: Compiler, node: Declaration): ASTNode = - ## Infers the type of a given declaration if it's - ## not already defined and returns it - case node.kind: - of funDecl: - var node = FunDecl(node) - if node.returnType != nil: - return node.returnType - of NodeKind.varDecl: - var node = VarDecl(node) - if node.valueType != nil: - return node.valueType - else: - return # Unreachable - - proc varDecl(self: Compiler, node: VarDecl) = ## Compiles variable declarations if self.inferDeclType(node) == nil: @@ -994,7 +1027,6 @@ proc funDecl(self: Compiler, node: FunDecl) = # of boilerplate code to make closures work, but # that's about it - self.emitBytes(OpCode.Nil, OpCode.Return) # Currently defer is not functional so we diff --git a/src/frontend/meta/ast.nim b/src/frontend/meta/ast.nim index cd62f4d..f07ad41 100644 --- a/src/frontend/meta/ast.nim +++ b/src/frontend/meta/ast.nim @@ -250,7 +250,6 @@ type name*: ASTNode value*: ASTNode isConst*: bool - isStatic*: bool isPrivate*: bool isLet*: bool valueType*: ASTNode diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index e24622f..242790d 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -105,13 +105,8 @@ type PopN, # Pops x elements off the stack (optimization for exiting scopes and returning from functions) ## Name resolution/handling LoadAttribute, - DeclareName, # Declares a global dynamically bound name in the current scope - LoadName, # Loads a dynamically bound variable - LoadFast, # Loads a statically bound variable - StoreName, # Sets/updates a dynamically bound variable's value - StoreFast, # Sets/updates a statically bound variable's value - DeleteName, # Unbinds a dynamically bound variable's name from the current scope - DeleteFast, # Unbinds a statically bound variable's name from the current scope + LoadVar, # Loads a variable from the stack + StoreVar, # Sets/updates a statically bound variable's value LoadHeap, # Loads a closed-over variable StoreHeap, # Stores a closed-over variable ## Looping and jumping @@ -176,11 +171,11 @@ const simpleInstructions* = {OpCode.Return, BinaryAdd, BinaryMultiply, MakeClass, ImplicitReturn} # Constant instructions are instructions that operate on the bytecode constant table -const constantInstructions* = {LoadConstant, DeclareName, LoadName, StoreName, DeleteName} +const constantInstructions* = {LoadConstant, } # Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form # of 24 bit integers -const stackTripleInstructions* = {Call, StoreFast, DeleteFast, LoadFast, LoadHeap, StoreHeap} +const stackTripleInstructions* = {Call, StoreVar, LoadVar, LoadHeap, StoreHeap} # Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form # of 16 bit integers diff --git a/src/frontend/optimizer.nim b/src/frontend/optimizer.nim index d6da32a..5362393 100644 --- a/src/frontend/optimizer.nim +++ b/src/frontend/optimizer.nim @@ -153,9 +153,9 @@ proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode = a = self.optimizeNode(node.a) b = self.optimizeNode(node.b) if self.warnings.len() > 0 and self.warnings[^1].kind == valueOverflow and (self.warnings[^1].node == a or self.warnings[^1].node == b): - # We can't optimize further, the overflow will be caught in the compiler. We don't return the same node + # We can't optimize further: the overflow will be caught in the compiler. We don't return the same node # because optimizeNode might've been able to optimize one of the two operands and we don't know which - return BinaryExpr(kind: binaryExpr, a: a, b: b, operator: node.operator) + return newBinaryExpr(a, node.operator, b) if node.operator.kind == DoubleEqual: if a.kind in {trueExpr, falseExpr, nilExpr, nanExpr, infExpr}: self.newWarning(equalityWithSingleton, a) @@ -204,10 +204,10 @@ proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode = result = newBinaryExpr(a, node.operator, b) except OverflowDefect: self.newWarning(valueOverflow, node) - return BinaryExpr(kind: binaryExpr, a: a, b: b, operator: node.operator) + return newBinaryExpr(a, node.operator, b) except RangeDefect: # TODO: What warning do we raise here? - return BinaryExpr(kind: binaryExpr, a: a, b: b, operator: node.operator) + return newBinaryExpr(a, node.operator, b) result = newIntExpr(Token(kind: Integer, lexeme: $z, line: IntExpr(a).literal.line, pos: (start: -1, stop: -1))) elif a.kind == floatExpr or b.kind == floatExpr: var x, y, z: float @@ -241,7 +241,7 @@ proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode = of Percentage: z = x mod y else: - result = BinaryExpr(kind: binaryExpr, a: a, b: b, operator: node.operator) + result = newBinaryExpr(a, node.operator, b) except OverflowDefect: self.newWarning(valueOverflow, node) return newBinaryExpr(a, node.operator, b)