Initial work on type inference and variable declarations. Minor changes to the optimizer

This commit is contained in:
Mattia Giambirtone 2022-04-21 15:25:29 +02:00
parent e02f159514
commit 79ca72d983
4 changed files with 114 additions and 88 deletions

View File

@ -23,6 +23,7 @@ import strformat
import algorithm import algorithm
import parseutils import parseutils
import sequtils import sequtils
import strutils
export ast export ast
@ -119,6 +120,8 @@ proc expression(self: Compiler, node: ASTNode)
proc statement(self: Compiler, node: ASTNode) proc statement(self: Compiler, node: ASTNode)
proc declaration(self: Compiler, node: ASTNode) proc declaration(self: Compiler, node: ASTNode)
proc peek(self: Compiler, distance: int = 0): 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 ## End of forward declarations
## Public getters for nicer error formatting ## 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)") 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) = proc declareName(self: Compiler, node: ASTNode) =
## Compiles all name declarations (constants, static, ## Compiles all name declarations
## and dynamic)
case node.kind: case node.kind:
of varDecl: of NodeKind.varDecl:
var node = VarDecl(node) var node = VarDecl(node)
if not node.isStatic: # Statically resolved variable here. Creates a new Name entry
# This emits code for dynamically-resolved variables (i.e. globals declared as dynamic and unresolvable names) # so that self.identifier emits the proper stack offset
self.emitByte(DeclareName) if self.names.high() > 16777215:
self.emitBytes(self.identifierConstant(IdentExpr(node.name))) # If someone ever hits this limit in real-world scenarios, I swear I'll
else: # slap myself 100 times with a sign saying "I'm dumb". Mark my words
# Statically resolved variable here. Creates a new Name entry self.error("cannot declare more than 16777215 static variables at a time")
# so that self.identifier emits the proper stack offset self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name),
if self.names.high() > 16777215: isPrivate: node.isPrivate,
# If someone ever hits this limit in real-world scenarios, I swear I'll owner: "",
# slap myself 100 times with a sign saying "I'm dumb". Mark my words isConst: node.isConst))
self.error("cannot declare more than 16777215 static variables at a time") self.emitByte(StoreVar)
self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name), self.emitBytes(self.names.high().toTriple())
isPrivate: node.isPrivate,
owner: "",
isConst: node.isConst))
self.emitByte(StoreFast)
self.emitBytes(self.names.high().toTriple())
of funDecl: of funDecl:
var node = FunDecl(node) var node = FunDecl(node)
# Declares the function's name in the # Declares the function's name in the
@ -515,15 +510,13 @@ proc declareName(self: Compiler, node: ASTNode) =
if self.names.high() > 16777215: if self.names.high() > 16777215:
self.error("cannot declare more than 16777215 static variables at a time") 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.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.emitBytes(self.names.high().toTriple())
self.scopeDepth -= 1 self.scopeDepth -= 1
# TODO: Default arguments and unpacking # TODO: Default arguments and unpacking
else: else:
discard # TODO: Classes discard # TODO: Classes
proc varDecl(self: Compiler, node: VarDecl)
proc resolveStatic(self: Compiler, name: IdentExpr, proc resolveStatic(self: Compiler, name: IdentExpr,
depth: int = self.scopeDepth): Name = depth: int = self.scopeDepth): Name =
@ -571,7 +564,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
let index = self.getStaticIndex(node) let index = self.getStaticIndex(node)
if index != -1: if index != -1:
if index >= 0: 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()) self.emitBytes(index.toTriple())
else: else:
if self.closedOver.len() == 0: 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.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()) self.emitBytes(self.closedOver.high().toTriple())
else: else:
self.emitByte(LoadName) # Resolves by name, at runtime, in a global hashmap. Slowest method self.error(&"reference to undeclared name '{node.token.lexeme}'")
self.emitBytes(self.identifierConstant(node))
proc assignment(self: Compiler, node: ASTNode) = proc assignment(self: Compiler, node: ASTNode) =
@ -631,15 +623,10 @@ proc assignment(self: Compiler, node: ASTNode) =
# but that requires variants for stack, # but that requires variants for stack,
# heap, and closure variables and I cba # heap, and closure variables and I cba
if index != -1: if index != -1:
self.emitByte(StoreFast) self.emitByte(StoreVar)
self.emitBytes(index.toTriple()) self.emitBytes(index.toTriple())
else: else:
# Assignment only encompasses variable assignments, self.error(&"reference to undeclared name '{node.token.lexeme}'")
# 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))
of setItemExpr: of setItemExpr:
discard discard
# TODO # TODO
@ -749,8 +736,89 @@ proc whileStmt(self: Compiler, node: WhileStmt) =
self.emitLoop(start) 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) = proc expression(self: Compiler, node: ASTNode) =
## Compiles all expressions ## Compiles all expressions
if self.inferExprType(node) == nil:
self.error("expression has no type")
case node.kind: case node.kind:
of getItemExpr: of getItemExpr:
discard # TODO discard # TODO
@ -874,7 +942,8 @@ proc statement(self: Compiler, node: ASTNode) =
## Compiles all statements ## Compiles all statements
case node.kind: case node.kind:
of exprStmt: 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 self.emitByte(Pop) # Expression statements discard their value. Their main use case is side effects in function calls
of NodeKind.ifStmt: of NodeKind.ifStmt:
self.ifStmt(IfStmt(node)) self.ifStmt(IfStmt(node))
@ -915,42 +984,6 @@ proc statement(self: Compiler, node: ASTNode) =
self.expression(node) 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) = proc varDecl(self: Compiler, node: VarDecl) =
## Compiles variable declarations ## Compiles variable declarations
if self.inferDeclType(node) == nil: if self.inferDeclType(node) == nil:
@ -994,7 +1027,6 @@ proc funDecl(self: Compiler, node: FunDecl) =
# of boilerplate code to make closures work, but # of boilerplate code to make closures work, but
# that's about it # that's about it
self.emitBytes(OpCode.Nil, OpCode.Return) self.emitBytes(OpCode.Nil, OpCode.Return)
# Currently defer is not functional so we # Currently defer is not functional so we

