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:
Nocturn9x 2021-11-20 18:12:49 +01:00
parent 690d4d364a
commit a1698ef892
4 changed files with 108 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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