Fixed wrong bytecode emission when compiling constants. Added a few more checks and made dynamic declarations illegal inside local scopes again (will change once closures are implemented)
This commit is contained in:
parent
690d4d364a
commit
a1698ef892
|
@ -170,6 +170,13 @@ proc emitBytes(self: Compiler, byt1: OpCode|uint8, byt2: OpCode|uint8) =
|
|||
self.emitByte(uint8 byt2)
|
||||
|
||||
|
||||
proc emitBytes(self: Compiler, bytarr: array[2, uint8]) =
|
||||
## Handy helper method to write an array of 2 bytes into
|
||||
## the current chunk, calling emitByte on each of its
|
||||
## elements
|
||||
self.emitBytes(bytarr[0], bytarr[1])
|
||||
|
||||
|
||||
proc emitBytes(self: Compiler, bytarr: array[3, uint8]) =
|
||||
## Handy helper method to write an array of 3 bytes into
|
||||
## the current chunk, calling emitByte on each of its
|
||||
|
@ -370,14 +377,6 @@ proc declareName(self: Compiler, node: ASTNode) =
|
|||
self.emitBytes(self.identifierConstant(IdentExpr(node.name)))
|
||||
self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name),
|
||||
isPrivate: node.isPrivate, owner: node.owner))
|
||||
elif node.isConst:
|
||||
# Constants are emitted as, you guessed it, constant instructions
|
||||
# no matter the scope depth. Also, name resolution specifiers do not
|
||||
# apply to them (because what would it mean for a constant to be dynamic
|
||||
# anyway?)
|
||||
self.names.add(Name(depth: self.scopeDepth, name: IdentExpr(node.name),
|
||||
isPrivate: node.isPrivate, owner: node.owner))
|
||||
self.emitConstant(node.value)
|
||||
else:
|
||||
# Statically resolved variable here. Only creates a new StaticName entry
|
||||
# so that self.identifier (and, by extension, self.getStaticIndex) emit the
|
||||
|
@ -442,13 +441,20 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
if s == nil and d == nil and self.scopeDepth == 0:
|
||||
# Usage of undeclared globals is easy to detect at the top level
|
||||
self.error(&"reference to undeclared name '{node.name.lexeme}'")
|
||||
let index = self.getStaticIndex(node)
|
||||
if index != -1:
|
||||
self.emitByte(LoadNameFast) # Scoping/static resolution
|
||||
self.emitBytes(index.toTriple())
|
||||
if s.isConst:
|
||||
# Constants are emitted as, you guessed it, constant instructions
|
||||
# no matter the scope depth. Also, name resolution specifiers do not
|
||||
# apply to them (because what would it mean for a constant to be dynamic
|
||||
# anyway?)
|
||||
self.emitConstant(node)
|
||||
else:
|
||||
self.emitByte(LoadName)
|
||||
self.emitBytes(self.identifierConstant(node))
|
||||
let index = self.getStaticIndex(node)
|
||||
if index != -1:
|
||||
self.emitByte(LoadNameFast) # Static name resolution
|
||||
self.emitBytes(index.toTriple())
|
||||
else:
|
||||
self.emitByte(LoadName)
|
||||
self.emitBytes(self.identifierConstant(node))
|
||||
|
||||
|
||||
proc assignment(self: Compiler, node: ASTNode) =
|
||||
|
@ -478,7 +484,8 @@ proc assignment(self: Compiler, node: ASTNode) =
|
|||
|
||||
|
||||
proc beginScope(self: Compiler) =
|
||||
## Begins a new local scope
|
||||
## Begins a new local scope by incrementing the current
|
||||
## scope's depth
|
||||
inc(self.scopeDepth)
|
||||
|
||||
|
||||
|
@ -486,12 +493,35 @@ proc endScope(self: Compiler) =
|
|||
## Ends the current local scope
|
||||
if self.scopeDepth < 0:
|
||||
self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)")
|
||||
var popped: int = 0
|
||||
for ident in reversed(self.staticNames):
|
||||
if ident.depth > self.scopeDepth:
|
||||
# All variables with a scope depth larger than the current one
|
||||
# are now out of scope. Begone, you're now homeless!
|
||||
self.emitByte(Pop)
|
||||
discard self.staticNames.pop()
|
||||
if not self.enableOptimizations:
|
||||
if ident.depth > self.scopeDepth:
|
||||
# All variables with a scope depth larger than the current one
|
||||
# are now out of scope. Begone, you're now homeless!
|
||||
self.emitByte(Pop)
|
||||
inc(popped)
|
||||
if self.enableOptimizations and popped > 1:
|
||||
# If we're popping less than 65535 variables, then
|
||||
# we can emit a PopN instruction. This is true for
|
||||
# 99.99999% of the use cases of the language (who the
|
||||
# hell is going to use 65 THOUSAND local variables?), but
|
||||
# if you'll ever use more then JAPL will emit a PopN instruction
|
||||
# for the first 65 thousand and change local variables and then
|
||||
# emit another batch of plain ol' Pop instructions for the rest
|
||||
if popped <= uint16.high().int():
|
||||
self.emitByte(PopN)
|
||||
self.emitBytes(popped.toDouble())
|
||||
else:
|
||||
self.emitByte(PopN)
|
||||
self.emitBytes(uint16.high().toDouble())
|
||||
for i in countdown(self.staticNames.high(), popped - uint16.high().int()):
|
||||
if self.staticNames[i].depth > self.scopeDepth:
|
||||
self.emitByte(Pop)
|
||||
elif popped == 1:
|
||||
self.emitByte(Pop)
|
||||
for _ in countup(0, popped - 1):
|
||||
discard self.staticNames.pop()
|
||||
dec(self.scopeDepth)
|
||||
|
||||
|
||||
|
@ -618,3 +648,5 @@ proc compile*(self: Compiler, ast: seq[ASTNode], file: string): Chunk =
|
|||
self.endScope()
|
||||
self.emitByte(OpCode.Return) # Exits the VM's main loop when used at the global scope
|
||||
result = self.chunk
|
||||
if self.scopeDepth != -1:
|
||||
self.error(&"internal error: invalid scopeDepth state (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?")
|
||||
|
|
|
@ -165,7 +165,7 @@ const simpleInstructions* = {Return, BinaryAdd, BinaryMultiply,
|
|||
BinaryShiftLeft, BinaryShiftRight,
|
||||
BinaryXor, LogicalNot, EqualTo,
|
||||
GreaterThan, LessThan, LoadAttribute,
|
||||
BinarySlice, Pop, PopN, UnaryNegate,
|
||||
BinarySlice, Pop, UnaryNegate,
|
||||
BinaryIs, BinaryAs, GreaterOrEqual,
|
||||
LessOrEqual, BinaryOr, BinaryAnd,
|
||||
UnaryNot, InPlaceAdd, InPlaceDivide,
|
||||
|
@ -176,8 +176,16 @@ const simpleInstructions* = {Return, BinaryAdd, BinaryMultiply,
|
|||
# Constant instructions are instructions that operate on the bytecode constant table
|
||||
const constantInstructions* = {LoadConstant, DeclareName, LoadName, UpdateName, DeleteName}
|
||||
|
||||
# Stack instructions operate on the stack at arbitrary offsets
|
||||
const stackInstructions* = {Call, UpdateNameFast, DeleteNameFast, LoadNameFast}
|
||||
# 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, UpdateNameFast, DeleteNameFast, LoadNameFast}
|
||||
|
||||
# Stack Double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
|
||||
# of 16 bit integers
|
||||
const stackDoubleInstructions* = {}
|
||||
|
||||
# Argument double argument instructions take hardcoded arguments on the stack as 16 bit integers
|
||||
const argumentDoubleInstructions* = {PopN, }
|
||||
|
||||
# Jump instructions jump at relative or absolute bytecode offsets
|
||||
const jumpInstructions* = {JumpIfFalse, JumpIfFalsePop, JumpForwards, JumpBackwards,
|
||||
|
|
|
@ -779,8 +779,22 @@ proc ifStmt(self: Parser): ASTNode =
|
|||
result = newIfStmt(condition, thenBranch, elseBranch, tok)
|
||||
|
||||
|
||||
proc checkDecl(self: Parser, isStatic, isPrivate: bool) =
|
||||
## Handy utility function that avoids us from copy
|
||||
## pasting the same checks to all declaration handlers
|
||||
if not isStatic and self.currentFunction != nil:
|
||||
self.error("dynamic declarations are not allowed inside functions")
|
||||
if not isStatic and self.scopeDepth > 0:
|
||||
self.error("dynamic declarations are not allowed inside local scopes")
|
||||
if not isPrivate and self.currentFunction != nil:
|
||||
self.error("cannot bind public names inside functions")
|
||||
if not isPrivate and self.scopeDepth > 0:
|
||||
self.error("cannot bind public names inside local scopes")
|
||||
|
||||
|
||||
proc varDecl(self: Parser, isStatic: bool = true, isPrivate: bool = true): ASTNode =
|
||||
## Parses variable declarations
|
||||
self.checkDecl(isStatic, isPrivate)
|
||||
var varKind = self.peek(-1)
|
||||
var keyword = ""
|
||||
var value: ASTNode
|
||||
|
@ -792,10 +806,6 @@ proc varDecl(self: Parser, isStatic: bool = true, isPrivate: bool = true): ASTNo
|
|||
keyword = "constant"
|
||||
else:
|
||||
keyword = "variable"
|
||||
if not isPrivate and self.currentFunction != nil:
|
||||
self.error("cannot bind public names inside functions")
|
||||
elif not isPrivate and self.scopeDepth > 0:
|
||||
self.error("cannot bind public names in local scopes")
|
||||
self.expect(Identifier, &"expecting {keyword} name after '{varKind.lexeme}'")
|
||||
var name = newIdentExpr(self.peek(-1))
|
||||
if self.match(Equal):
|
||||
|
|
|
@ -52,8 +52,8 @@ proc simpleInstruction(instruction: OpCode, offset: int): int =
|
|||
return offset + 1
|
||||
|
||||
|
||||
proc stackInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on a single value on the stack
|
||||
proc stackTripleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on a single value on the stack using a 24-bit operand
|
||||
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
|
||||
printInstruction(instruction)
|
||||
stdout.write(&", points to stack index ")
|
||||
|
@ -63,8 +63,30 @@ proc stackInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
|||
return offset + 4
|
||||
|
||||
|
||||
proc stackDoubleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on a single value on the stack using a 16-bit operand
|
||||
var slot = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble()
|
||||
printInstruction(instruction)
|
||||
stdout.write(&", points to stack index ")
|
||||
setForegroundColor(fgYellow)
|
||||
stdout.write(&"{slot}")
|
||||
nl()
|
||||
return offset + 3
|
||||
|
||||
|
||||
proc argumentDoubleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on a hardcoded value value on the stack using a 16-bit operand
|
||||
var slot = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble()
|
||||
printInstruction(instruction)
|
||||
stdout.write(&", has argument ")
|
||||
setForegroundColor(fgYellow)
|
||||
stdout.write(&"{slot}")
|
||||
nl()
|
||||
return offset + 3
|
||||
|
||||
|
||||
proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
|
||||
## Debugs instructions that operate on a constant
|
||||
## Debugs instructions that operate on the constant table
|
||||
var constant = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
|
||||
printInstruction(instruction)
|
||||
stdout.write(&", points to constant at position ")
|
||||
|
@ -141,8 +163,12 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int =
|
|||
result = simpleInstruction(opcode, offset)
|
||||
of constantInstructions:
|
||||
result = constantInstruction(opcode, chunk, offset)
|
||||
of stackInstructions:
|
||||
result = stackInstruction(opcode, chunk, offset)
|
||||
of stackDoubleInstructions:
|
||||
result = stackDoubleInstruction(opcode, chunk, offset)
|
||||
of stackTripleInstructions:
|
||||
result = stackTripleInstruction(opcode, chunk, offset)
|
||||
of argumentDoubleInstructions:
|
||||
result = argumentDoubleInstruction(opcode, chunk, offset)
|
||||
of jumpInstructions:
|
||||
result = jumpInstruction(opcode, chunk, offset)
|
||||
of collectionInstructions:
|
||||
|
|
Loading…
Reference in New Issue