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

View File

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

View File

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

View File

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