View File

@ -250,7 +250,6 @@ type
name*: ASTNode name*: ASTNode
value*: ASTNode value*: ASTNode
isConst*: bool isConst*: bool
isStatic*: bool
isPrivate*: bool isPrivate*: bool
isLet*: bool isLet*: bool
valueType*: ASTNode valueType*: ASTNode

View File

@ -105,13 +105,8 @@ type
PopN, # Pops x elements off the stack (optimization for exiting scopes and returning from functions) PopN, # Pops x elements off the stack (optimization for exiting scopes and returning from functions)
## Name resolution/handling ## Name resolution/handling
LoadAttribute, LoadAttribute,
DeclareName, # Declares a global dynamically bound name in the current scope LoadVar, # Loads a variable from the stack
LoadName, # Loads a dynamically bound variable StoreVar, # Sets/updates a statically bound variable's value
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
LoadHeap, # Loads a closed-over variable LoadHeap, # Loads a closed-over variable
StoreHeap, # Stores a closed-over variable StoreHeap, # Stores a closed-over variable
## Looping and jumping ## Looping and jumping
@ -176,11 +171,11 @@ const simpleInstructions* = {OpCode.Return, BinaryAdd, BinaryMultiply,
MakeClass, ImplicitReturn} MakeClass, ImplicitReturn}
# Constant instructions are instructions that operate on the bytecode constant table # 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 # Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers # 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 # Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers # of 16 bit integers

View File

@ -153,9 +153,9 @@ proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode =
a = self.optimizeNode(node.a) a = self.optimizeNode(node.a)
b = self.optimizeNode(node.b) 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): 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 # 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 node.operator.kind == DoubleEqual:
if a.kind in {trueExpr, falseExpr, nilExpr, nanExpr, infExpr}: if a.kind in {trueExpr, falseExpr, nilExpr, nanExpr, infExpr}:
self.newWarning(equalityWithSingleton, a) self.newWarning(equalityWithSingleton, a)
@ -204,10 +204,10 @@ proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode =
result = newBinaryExpr(a, node.operator, b) result = newBinaryExpr(a, node.operator, b)
except OverflowDefect: except OverflowDefect:
self.newWarning(valueOverflow, node) self.newWarning(valueOverflow, node)
return BinaryExpr(kind: binaryExpr, a: a, b: b, operator: node.operator) return newBinaryExpr(a, node.operator, b)
except RangeDefect: except RangeDefect:
# TODO: What warning do we raise here? # 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))) 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: elif a.kind == floatExpr or b.kind == floatExpr:
var x, y, z: float var x, y, z: float
@ -241,7 +241,7 @@ proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode =
of Percentage: of Percentage:
z = x mod y z = x mod y
else: else:
result = BinaryExpr(kind: binaryExpr, a: a, b: b, operator: node.operator) result = newBinaryExpr(a, node.operator, b)
except OverflowDefect: except OverflowDefect:
self.newWarning(valueOverflow, node) self.newWarning(valueOverflow, node)
return newBinaryExpr(a, node.operator, b) return newBinaryExpr(a, node.operator, b)