split up expression.nim
This commit is contained in:
parent
960be5f0dd
commit
d4a6921302
|
@ -0,0 +1,76 @@
|
|||
import strformat
|
||||
import strutils
|
||||
import bitops # needed for value
|
||||
|
||||
import ../scanner
|
||||
import ../chunk
|
||||
import ../types/value
|
||||
import ../config
|
||||
|
||||
# the following order of imports here is important
|
||||
# it defines the allowed dependency precedence between the compiler files
|
||||
import types
|
||||
import utils
|
||||
import precedence
|
||||
import jumps
|
||||
import scope
|
||||
|
||||
# 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 != tkRightBrace:
|
||||
comp.expression()
|
||||
comp.consume(tkEqual, "Equal sign 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)
|
|
@ -2,17 +2,15 @@ import ../scanner
|
|||
import ../chunk
|
||||
import ../config
|
||||
|
||||
# the following order of imports here is important
|
||||
# it defines the allowed dependency precedence between the compiler files
|
||||
import types
|
||||
import utils
|
||||
#import precedence
|
||||
#import jumps
|
||||
#import scope
|
||||
#import variables
|
||||
#import expressions
|
||||
import statement
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import strformat
|
||||
import strutils
|
||||
import bitops # needed for value
|
||||
|
||||
import ../scanner
|
||||
import ../chunk
|
||||
import ../types/value
|
||||
import ../config
|
||||
|
||||
# the following order of imports here is important
|
||||
# it defines the allowed dependency precedence between the compiler files
|
||||
import types
|
||||
import utils
|
||||
import precedence
|
||||
import jumps
|
||||
import scope
|
||||
|
||||
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.")
|
||||
|
||||
let thenJump = comp.emitJump(0, opJumpIfFalse)
|
||||
|
||||
# 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
|
||||
|
||||
let elseJump = comp.emitJump(0, opJump)
|
||||
comp.patchJump(thenJump)
|
||||
|
||||
if comp.match(tkElse):
|
||||
comp.writeChunk(-1, opPop)
|
||||
comp.expression()
|
||||
|
||||
comp.patchJump(elseJump)
|
||||
|
||||
|
||||
tkIf.genRule(ifExpr, nop, pcNone)
|
||||
|
||||
proc andExpr(comp: Compiler) =
|
||||
let endJump = comp.emitJump(0, opJumpIfFalse)
|
||||
|
||||
comp.writeChunk(-1, opPop)
|
||||
comp.parsePrecedence(pcAnd)
|
||||
# net effect on stack: -1 + 1 = 0
|
||||
|
||||
comp.patchJump(endJump)
|
||||
|
||||
tkAnd.genRule(nop, andExpr, pcAnd)
|
||||
|
||||
proc orExpr(comp: Compiler) =
|
||||
let elseJump = comp.emitJump(0, opJumpIfFalse)
|
||||
let endJump = comp.emitJump(0, opJump)
|
||||
|
||||
comp.patchJump(elseJump)
|
||||
|
||||
comp.writeChunk(-1, opPop)
|
||||
comp.parsePrecedence(pcOr)
|
||||
# net effect on stack: -1 + 1 = 0
|
||||
|
||||
comp.patchJump(endJump)
|
||||
|
||||
tkOr.genRule(nop, orExpr, pcOr)
|
||||
|
||||
proc parseWhile(comp: Compiler) =
|
||||
|
||||
comp.writeChunk(1, opNil) # return value
|
||||
let loopStart = comp.chunk.len
|
||||
comp.consume(tkLeftParen, "Expect '(' after 'while'.")
|
||||
# condition
|
||||
comp.expression()
|
||||
comp.consume(tkRightParen, "Expect ')' after condition.")
|
||||
|
||||
let exitJump = comp.emitJump(-1, opJumpIfFalsePop)
|
||||
# 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)
|
||||
comp.patchJump(exitJump)
|
||||
|
||||
tkWhile.genRule(parseWhile, nop, pcNone)
|
|
@ -16,8 +16,6 @@ import utils
|
|||
import precedence
|
||||
import jumps
|
||||
import scope
|
||||
import variables
|
||||
|
||||
|
||||
# EXPRESSIONS
|
||||
|
||||
|
@ -53,35 +51,6 @@ proc expString(comp: Compiler) =
|
|||
|
||||
tkString.genRule(expString, nop, pcNone)
|
||||
|
||||
proc grouping(comp: Compiler) =
|
||||
# assume initial '(' is already consumed
|
||||
comp.expression()
|
||||
comp.consume(tkRightParen, "Expect ')' after expression.")
|
||||
|
||||
proc parseCall(comp: Compiler) =
|
||||
# ( consumed
|
||||
|
||||
# create the call env
|
||||
# current stack before opCall:
|
||||
# ... <funct obj> <arg1> <arg2> <arg3>
|
||||
# opCall converts it to this
|
||||
# ... <ret val> <arg1> <arg2> <arg3>
|
||||
|
||||
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.")
|
||||
|
||||
# emit call
|
||||
comp.writeChunk(-argcount, opCall)
|
||||
comp.writeChunk(0, argcount.uint8)
|
||||
|
||||
tkLeftParen.genRule(grouping, parseCall, pcCall)
|
||||
|
||||
proc unary(comp: Compiler) =
|
||||
let opType = comp.previous.tokenType
|
||||
|
||||
|
@ -142,201 +111,6 @@ tkGreaterEqual.genRule(nop, binary, pcComparison)
|
|||
tkLess.genRule(nop, binary, pcComparison)
|
||||
tkLessEqual.genRule(nop, binary, pcComparison)
|
||||
|
||||
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.")
|
||||
|
||||
let thenJump = comp.emitJump(0, opJumpIfFalse)
|
||||
|
||||
# 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
|
||||
|
||||
let elseJump = comp.emitJump(0, opJump)
|
||||
comp.patchJump(thenJump)
|
||||
|
||||
if comp.match(tkElse):
|
||||
comp.writeChunk(-1, opPop)
|
||||
comp.expression()
|
||||
|
||||
comp.patchJump(elseJump)
|
||||
|
||||
|
||||
tkIf.genRule(ifExpr, nop, pcNone)
|
||||
|
||||
proc andExpr(comp: Compiler) =
|
||||
let endJump = comp.emitJump(0, opJumpIfFalse)
|
||||
|
||||
comp.writeChunk(-1, opPop)
|
||||
comp.parsePrecedence(pcAnd)
|
||||
# net effect on stack: -1 + 1 = 0
|
||||
|
||||
comp.patchJump(endJump)
|
||||
|
||||
tkAnd.genRule(nop, andExpr, pcAnd)
|
||||
|
||||
proc orExpr(comp: Compiler) =
|
||||
let elseJump = comp.emitJump(0, opJumpIfFalse)
|
||||
let endJump = comp.emitJump(0, opJump)
|
||||
|
||||
comp.patchJump(elseJump)
|
||||
|
||||
comp.writeChunk(-1, opPop)
|
||||
comp.parsePrecedence(pcOr)
|
||||
# net effect on stack: -1 + 1 = 0
|
||||
|
||||
comp.patchJump(endJump)
|
||||
|
||||
tkOr.genRule(nop, orExpr, pcOr)
|
||||
|
||||
proc parseWhile(comp: Compiler) =
|
||||
|
||||
comp.writeChunk(1, opNil) # return value
|
||||
let loopStart = comp.chunk.len
|
||||
comp.consume(tkLeftParen, "Expect '(' after 'while'.")
|
||||
# condition
|
||||
comp.expression()
|
||||
comp.consume(tkRightParen, "Expect ')' after condition.")
|
||||
|
||||
let exitJump = comp.emitJump(-1, opJumpIfFalsePop)
|
||||
# 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)
|
||||
comp.patchJump(exitJump)
|
||||
|
||||
|
||||
tkWhile.genRule(parseWhile, nop, pcNone)
|
||||
|
||||
proc parseFunct(comp: Compiler) =
|
||||
|
||||
# jump over
|
||||
let jumpOverBody = comp.emitJump(1, opFunctionDef)
|
||||
|
||||
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()
|
||||
when assertionsCompiler:
|
||||
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)
|
||||
# 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)
|
||||
|
||||
tkFunct.genRule(parseFunct, nop, pcNone)
|
||||
|
||||
# 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 != tkRightBrace:
|
||||
comp.expression()
|
||||
comp.consume(tkEqual, "Equal sign 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 parseAmpersand(comp: Compiler) =
|
||||
# just a simple expression separator
|
||||
discard
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import strformat
|
||||
import strutils
|
||||
import bitops # needed for value
|
||||
|
||||
import ../scanner
|
||||
import ../chunk
|
||||
import ../types/value
|
||||
import ../config
|
||||
|
||||
# the following order of imports here is important
|
||||
# it defines the allowed dependency precedence between the compiler files
|
||||
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 parseCall(comp: Compiler) =
|
||||
# ( consumed
|
||||
|
||||
# create the call env
|
||||
# current stack before opCall:
|
||||
# ... <funct obj> <arg1> <arg2> <arg3>
|
||||
# opCall converts it to this
|
||||
# ... <ret val> <arg1> <arg2> <arg3>
|
||||
|
||||
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.")
|
||||
|
||||
# emit call
|
||||
comp.writeChunk(-argcount, opCall)
|
||||
comp.writeChunk(0, argcount.uint8)
|
||||
|
||||
tkLeftParen.genRule(grouping, parseCall, pcCall)
|
||||
|
||||
proc parseFunct(comp: Compiler) =
|
||||
|
||||
# jump over
|
||||
let jumpOverBody = comp.emitJump(1, opFunctionDef)
|
||||
|
||||
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()
|
||||
when assertionsCompiler:
|
||||
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)
|
||||
# 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)
|
||||
|
||||
tkFunct.genRule(parseFunct, nop, pcNone)
|
Loading…
Reference in New Issue