removed old compiler

This commit is contained in:
prod2 2022-12-03 20:05:59 +01:00
parent 57b3a4863e
commit 09a66b7306
14 changed files with 16 additions and 1162 deletions

View File

@ -5,11 +5,6 @@ import ndspkg/compv2/emitter
import ndspkg/config
import ndspkg/chunk
when compilerChoice == cmOne:
import ndspkg/compiler/compiler
import ndspkg/compiler/types
import os
import strformat
@ -17,39 +12,24 @@ type Result = enum
rsOK, rsCompileError, rsRuntimeError
proc interpret(name: string, source: string): Result =
when compilerChoice == cmAst:
let parser = newParser(name, source)
let node = parser.parse()
let parser = newParser(name, source)
let node = parser.parse()
if parser.hadError:
return rsCompileError
when debugDumpAst:
echo $node
elif compilerChoice == cmTwo:
let parser = newParser(name, source)
let node = parser.parse()
if parser.hadError:
return rsCompileError
when debugDumpAst:
echo $node
let emitter = newEmitter(name, node)
emitter.emit()
if emitter.hadError:
return rsCompileError
when debugDumpChunk:
emitter.chunk.disassembleChunk()
let emitter = newEmitter(name, node)
emitter.emit()
if emitter.hadError:
return rsCompileError
when debugDumpChunk:
emitter.chunk.disassembleChunk()
case emitter.chunk.run():
of irOK:
rsOK
of irRuntimeError:
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
case emitter.chunk.run():
of irOK:
rsOK
of irRuntimeError:
rsRuntimeError
proc repl =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = @[]

View File

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

View File

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

View File

@ -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 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
const lineEditor = leRdstdin