Initial work on type inference and variable declarations. Minor changes to the optimizer
This commit is contained in:
parent
e02f159514
commit
79ca72d983
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue