removed old compiler
This commit is contained in:
parent
57b3a4863e
commit
09a66b7306
52
src/nds.nim
52
src/nds.nim
|
@ -5,11 +5,6 @@ import ndspkg/compv2/emitter
|
||||||
import ndspkg/config
|
import ndspkg/config
|
||||||
import ndspkg/chunk
|
import ndspkg/chunk
|
||||||
|
|
||||||
when compilerChoice == cmOne:
|
|
||||||
import ndspkg/compiler/compiler
|
|
||||||
import ndspkg/compiler/types
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import strformat
|
import strformat
|
||||||
|
|
||||||
|
@ -17,39 +12,24 @@ type Result = enum
|
||||||
rsOK, rsCompileError, rsRuntimeError
|
rsOK, rsCompileError, rsRuntimeError
|
||||||
|
|
||||||
proc interpret(name: string, source: string): Result =
|
proc interpret(name: string, source: string): Result =
|
||||||
when compilerChoice == cmAst:
|
let parser = newParser(name, source)
|
||||||
let parser = newParser(name, source)
|
let node = parser.parse()
|
||||||
let node = parser.parse()
|
if parser.hadError:
|
||||||
|
return rsCompileError
|
||||||
|
when debugDumpAst:
|
||||||
echo $node
|
echo $node
|
||||||
elif compilerChoice == cmTwo:
|
let emitter = newEmitter(name, node)
|
||||||
let parser = newParser(name, source)
|
emitter.emit()
|
||||||
let node = parser.parse()
|
if emitter.hadError:
|
||||||
if parser.hadError:
|
return rsCompileError
|
||||||
return rsCompileError
|
when debugDumpChunk:
|
||||||
when debugDumpAst:
|
emitter.chunk.disassembleChunk()
|
||||||
echo $node
|
|
||||||
let emitter = newEmitter(name, node)
|
|
||||||
emitter.emit()
|
|
||||||
if emitter.hadError:
|
|
||||||
return rsCompileError
|
|
||||||
when debugDumpChunk:
|
|
||||||
emitter.chunk.disassembleChunk()
|
|
||||||
|
|
||||||
case emitter.chunk.run():
|
case emitter.chunk.run():
|
||||||
of irOK:
|
of irOK:
|
||||||
rsOK
|
rsOK
|
||||||
of irRuntimeError:
|
of irRuntimeError:
|
||||||
rsRuntimeError
|
rsRuntimeError
|
||||||
elif compilerChoice == cmOne:
|
|
||||||
let compiler = newCompiler(name, source)
|
|
||||||
compiler.compile()
|
|
||||||
if compiler.hadError:
|
|
||||||
return rsCompileError
|
|
||||||
case compiler.chunk.run():
|
|
||||||
of irOK:
|
|
||||||
rsOK
|
|
||||||
of irRuntimeError:
|
|
||||||
rsRuntimeError
|
|
||||||
|
|
||||||
|
|
||||||
proc repl =
|
proc repl =
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
import ../types/value
|
|
||||||
import bitops
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import precedence
|
|
||||||
|
|
||||||
# lists
|
|
||||||
|
|
||||||
proc parseList(comp: Compiler) =
|
|
||||||
var count: int
|
|
||||||
while comp.current.tokenType != tkRightBracket:
|
|
||||||
comp.expression()
|
|
||||||
count.inc()
|
|
||||||
if comp.current.tokenType != tkRightBracket:
|
|
||||||
comp.consume(tkComma, "Comma expected after list member.")
|
|
||||||
comp.consume(tkRightBracket, "Right bracket expected after list members.")
|
|
||||||
if count > argMax:
|
|
||||||
comp.error("Maximum list length exceeded.")
|
|
||||||
comp.writeChunk(1 - count, opCreateList)
|
|
||||||
comp.writeChunk(0, count.toDU8())
|
|
||||||
|
|
||||||
|
|
||||||
tkStartList.genRule(parseList, nop, pcNone)
|
|
||||||
|
|
||||||
# tables
|
|
||||||
|
|
||||||
proc parseTable(comp: Compiler) =
|
|
||||||
var count: int
|
|
||||||
while comp.current.tokenType notin {tkEof, tkRightBrace}:
|
|
||||||
if comp.match(tkLeftBracket):
|
|
||||||
comp.expression()
|
|
||||||
comp.consume(tkRightBracket, "Expect ']' after key.")
|
|
||||||
elif comp.match(tkIdentifier):
|
|
||||||
let ident = comp.previous.text.fromNimString()
|
|
||||||
comp.writeConstant(ident)
|
|
||||||
else:
|
|
||||||
comp.error("Key expected in table (perhaps you forgot to encapsulate the key with []?).")
|
|
||||||
if not comp.match(tkColon):
|
|
||||||
comp.consume(tkEqual, "Equal sign or colon expected after key.")
|
|
||||||
comp.expression()
|
|
||||||
count.inc()
|
|
||||||
if comp.current.tokenType != tkRightBrace:
|
|
||||||
comp.consume(tkComma, "Comma expected after key-value pair.")
|
|
||||||
|
|
||||||
comp.consume(tkRightBrace, "Right brace expected after table members.")
|
|
||||||
if count > argMax:
|
|
||||||
comp.error("Maximum table length exceeded.")
|
|
||||||
comp.writeChunk(1 - 2 * count, opCreateTable)
|
|
||||||
comp.writeChunk(0, count.toDU8())
|
|
||||||
|
|
||||||
tkStartTable.genRule(parseTable, nop, pcNone)
|
|
||||||
|
|
||||||
# len op
|
|
||||||
|
|
||||||
proc parseLen(comp: Compiler) =
|
|
||||||
comp.expression()
|
|
||||||
comp.writeChunk(0, opLen)
|
|
||||||
|
|
||||||
tkHashtag.genRule(parseLen, nop, pcNone)
|
|
||||||
|
|
||||||
# get/set index
|
|
||||||
|
|
||||||
proc parseIndex(comp: Compiler) =
|
|
||||||
# the index
|
|
||||||
comp.expression()
|
|
||||||
comp.consume(tkRightBracket, "Right bracket expected after index.")
|
|
||||||
if comp.match(tkEqual):
|
|
||||||
comp.parsePrecedence(pcNonAssignTop)
|
|
||||||
comp.writeChunk(-2, opSetIndex)
|
|
||||||
else:
|
|
||||||
comp.writeChunk(-1, opGetIndex)
|
|
||||||
|
|
||||||
tkLeftBracket.genRule(nop, parseIndex, pcIndex)
|
|
||||||
|
|
||||||
proc parseDotIndex(comp: Compiler) =
|
|
||||||
# the index
|
|
||||||
comp.consume(tkIdentifier, "Identifier expected after dot index.")
|
|
||||||
let index = comp.previous.text.fromNimString()
|
|
||||||
comp.writeConstant(index)
|
|
||||||
|
|
||||||
if comp.match(tkEqual):
|
|
||||||
comp.parsePrecedence(pcNonAssignTop)
|
|
||||||
comp.writeChunk(-2, opSetIndex)
|
|
||||||
else:
|
|
||||||
comp.writeChunk(-1, opGetIndex)
|
|
||||||
|
|
||||||
tkDot.genRule(nop, parseDotIndex, pcIndex)
|
|
|
@ -1,27 +0,0 @@
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
import ../config
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
|
|
||||||
# the following modify precedence by being imported
|
|
||||||
import expressions
|
|
||||||
import controlflow
|
|
||||||
import collections
|
|
||||||
import functions
|
|
||||||
import statement
|
|
||||||
|
|
||||||
proc compile*(comp: Compiler) =
|
|
||||||
comp.scanner = newScanner(comp.source)
|
|
||||||
comp.writeChunk(0, opNil)
|
|
||||||
# the starting stackIndex is 0, which points to this nil
|
|
||||||
# it is correctly set to delta = 0!!!
|
|
||||||
comp.advance()
|
|
||||||
while comp.current.tokenType != tkEof:
|
|
||||||
comp.statement()
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
comp.writeChunk(0, opReturn)
|
|
||||||
when debugDumpChunk:
|
|
||||||
if not comp.hadError:
|
|
||||||
comp.chunk.disassembleChunk()
|
|
|
@ -1,92 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import precedence
|
|
||||||
import jumps
|
|
||||||
|
|
||||||
proc ifExpr(comp: Compiler) =
|
|
||||||
# if expressions return the body if condition is truthy,
|
|
||||||
# the else expression otherwise, unless there is no else:
|
|
||||||
# if there is no else, it returns the condition if it is falsey
|
|
||||||
comp.consume(tkLeftParen, "Expect '(' after 'if'.")
|
|
||||||
comp.expression()
|
|
||||||
comp.consume(tkRightParen, "Expect ')' after condition.")
|
|
||||||
|
|
||||||
var thenStacklen = 0
|
|
||||||
let thenJump = comp.emitJump(0, opJumpIfFalse, thenStacklen)
|
|
||||||
|
|
||||||
# conditional code that can be jumped over must leave the stack in tact!
|
|
||||||
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
comp.expression()
|
|
||||||
# net change to stack: -1 + 1 = 0
|
|
||||||
|
|
||||||
var elseStacklen = 0
|
|
||||||
let elseJump = comp.emitJump(0, opJump, elseStacklen)
|
|
||||||
comp.patchJump(thenJump, thenStacklen)
|
|
||||||
|
|
||||||
if comp.match(tkElse):
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
comp.expression()
|
|
||||||
|
|
||||||
comp.patchJump(elseJump, elseStacklen)
|
|
||||||
|
|
||||||
|
|
||||||
tkIf.genRule(ifExpr, nop, pcNone)
|
|
||||||
|
|
||||||
proc andExpr(comp: Compiler) =
|
|
||||||
var stacklen = 0
|
|
||||||
let endJump = comp.emitJump(0, opJumpIfFalse, stacklen)
|
|
||||||
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
comp.parsePrecedence(pcAnd)
|
|
||||||
# net effect on stack: -1 + 1 = 0
|
|
||||||
|
|
||||||
comp.patchJump(endJump, stacklen)
|
|
||||||
|
|
||||||
tkAnd.genRule(nop, andExpr, pcAnd)
|
|
||||||
|
|
||||||
proc orExpr(comp: Compiler) =
|
|
||||||
var elseStacklen = 0
|
|
||||||
var endStacklen = 0
|
|
||||||
let elseJump = comp.emitJump(0, opJumpIfFalse, elseStacklen)
|
|
||||||
let endJump = comp.emitJump(0, opJump, endStacklen)
|
|
||||||
|
|
||||||
comp.patchJump(elseJump, elseStacklen)
|
|
||||||
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
comp.parsePrecedence(pcOr)
|
|
||||||
# net effect on stack: -1 + 1 = 0
|
|
||||||
|
|
||||||
comp.patchJump(endJump, endStacklen)
|
|
||||||
|
|
||||||
tkOr.genRule(nop, orExpr, pcOr)
|
|
||||||
|
|
||||||
proc parseWhile(comp: Compiler) =
|
|
||||||
|
|
||||||
comp.writeChunk(1, opNil) # return value
|
|
||||||
let loopStart = comp.chunk.len
|
|
||||||
let loopStacklen = comp.stackIndex
|
|
||||||
comp.consume(tkLeftParen, "Expect '(' after 'while'.")
|
|
||||||
# condition
|
|
||||||
comp.expression()
|
|
||||||
comp.consume(tkRightParen, "Expect ')' after condition.")
|
|
||||||
|
|
||||||
var exitStacklen = 0
|
|
||||||
let exitJump = comp.emitJump(-1, opJumpIfFalsePop, exitStacklen)
|
|
||||||
# this cannot be handled with just opPop, since the net change in the
|
|
||||||
# stack size inside code that is conditional must be 0!
|
|
||||||
|
|
||||||
# body
|
|
||||||
comp.writeChunk(-1, opPop) # pop the old return value
|
|
||||||
comp.expression()
|
|
||||||
# net stack change: 1 + -1 = 0
|
|
||||||
|
|
||||||
comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop, loopStacklen)
|
|
||||||
comp.patchJump(exitJump, exitStacklen)
|
|
||||||
|
|
||||||
tkWhile.genRule(parseWhile, nop, pcNone)
|
|
|
@ -1,109 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import strutils
|
|
||||||
import bitops # needed for value
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
import ../types/value
|
|
||||||
import ../config
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import precedence
|
|
||||||
|
|
||||||
# EXPRESSIONS
|
|
||||||
|
|
||||||
proc number(comp: Compiler) =
|
|
||||||
# assume the number is already advanced through
|
|
||||||
let value = comp.previous.text.parseFloat.fromFloat()
|
|
||||||
comp.writeConstant(value)
|
|
||||||
|
|
||||||
tkNumber.genRule(number, nop, pcNone)
|
|
||||||
|
|
||||||
proc expFalse(comp: Compiler) =
|
|
||||||
comp.writeChunk(1, opFalse)
|
|
||||||
|
|
||||||
tkFalse.genRule(expFalse, nop, pcNone)
|
|
||||||
|
|
||||||
proc expTrue(comp: Compiler) =
|
|
||||||
comp.writeChunk(1, opTrue)
|
|
||||||
|
|
||||||
tkTrue.genRule(expTrue, nop, pcNone)
|
|
||||||
|
|
||||||
proc expNil(comp: Compiler) =
|
|
||||||
comp.writeChunk(1, opNil)
|
|
||||||
|
|
||||||
tkNil.genRule(expNil, nop, pcNone)
|
|
||||||
|
|
||||||
proc expString(comp: Compiler) =
|
|
||||||
let value = comp.previous.text[1..^2].fromNimString()
|
|
||||||
comp.writeConstant(value)
|
|
||||||
|
|
||||||
tkString.genRule(expString, nop, pcNone)
|
|
||||||
|
|
||||||
proc unary(comp: Compiler) =
|
|
||||||
let opType = comp.previous.tokenType
|
|
||||||
|
|
||||||
comp.parsePrecedence(pcUnary)
|
|
||||||
|
|
||||||
case opType:
|
|
||||||
of tkMinus:
|
|
||||||
comp.writeChunk(0, opNegate)
|
|
||||||
of tkBang:
|
|
||||||
comp.writeChunk(0, opNot)
|
|
||||||
else:
|
|
||||||
discard # unreachable
|
|
||||||
|
|
||||||
tkBang.genRule(unary, nop, pcNone)
|
|
||||||
|
|
||||||
proc binary(comp: Compiler) =
|
|
||||||
let opType = comp.previous.tokenType
|
|
||||||
# safety checked in parsePrecedence
|
|
||||||
let rule = opType.getRule()
|
|
||||||
comp.parsePrecedence(rule.applyRule.increment)
|
|
||||||
|
|
||||||
case opType:
|
|
||||||
of tkPlus:
|
|
||||||
comp.writeChunk(-1, opAdd)
|
|
||||||
of tkMinus:
|
|
||||||
comp.writeChunk(-1, opSubtract)
|
|
||||||
of tkStar:
|
|
||||||
comp.writeChunk(-1, opMultiply)
|
|
||||||
of tkSlash:
|
|
||||||
comp.writeChunk(-1, opDivide)
|
|
||||||
of tkEqualEqual:
|
|
||||||
comp.writeChunk(-1, opEqual)
|
|
||||||
of tkBangEqual:
|
|
||||||
comp.writeChunk(-1, opEqual)
|
|
||||||
comp.writeChunk(0, opNot)
|
|
||||||
of tkGreater:
|
|
||||||
comp.writeChunk(-1, opGreater)
|
|
||||||
of tkLess:
|
|
||||||
comp.writeChunk(-1, opLess)
|
|
||||||
of tkGreaterEqual:
|
|
||||||
comp.writeChunk(-1, opLess)
|
|
||||||
comp.writeChunk(0, opNot)
|
|
||||||
of tkLessEqual:
|
|
||||||
comp.writeChunk(-1, opGreater)
|
|
||||||
comp.writeChunk(0, opNot)
|
|
||||||
else:
|
|
||||||
return # unreachable
|
|
||||||
|
|
||||||
tkMinus.genRule(unary, binary, pcTerm)
|
|
||||||
tkPlus.genRule(nop, binary, pcTerm)
|
|
||||||
tkSlash.genRule(nop, binary, pcFactor)
|
|
||||||
tkStar.genRule(nop, binary, pcFactor)
|
|
||||||
|
|
||||||
tkEqualEqual.genRule(nop, binary, pcEquality)
|
|
||||||
tkBangEqual.genRule(nop, binary, pcEquality)
|
|
||||||
tkGreater.genRule(nop, binary, pcComparison)
|
|
||||||
tkGreaterEqual.genRule(nop, binary, pcComparison)
|
|
||||||
tkLess.genRule(nop, binary, pcComparison)
|
|
||||||
tkLessEqual.genRule(nop, binary, pcComparison)
|
|
||||||
|
|
||||||
proc parseAmpersand(comp: Compiler) =
|
|
||||||
# just a simple expression separator
|
|
||||||
discard
|
|
||||||
|
|
||||||
tkAmpersand.genRule(nop, parseAmpersand, pcAmpersand)
|
|
|
@ -1,148 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import strformat
|
|
||||||
import bitops
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
import ../types/value
|
|
||||||
import ../config
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import precedence
|
|
||||||
import jumps
|
|
||||||
import scope
|
|
||||||
|
|
||||||
proc grouping(comp: Compiler) =
|
|
||||||
# assume initial '(' is already consumed
|
|
||||||
comp.expression()
|
|
||||||
comp.consume(tkRightParen, "Expect ')' after expression.")
|
|
||||||
|
|
||||||
proc parseArgs(comp: Compiler): int =
|
|
||||||
var argcount = 0
|
|
||||||
# put args on stack
|
|
||||||
while comp.current.tokenType notin {tkRightParen, tkEof}:
|
|
||||||
comp.expression()
|
|
||||||
inc argcount
|
|
||||||
if comp.current.tokenType != tkRightParen:
|
|
||||||
comp.consume(tkComma, "Expected ',' between arguments in function calls.")
|
|
||||||
comp.consume(tkRightParen, "Expected ')' after arguments in function calls.")
|
|
||||||
return argcount
|
|
||||||
|
|
||||||
|
|
||||||
proc parseCall(comp: Compiler) =
|
|
||||||
# ( consumed
|
|
||||||
|
|
||||||
let argcount = comp.parseArgs()
|
|
||||||
|
|
||||||
# emit call
|
|
||||||
comp.writeChunk(-argcount, opCall)
|
|
||||||
comp.writeChunk(0, argcount.uint8)
|
|
||||||
|
|
||||||
tkLeftParen.genRule(grouping, parseCall, pcCall)
|
|
||||||
|
|
||||||
proc parsePipeCall(comp: Compiler) =
|
|
||||||
# can be followed by:
|
|
||||||
# idents
|
|
||||||
# idents+indexes
|
|
||||||
# NOT calls, so the parsePrecedence after it should look for pcIndex at most
|
|
||||||
|
|
||||||
# get the function on the stack
|
|
||||||
comp.parsePrecedence(pcIndex)
|
|
||||||
|
|
||||||
# swap the function and the first arg
|
|
||||||
comp.writeChunk(0, opSwap)
|
|
||||||
|
|
||||||
var argcount = 1
|
|
||||||
if comp.match(tkLeftParen):
|
|
||||||
argcount = 1 + comp.parseArgs()
|
|
||||||
|
|
||||||
comp.writeChunk(-argcount, opCall)
|
|
||||||
comp.writeChunk(0, argcount.uint8)
|
|
||||||
|
|
||||||
tkDoublecolon.genRule(nop, parsePipeCall, pcAmpersand)
|
|
||||||
|
|
||||||
proc parseArrowCall(comp: Compiler) =
|
|
||||||
# -> syntactic sugar for table "methods"
|
|
||||||
|
|
||||||
# when called, the stack contains the table
|
|
||||||
# the table should be duplicated first
|
|
||||||
comp.writeChunk(1, opDup)
|
|
||||||
# then the index should be read
|
|
||||||
comp.consume(tkIdentifier, "Identifier (index) expected after '->'.")
|
|
||||||
let index = comp.previous.text.fromNimString()
|
|
||||||
comp.writeConstant(index)
|
|
||||||
comp.writeChunk(-1, opGetIndex)
|
|
||||||
# the stack's state now is table, method
|
|
||||||
# invert to get the right alignment for further function call
|
|
||||||
comp.writeChunk(0, opSwap)
|
|
||||||
# now the same code as :: applies
|
|
||||||
var argcount = 1
|
|
||||||
# args optional
|
|
||||||
if comp.match(tkLeftParen):
|
|
||||||
argcount = 1 + comp.parseArgs()
|
|
||||||
|
|
||||||
comp.writeChunk(-argcount, opCall)
|
|
||||||
comp.writeChunk(0, argcount.uint8)
|
|
||||||
|
|
||||||
|
|
||||||
tkArrow.genRule(nop, parseArrowCall, pcIndex)
|
|
||||||
|
|
||||||
proc parseFunct*(comp: Compiler) =
|
|
||||||
|
|
||||||
# jump over
|
|
||||||
var jumpStacklen = 0
|
|
||||||
let jumpOverBody = comp.emitJump(1, opFunctionDef, jumpStacklen)
|
|
||||||
|
|
||||||
comp.consume(tkLeftParen, "Expected '(' after keyword 'funct'.")
|
|
||||||
|
|
||||||
var params: seq[string]
|
|
||||||
# parameters
|
|
||||||
while comp.current.tokenType == tkIdentifier:
|
|
||||||
comp.advance()
|
|
||||||
params.add(comp.previous.text)
|
|
||||||
if comp.current.tokenType == tkRightParen:
|
|
||||||
break
|
|
||||||
comp.consume(tkComma, "Expected ',' to separate items in the parameter list.")
|
|
||||||
|
|
||||||
comp.consume(tkRightParen, "Expected ')' after parameter list.")
|
|
||||||
|
|
||||||
# function body:
|
|
||||||
let functII = comp.chunk.len
|
|
||||||
comp.beginScope(function = true) # this saves the old stackindex, sets it to 0, :function and :result at index 0
|
|
||||||
# assumption:
|
|
||||||
# the caller will create the following stack for the function to run in:
|
|
||||||
# [0] = return value placeholder
|
|
||||||
# [1] = arg #1
|
|
||||||
# [2] = arg #2
|
|
||||||
# [3] = arg #3
|
|
||||||
|
|
||||||
if params.len > shortArgMax:
|
|
||||||
comp.error("Too many parameters.")
|
|
||||||
|
|
||||||
comp.writeChunk(0, opCheckArity) # runtime arity check
|
|
||||||
comp.writeChunk(0, params.len.uint8)
|
|
||||||
|
|
||||||
for i in countup(1, params.len):
|
|
||||||
comp.stackIndex = i
|
|
||||||
comp.addLocal(params[i-1], 0)
|
|
||||||
comp.expression()
|
|
||||||
let shouldbeStackIndex = params.len + 1
|
|
||||||
if shouldbeStackIndex != comp.stackIndex:
|
|
||||||
comp.error(&"Assertion failed: wrong stackindex ({comp.stackIndex}) in function declaration (should be {shouldbeStackIndex}).")
|
|
||||||
let f = comp.endScope()
|
|
||||||
dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over
|
|
||||||
|
|
||||||
# end of function declaration:
|
|
||||||
comp.patchJump(jumpOverBody, jumpStacklen)
|
|
||||||
# closures are implemented in a way, where the vm pops the function from the stack
|
|
||||||
# and reads the upvalue details from the following bytes
|
|
||||||
if f.upvalues.len() > 0:
|
|
||||||
comp.writeChunk(0, opClosure)
|
|
||||||
comp.writeChunk(0, f.upvalues.len().toDU8())
|
|
||||||
for upval in f.upvalues:
|
|
||||||
comp.writeChunk(0, upval.index.toDU8())
|
|
||||||
comp.writeChunk(0, if upval.isLocal: 0'u8 else: 1'u8)
|
|
||||||
|
|
||||||
tkProc.genRule(parseFunct, nop, pcNone)
|
|
|
@ -1,40 +0,0 @@
|
||||||
import ../chunk
|
|
||||||
import ../config
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
|
|
||||||
# JUMP HELPERS
|
|
||||||
|
|
||||||
proc emitJump*(comp: Compiler, delta: int, op: OpCode, stacklen: var int): int =
|
|
||||||
# delta -> 0 if the jump does not pop
|
|
||||||
# delta -> -1 if the jump pops the condition from the stack
|
|
||||||
comp.writeChunk(delta, op)
|
|
||||||
comp.writeChunk(0, 0xffffff.toDU8)
|
|
||||||
stacklen = comp.stackIndex
|
|
||||||
comp.chunk.len - argSize
|
|
||||||
|
|
||||||
proc patchJump*(comp: Compiler, offset: int, stacklen: int) =
|
|
||||||
if comp.stackIndex != stacklen:
|
|
||||||
comp.error("Assertion failed: loop doesn't preserve stackindex.")
|
|
||||||
let jump = (comp.chunk.len - offset - argSize)
|
|
||||||
|
|
||||||
if (jump > argMax):
|
|
||||||
comp.error("Too much code to jump over.")
|
|
||||||
|
|
||||||
let jumpt = jump.toDU8
|
|
||||||
|
|
||||||
comp.chunk.code[offset] = jumpt[0]
|
|
||||||
comp.chunk.code[offset + 1] = jumpt[1]
|
|
||||||
|
|
||||||
proc emitLoop*(comp: Compiler, loopstart: int, delta: int, op: OpCode, stacklen: int) =
|
|
||||||
if comp.stackIndex != stacklen:
|
|
||||||
comp.error("Assertion failed: loop doesn't preserve stackindex.")
|
|
||||||
comp.writeChunk(delta, op)
|
|
||||||
|
|
||||||
let offset = comp.chunk.len - loopstart + argSize
|
|
||||||
if offset > argMax:
|
|
||||||
comp.error("Loop body too large.")
|
|
||||||
|
|
||||||
comp.writeChunk(0, offset.toDU8)
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import sugar
|
|
||||||
import strformat
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../types/value
|
|
||||||
import ../config
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
|
|
||||||
# PARSE RULE/PRECEDENCE MISC
|
|
||||||
proc nop*(comp: Compiler) = discard
|
|
||||||
|
|
||||||
var rules*: array[TokenType, ParseRule]
|
|
||||||
template genRule*(ttype: TokenType, tprefix: (Compiler) -> void, tinfix: (Compiler) -> void, tprec: Precedence) =
|
|
||||||
if tprec == pcUnary:
|
|
||||||
raise newException(Exception, "pcUnary cannot be used as a rule precedence! Use pcNone for unary-only rules!")
|
|
||||||
elif tprec == pcPrimary:
|
|
||||||
raise newException(Exception, "Invalid rule: pcPrimary cannot be used for binary operators, if this rule is for a primary value, use pcNone!")
|
|
||||||
elif tprec in {pcNonAssignTop, pcExprTop}:
|
|
||||||
raise newException(Exception, "Invalid rule: a top pc is just a placeholder")
|
|
||||||
elif tprec == pcNone and tinfix != nop:
|
|
||||||
raise newException(Exception, "Invalid rule: pcNone only allowed for unary operators and primary values, not for infix ones!")
|
|
||||||
rules[ttype] = ParseRule(name: $ttype, prefix: tprefix, infix: tinfix, prec: tprec)
|
|
||||||
|
|
||||||
for i in TokenType:
|
|
||||||
genRule(i, nop, nop, pcNone)
|
|
||||||
|
|
||||||
proc getRule*(opType: TokenType): ParseRule =
|
|
||||||
rules[opType]
|
|
||||||
|
|
||||||
proc applyRule*(rule: ParseRule): Precedence =
|
|
||||||
# returns the rule's precedence
|
|
||||||
rule.prec
|
|
||||||
|
|
||||||
proc increment*(prec: Precedence): Precedence =
|
|
||||||
# increases precedence by one
|
|
||||||
if prec == pcPrimary:
|
|
||||||
raise newException(Exception, "Invalid ruletable, pcPrimary precedence increment attempted.")
|
|
||||||
else:
|
|
||||||
Precedence(int(prec) + 1)
|
|
||||||
|
|
||||||
proc `<=`*(a, b: Precedence): bool =
|
|
||||||
int(a) <= int(b)
|
|
||||||
|
|
||||||
proc parsePrecedence*(comp: Compiler, prec: Precedence) =
|
|
||||||
comp.advance()
|
|
||||||
let rule = comp.previous.tokenType.getRule()
|
|
||||||
|
|
||||||
if rule.prefix != nop:
|
|
||||||
comp.canAssign = prec <= pcAssignment
|
|
||||||
|
|
||||||
rule.prefix(comp)
|
|
||||||
|
|
||||||
while prec <= comp.current.tokenType.getRule().prec:
|
|
||||||
comp.advance()
|
|
||||||
# checked for isSome in the loop
|
|
||||||
# since advance moves current to previous
|
|
||||||
if comp.previous.tokenType.getRule().infix == nop:
|
|
||||||
# should never happen, as having a precedence set
|
|
||||||
# means that it is a binary op
|
|
||||||
comp.error("Invalid rule table.")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
let infixRule = comp.previous.tokenType.getRule().infix
|
|
||||||
infixRule(comp)
|
|
||||||
else:
|
|
||||||
comp.error(&"Expect expression, got {($comp.previous.tokenType)[2..^1]}.")
|
|
||||||
|
|
||||||
proc expression*(comp: Compiler) =
|
|
||||||
## The lowest precedence among the pratt-parsed expressions
|
|
||||||
|
|
||||||
# DO NOT ADD ANYTHING HERE
|
|
||||||
# only use the pratt table for parsing expressions!
|
|
||||||
when assertionsVM:
|
|
||||||
let oldStackIndex = comp.stackIndex
|
|
||||||
comp.parsePrecedence(pcExprTop)
|
|
||||||
when assertionsVM:
|
|
||||||
let diff = comp.stackIndex - oldStackIndex
|
|
||||||
if diff != 1:
|
|
||||||
comp.error(&"Assertion failed: expression increased ({oldStackIndex} -> {comp.stackIndex}) the stack index by {diff} (should be 1).")
|
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
import strformat
|
|
||||||
import sequtils
|
|
||||||
import sugar
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
import ../config
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import jumps
|
|
||||||
|
|
||||||
|
|
||||||
# SCOPE HELPERS
|
|
||||||
proc beginScope*(comp: Compiler, function: bool = false) =
|
|
||||||
let scope = comp.newScope(function)
|
|
||||||
|
|
||||||
if not function:
|
|
||||||
while comp.match(tkLabel):
|
|
||||||
let label = comp.previous.text[1..^1]
|
|
||||||
scope.labels.add(label)
|
|
||||||
# only put the opNil if it's not a function scope, since
|
|
||||||
# function scopes are initialized by the caller
|
|
||||||
comp.writeChunk(1, opNil)
|
|
||||||
else:
|
|
||||||
# if it's a function scope, the frame will move
|
|
||||||
# access to outside locals is also limited to upvalues and closures
|
|
||||||
comp.stackIndex = 0
|
|
||||||
|
|
||||||
for label in scope.labels:
|
|
||||||
comp.addLocal(&":{label}", delta = 0)
|
|
||||||
|
|
||||||
proc scopeRetIndex*(comp: Compiler): int =
|
|
||||||
let scope = comp.scopes[comp.scopes.high()]
|
|
||||||
# this is an illegal operation for function scopes, as they work differently
|
|
||||||
if scope.function:
|
|
||||||
comp.error("Assertion failed, Internal error, scopeRetIndex calculation for a function scope.")
|
|
||||||
return scope.goalStackIndex
|
|
||||||
|
|
||||||
proc restore*(comp: Compiler, scope: Scope) =
|
|
||||||
let delta = comp.stackIndex - scope.goalStackIndex
|
|
||||||
comp.writePops(delta)
|
|
||||||
if not comp.stackIndex == scope.goalStackIndex:
|
|
||||||
comp.error("Assertion failed in restore")
|
|
||||||
|
|
||||||
proc restoreInFunct*(comp: Compiler, scope: Scope) =
|
|
||||||
#let pops = comp.stackIndex
|
|
||||||
#comp.writePops(pops)
|
|
||||||
|
|
||||||
comp.stackIndex = scope.goalStackIndex
|
|
||||||
#when debugCompiler:
|
|
||||||
# debugEcho &"Restored function scope: delta {pops}; new stackindex: {comp.stackIndex}"
|
|
||||||
|
|
||||||
proc jumpToEnd*(comp: Compiler, scope: Scope) =
|
|
||||||
## Jumps to the end of scope, does not affect stackIndex
|
|
||||||
var delta: int
|
|
||||||
if scope.function:
|
|
||||||
delta = comp.stackIndex
|
|
||||||
else:
|
|
||||||
delta = comp.stackIndex - scope.goalStackIndex
|
|
||||||
comp.writePops(delta)
|
|
||||||
var s = 0 # discard the saved stack length
|
|
||||||
let jmp = comp.emitJump(delta, opJump, s)
|
|
||||||
scope.jumps.add(jmp)
|
|
||||||
|
|
||||||
proc endScope*(comp: Compiler): Scope =
|
|
||||||
# remove locals
|
|
||||||
let popped = comp.scopes.pop()
|
|
||||||
if popped.parentFunction == popped:
|
|
||||||
popped.parentFunction = nil # cleanup cycles
|
|
||||||
let function = popped.function
|
|
||||||
# close upvalues
|
|
||||||
var i = comp.locals.high()
|
|
||||||
while i >= 0:
|
|
||||||
let local = comp.locals[i]
|
|
||||||
if local.scope == popped and local.captured:
|
|
||||||
comp.writeChunk(0, opCloseUpvalue)
|
|
||||||
comp.writeChunk(0, local.index.toDU8())
|
|
||||||
if local.depth < comp.scopes.len():
|
|
||||||
break
|
|
||||||
i.dec()
|
|
||||||
|
|
||||||
# restore the stackIndex, emit pops
|
|
||||||
if function:
|
|
||||||
comp.restoreInFunct(popped)
|
|
||||||
else:
|
|
||||||
comp.restore(popped)
|
|
||||||
# patch jumps to after the scope (such jumps from breaks emit the pops before jumping)
|
|
||||||
for jump in popped.jumps:
|
|
||||||
if function:
|
|
||||||
# all jumps should only happen to named block expressions
|
|
||||||
comp.error("Assertion failed: jump attempt to end function.")
|
|
||||||
comp.patchJump(jump, popped.goalStackIndex)
|
|
||||||
if function:
|
|
||||||
comp.writeChunk(0, opReturn)
|
|
||||||
# remove locals from the comp object that were of this scope
|
|
||||||
comp.locals.keepIf((it) => it.depth < comp.scopes.len())
|
|
||||||
popped
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import precedence
|
|
||||||
import scope
|
|
||||||
import variables
|
|
||||||
|
|
||||||
# below are the expressions that can contain statements in some way
|
|
||||||
# the only expressions that can contain a statement are:
|
|
||||||
# the block expression
|
|
||||||
proc statement*(comp: Compiler)
|
|
||||||
|
|
||||||
proc parseBlock(comp: Compiler) =
|
|
||||||
## Despite the name, can be used for statements if the arg statement is true
|
|
||||||
## Also can be used for function bodies
|
|
||||||
comp.beginScope()
|
|
||||||
while comp.current.tokenType != tkRightBrace and comp.current.tokenType != tkEof:
|
|
||||||
comp.statement()
|
|
||||||
discard comp.endScope()
|
|
||||||
|
|
||||||
comp.consume(tkRightBrace, "Expect '}' after block.")
|
|
||||||
|
|
||||||
tkLeftBrace.genRule(parseBlock, nop, pcNone)
|
|
||||||
|
|
||||||
# statements
|
|
||||||
|
|
||||||
proc breakStatement(comp: Compiler) =
|
|
||||||
if not comp.match(tkLabel):
|
|
||||||
comp.error("Label expected after break.")
|
|
||||||
|
|
||||||
let label = comp.previous.text[1..^1]
|
|
||||||
for i in countdown(comp.scopes.high, 0):
|
|
||||||
let scope = comp.scopes[i]
|
|
||||||
if scope.labels.contains(label):
|
|
||||||
comp.jumpToEnd(scope)
|
|
||||||
break
|
|
||||||
|
|
||||||
comp.consume(tkSemicolon, "Semicolon expected after break statement.")
|
|
||||||
if comp.current.tokenType != tkRightBrace:
|
|
||||||
comp.error("Break statement must be the last element inside the innermost block it is in.")
|
|
||||||
|
|
||||||
proc statement*(comp: Compiler) =
|
|
||||||
if comp.match(tkVar):
|
|
||||||
comp.varStatement()
|
|
||||||
comp.consume(tkSemicolon, "Semicolon expected after expression statement.")
|
|
||||||
# elif comp.match(tkDef):
|
|
||||||
# comp.procStatement()
|
|
||||||
# comp.consume(tkSemicolon, "Semicolon expected after procedure declaration.")
|
|
||||||
elif comp.match(tkBreak):
|
|
||||||
comp.breakStatement()
|
|
||||||
else:
|
|
||||||
comp.expression()
|
|
||||||
if comp.current.tokenType == tkRightBrace:
|
|
||||||
# last expression in the block expression -> semicolon optional
|
|
||||||
comp.writeChunk(0, opSetLocal)
|
|
||||||
comp.writeChunk(0, comp.scopeRetIndex().toDU8())
|
|
||||||
else:
|
|
||||||
comp.consume(tkSemicolon, "Semicolon expected after expression statement.")
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
|
|
||||||
if comp.panicMode:
|
|
||||||
comp.synchronize()
|
|
|
@ -1,91 +0,0 @@
|
||||||
import sugar
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
|
|
||||||
type
|
|
||||||
Local* = ref object
|
|
||||||
name*: string # name of this local
|
|
||||||
index*: int # what is its index in the stack (0 is the stack bottom - nil in the main function)
|
|
||||||
depth*: int # depth of this local
|
|
||||||
# if depth is -1, the variable cannot be referenced yet
|
|
||||||
# its depth will be set once its first ever value is determined
|
|
||||||
scope*: Scope # the innermost scope of this local
|
|
||||||
captured*: bool
|
|
||||||
|
|
||||||
Upvalue* = ref object
|
|
||||||
index*: int
|
|
||||||
isLocal*: bool
|
|
||||||
|
|
||||||
Scope* = ref object
|
|
||||||
labels*: seq[string]
|
|
||||||
goalStackIndex*: int # the stack count it started with plus 1
|
|
||||||
jumps*: seq[int] # jumps to be patched that jump to the end
|
|
||||||
function*: bool # if true, it is a function
|
|
||||||
parentFunction*: Scope # if not a function, which scope is the innermost function it's in (if it's a function this points to itself)
|
|
||||||
# if parentFunction is nil, the scope is not inside a function
|
|
||||||
upvalues*: seq[Upvalue] # only needed for functions
|
|
||||||
|
|
||||||
Compiler* = ref object
|
|
||||||
#
|
|
||||||
# input
|
|
||||||
#
|
|
||||||
scanner*: Scanner
|
|
||||||
source*: string
|
|
||||||
#
|
|
||||||
# state
|
|
||||||
#
|
|
||||||
previous*: Token
|
|
||||||
current*: Token
|
|
||||||
canAssign*: bool
|
|
||||||
locals*: seq[Local]
|
|
||||||
scopes*: seq[Scope]
|
|
||||||
stackIndex*: int # how large the stack is
|
|
||||||
# when there's an error both are set
|
|
||||||
# panic mode can be turned off e.g. at block boundaries
|
|
||||||
panicMode*: bool
|
|
||||||
#
|
|
||||||
# output
|
|
||||||
#
|
|
||||||
chunk*: Chunk
|
|
||||||
hadError*: bool
|
|
||||||
|
|
||||||
Precedence* = enum
|
|
||||||
pcNone, pcExprTop, pcAmpersand, pcAssignment, pcNonAssignTop, pcOr, pcAnd, pcEquality, pcComparison,
|
|
||||||
pcTerm, pcFactor, pcUnary, pcCall, pcIndex, pcPrimary
|
|
||||||
# pcUnary applies to all prefix operators regardless of this enum's value
|
|
||||||
# changing pcUnary's position can change the priority of all unary ops
|
|
||||||
#
|
|
||||||
# Note: unary only rules should have precedence pcNone!!!
|
|
||||||
# pcExprTop, pcNonAssignTop are special placeholders!
|
|
||||||
|
|
||||||
ParseRule* = object
|
|
||||||
name*: string # debug purposes only
|
|
||||||
prefix*: (Compiler) -> void
|
|
||||||
infix*: (Compiler) -> void
|
|
||||||
prec*: Precedence # only relevant to infix, prefix always has pcUnary
|
|
||||||
|
|
||||||
|
|
||||||
proc newScope*(comp: Compiler, function: bool): Scope =
|
|
||||||
result.new()
|
|
||||||
#result.depth = comp.scopes.len + 1
|
|
||||||
result.function = function
|
|
||||||
result.goalStackIndex = comp.stackIndex + 1
|
|
||||||
if function:
|
|
||||||
result.parentFunction = result
|
|
||||||
elif comp.scopes.len() > 0 and comp.scopes[comp.scopes.high()].parentFunction != nil:
|
|
||||||
result.parentFunction = comp.scopes[comp.scopes.high()].parentFunction
|
|
||||||
|
|
||||||
comp.scopes.add(result)
|
|
||||||
|
|
||||||
# HELPERS FOR THE COMPILER TYPE
|
|
||||||
|
|
||||||
proc newCompiler*(name: string, source: string): Compiler =
|
|
||||||
result = new(Compiler)
|
|
||||||
result.chunk = initChunk(name)
|
|
||||||
result.source = source
|
|
||||||
result.hadError = false
|
|
||||||
result.panicMode = false
|
|
||||||
result.canAssign = true
|
|
||||||
result.locals = @[]
|
|
||||||
result.scopes = @[]
|
|
|
@ -1,95 +0,0 @@
|
||||||
import strformat
|
|
||||||
|
|
||||||
import ../chunk
|
|
||||||
import ../scanner
|
|
||||||
import ../config
|
|
||||||
import ../types/value
|
|
||||||
|
|
||||||
import types
|
|
||||||
|
|
||||||
proc errorAt*(comp: Compiler, line: int, msg: string, at: string = "") =
|
|
||||||
if comp.panicMode:
|
|
||||||
return
|
|
||||||
write stderr, &"[line {line}] Error "
|
|
||||||
if at.len > 0:
|
|
||||||
write stderr, &"at {at} "
|
|
||||||
write stderr, msg
|
|
||||||
write stderr, "\n"
|
|
||||||
comp.hadError = true
|
|
||||||
comp.panicMode = true
|
|
||||||
|
|
||||||
proc error*(comp: Compiler, msg: string) =
|
|
||||||
## create a simple error message
|
|
||||||
comp.errorAt(comp.previous.line, msg)
|
|
||||||
|
|
||||||
proc errorAtCurrent*(comp: Compiler, msg: string) =
|
|
||||||
comp.errorAt(comp.current.line, msg)
|
|
||||||
|
|
||||||
proc advance*(comp: Compiler) =
|
|
||||||
comp.previous = comp.current
|
|
||||||
while true:
|
|
||||||
comp.current = comp.scanner.scanToken()
|
|
||||||
if (comp.current.tokenType != tkError):
|
|
||||||
break
|
|
||||||
comp.errorAtCurrent(comp.current.text)
|
|
||||||
|
|
||||||
proc match*(comp: Compiler, tokenType: TokenType): bool =
|
|
||||||
if comp.current.tokenType == tokenType:
|
|
||||||
comp.advance()
|
|
||||||
true
|
|
||||||
else:
|
|
||||||
false
|
|
||||||
|
|
||||||
proc consume*(comp: Compiler, tokenType: TokenType, msg: string) =
|
|
||||||
if comp.current.tokenType == tokenType:
|
|
||||||
comp.advance()
|
|
||||||
else:
|
|
||||||
comp.errorAtCurrent(msg)
|
|
||||||
|
|
||||||
proc synchronize*(comp: Compiler) =
|
|
||||||
comp.panicMode = false
|
|
||||||
while comp.current.tokenType != tkEof:
|
|
||||||
if comp.previous.tokenType in {tkSemicolon, tkRightBrace}:
|
|
||||||
return
|
|
||||||
if comp.current.tokenType in {tkProc, tkVar, tkFor, tkIf, tkWhile}:
|
|
||||||
return
|
|
||||||
comp.advance()
|
|
||||||
|
|
||||||
proc writeChunk*(comp: Compiler, dStackIndex: int, ch: OpCode | DoubleUint8 | uint8) =
|
|
||||||
comp.stackIndex += dStackIndex
|
|
||||||
comp.chunk.writeChunk(ch, comp.previous.line)
|
|
||||||
|
|
||||||
proc writePops*(comp: Compiler, count: int) =
|
|
||||||
if count > argMax:
|
|
||||||
comp.error("Too many local variables in block.")
|
|
||||||
if count == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
if count == 1:
|
|
||||||
comp.writeChunk(-1, opPop)
|
|
||||||
elif count < shortArgMax:
|
|
||||||
comp.writeChunk(-count, opPopSA)
|
|
||||||
comp.writeChunk(0, count.uint8)
|
|
||||||
else:
|
|
||||||
comp.writeChunk(-count, opPopA)
|
|
||||||
comp.writeChunk(0, count.toDU8())
|
|
||||||
|
|
||||||
|
|
||||||
proc writeConstant*(comp: Compiler, constant: NdValue) =
|
|
||||||
comp.stackIndex.inc
|
|
||||||
let index = comp.chunk.writeConstant(constant, comp.previous.line)
|
|
||||||
if index >= argMax:
|
|
||||||
comp.error("Too many constants in one chunk.")
|
|
||||||
|
|
||||||
|
|
||||||
proc addLocal*(comp: Compiler, name: string, delta: int) =
|
|
||||||
if comp.locals.len >= argMax:
|
|
||||||
comp.error("Too many local variables in function.")
|
|
||||||
|
|
||||||
# if delta is 0 or negative, it means that it is already on the stack when addLocal is called
|
|
||||||
# if delta is positive, the first ever value of the local is to the right
|
|
||||||
comp.locals.add(Local(name: name, depth: if delta > 0: -1 else: comp.scopes.high, index: comp.stackIndex + delta, scope: comp.scopes[comp.scopes.high()], captured: false))
|
|
||||||
|
|
||||||
proc markInitialized*(comp: Compiler) =
|
|
||||||
comp.locals[comp.locals.high].depth = comp.scopes.high
|
|
||||||
|
|
|
@ -1,177 +0,0 @@
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import bitops # needed for value
|
|
||||||
|
|
||||||
import ../scanner
|
|
||||||
import ../chunk
|
|
||||||
import ../types/value
|
|
||||||
|
|
||||||
import types
|
|
||||||
import utils
|
|
||||||
import precedence
|
|
||||||
import functions
|
|
||||||
|
|
||||||
# UTILS
|
|
||||||
|
|
||||||
proc addUpvalue(comp: Compiler, local: Local): int =
|
|
||||||
## argument: local
|
|
||||||
## This proc takes an index to a local in the locals table
|
|
||||||
## and creates an upvalue in every function up until the one
|
|
||||||
## including this local so that all of the function scopes in between
|
|
||||||
## have the right upvalue in them (compile time)
|
|
||||||
##
|
|
||||||
## does not create duplicates: at each layer it will first find existing ones
|
|
||||||
## and create references to that further down the line
|
|
||||||
|
|
||||||
template lenCheck(scope: Scope, blk: untyped) =
|
|
||||||
if scope.upvalues.len() >= argMax:
|
|
||||||
comp.error("Too many closure variables in function.")
|
|
||||||
blk
|
|
||||||
|
|
||||||
var scopeIndex = local.depth + 1
|
|
||||||
# +1 must be here, because if scope the local is in is a function
|
|
||||||
# then the upvalues should only be in child functions
|
|
||||||
var isLocal = true # local means that it's the outermost function that's closing it
|
|
||||||
var upvalIndex: int = local.index
|
|
||||||
while scopeIndex < comp.scopes.len():
|
|
||||||
let scope = comp.scopes[scopeIndex]
|
|
||||||
if scope.function:
|
|
||||||
scope.lenCheck():
|
|
||||||
return 0
|
|
||||||
block ensure: # exiting this block means that the upvalueIndex is updated and points to the upvalue within scope
|
|
||||||
for i in countup(0, scope.upvalues.high()):
|
|
||||||
let upval = scope.upvalues[i]
|
|
||||||
if upval.index == upvalIndex and upval.isLocal == isLocal:
|
|
||||||
upvalIndex = i
|
|
||||||
break ensure
|
|
||||||
scope.upvalues.add(Upvalue(index: upvalIndex, isLocal: isLocal))
|
|
||||||
upvalIndex = scope.upvalues.high()
|
|
||||||
isLocal = false
|
|
||||||
scopeIndex.inc
|
|
||||||
return upvalIndex
|
|
||||||
|
|
||||||
proc resolveLocal(comp: Compiler, name: string): tuple[index: int, upvalue: bool] =
|
|
||||||
## the bool arg specifies whether it found an upvalue
|
|
||||||
## if it's a local: returns the stack index of the local of the name
|
|
||||||
## if it's an upvalue: returns the upvalue index
|
|
||||||
## if the number is -1, then the name cannot be resolved at compile time
|
|
||||||
var i = comp.locals.high
|
|
||||||
let cfunc: Scope = if comp.scopes.len() > 0: comp.scopes[comp.scopes.high()].parentFunction else: nil
|
|
||||||
while i >= 0:
|
|
||||||
let local = comp.locals[i]
|
|
||||||
i.dec
|
|
||||||
if local.name == name:
|
|
||||||
if local.depth == -1:
|
|
||||||
continue
|
|
||||||
let upvalue = local.scope.parentFunction != cfunc
|
|
||||||
if not upvalue:
|
|
||||||
return (local.index, false)
|
|
||||||
else:
|
|
||||||
# resolveUpvalue
|
|
||||||
local.captured = true
|
|
||||||
return (comp.addUpvalue(local), true)
|
|
||||||
return (index: -1, upvalue: false)
|
|
||||||
|
|
||||||
# EXPRESSIONS
|
|
||||||
|
|
||||||
proc variable(comp: Compiler) =
|
|
||||||
# named variable
|
|
||||||
var getOp = opGetGlobal
|
|
||||||
var setOp = opSetGlobal
|
|
||||||
|
|
||||||
let name = comp.previous.text
|
|
||||||
|
|
||||||
# try resolving local, set arg to the index on the stack
|
|
||||||
var (arg, upval) = comp.resolveLocal(name)
|
|
||||||
|
|
||||||
if arg != -1:
|
|
||||||
if upval:
|
|
||||||
getOp = opGetUpvalue
|
|
||||||
setOp = opSetUpvalue
|
|
||||||
else:
|
|
||||||
# local
|
|
||||||
getOp = opGetLocal
|
|
||||||
setOp = opSetLocal
|
|
||||||
else:
|
|
||||||
# global
|
|
||||||
arg = comp.chunk.addConstant(name.fromNimString())
|
|
||||||
|
|
||||||
if comp.match(tkEqual):
|
|
||||||
# assignment (global/local)
|
|
||||||
if not comp.canAssign:
|
|
||||||
comp.error("Invalid assignment target.")
|
|
||||||
return
|
|
||||||
comp.parsePrecedence(pcAssignment)
|
|
||||||
comp.writeChunk(0, setOp)
|
|
||||||
else:
|
|
||||||
# get (global/local)
|
|
||||||
comp.writeChunk(1, getOp)
|
|
||||||
|
|
||||||
comp.writeChunk(0, arg.toDU8)
|
|
||||||
|
|
||||||
tkIdentifier.genRule(variable, nop, pcNone)
|
|
||||||
|
|
||||||
# VAR STATEMENT, PROC STATEMENT
|
|
||||||
|
|
||||||
proc parseVariable(comp: Compiler, msg: string): int =
|
|
||||||
## Parses variable declarations
|
|
||||||
## During manipulation with variables:
|
|
||||||
## if global:
|
|
||||||
## consume the identifier and return index to work with
|
|
||||||
## if local:
|
|
||||||
## register the name with the vm
|
|
||||||
comp.consume(tkIdentifier, msg)
|
|
||||||
let name = comp.previous.text
|
|
||||||
|
|
||||||
if name[0] in {':'}:
|
|
||||||
comp.error("Illegal variable name.")
|
|
||||||
|
|
||||||
if comp.scopes.len > 0: # declareVariable
|
|
||||||
# local
|
|
||||||
|
|
||||||
# check if name exists already within scope
|
|
||||||
for i in countdown(comp.locals.high, 0):
|
|
||||||
let local = comp.locals[i]
|
|
||||||
if local.depth != -1 and local.depth < comp.scopes.len:
|
|
||||||
break
|
|
||||||
|
|
||||||
if name == local.name:
|
|
||||||
comp.error("Already a variable with this name in this scope.")
|
|
||||||
break
|
|
||||||
|
|
||||||
comp.addLocal(name, 1)
|
|
||||||
0 # index to the constant is irrelevant if the var is local
|
|
||||||
else:
|
|
||||||
# global
|
|
||||||
comp.chunk.addConstant(name.fromNimString())
|
|
||||||
|
|
||||||
proc defineVariable(comp: Compiler, index: int) =
|
|
||||||
## Generate code that moves the variable on the stack
|
|
||||||
## to a variable at the right place in memory
|
|
||||||
## the right place is defined by the 3 following byte after the op
|
|
||||||
## the thing to move is the item below it
|
|
||||||
|
|
||||||
if comp.scopes.len > 0:
|
|
||||||
# local variable: it's already on the right place
|
|
||||||
# but we need to mark initialized
|
|
||||||
comp.markInitialized()
|
|
||||||
else:
|
|
||||||
comp.writeChunk(-1, opDefineGlobal)
|
|
||||||
comp.writeChunk(0, index.toDU8)
|
|
||||||
|
|
||||||
proc varStatement*(comp: Compiler) =
|
|
||||||
let globalIndex = comp.parseVariable("Expect variable name.")
|
|
||||||
|
|
||||||
if comp.match(tkEqual):
|
|
||||||
comp.expression()
|
|
||||||
else:
|
|
||||||
comp.writeChunk(1, opNil)
|
|
||||||
|
|
||||||
comp.defineVariable(globalIndex)
|
|
||||||
|
|
||||||
proc procStatement*(comp: Compiler) =
|
|
||||||
let globalIndex = comp.parseVariable("Expect procedure name.")
|
|
||||||
|
|
||||||
comp.parseFunct()
|
|
||||||
|
|
||||||
comp.defineVariable(globalIndex)
|
|
|
@ -12,14 +12,6 @@ const boundsChecks* = defined(debug) or defined(release)
|
||||||
const profileInstructions* = defined(ndsprofile) # if true, the time spent on every opcode is measured
|
const profileInstructions* = defined(ndsprofile) # if true, the time spent on every opcode is measured
|
||||||
const debugClosures* = defined(debug) # specific closure debug switches
|
const debugClosures* = defined(debug) # specific closure debug switches
|
||||||
|
|
||||||
type compMode* = enum
|
|
||||||
cmOne, cmAst, cmTwo
|
|
||||||
const compilerChoice* = cmTwo
|
|
||||||
# choose a compiler: cmOne - version 1, deprecated
|
|
||||||
# cmAst - version 2, but only use parser and print AST produced
|
|
||||||
# cmOne will be removed once compv2 is done
|
|
||||||
# cmTwo - compv2 + execute
|
|
||||||
|
|
||||||
# choose a line editor for the repl
|
# choose a line editor for the repl
|
||||||
const lineEditor = leRdstdin
|
const lineEditor = leRdstdin
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue