155 lines
4.5 KiB
Nim
155 lines
4.5 KiB
Nim
import strformat
|
|
import strutils
|
|
|
|
import types/value
|
|
|
|
type
|
|
OpCode* = enum
|
|
opReturn, opCall, opCheckArity, opFunctionDef, # functions
|
|
opClosure, # closures
|
|
opPop, opPopSA, opPopA # pop
|
|
opNegate, opNot # unary
|
|
opAdd, opSubtract, opMultiply, opDivide, # math
|
|
opEqual, opGreater, opLess, # comparison
|
|
opTrue, opFalse, opNil, # literal
|
|
opConstant, # constant
|
|
opDefineGlobal, opGetGlobal, opSetGlobal, # globals (uses constants)
|
|
opGetLocal, opSetLocal, # locals
|
|
opGetUpvalue, opSetUpvalue, # upvalues
|
|
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop, # jumps
|
|
opCreateList, opCreateTable, # collection creation
|
|
opLen, opSetIndex, opGetIndex, # collection operators
|
|
opPrint, opChr, opInt, opPutchar, # temporary opcodes for the brainfuck interpreter TODO move to native funcs
|
|
|
|
Chunk* = object
|
|
code*: seq[uint8]
|
|
constants*: seq[NdValue]
|
|
lines*: seq[int]
|
|
name*: string # name of the module/chunk/files
|
|
|
|
DoubleUint8* = array[2, uint8]
|
|
|
|
|
|
# WARNING! short args can safely assumed to be 1 byte long outside of chunk.nim
|
|
const shortArgSize* = 1
|
|
const shortArgMax* = 256 - 1
|
|
|
|
# modules outside chunk.nim should not assume any length of argSize, however code inside chunk.nim can make such assumptions
|
|
const argSize* = 2
|
|
const argMax*: int = 256*256 - 1
|
|
|
|
proc initChunk*(name: string): Chunk =
|
|
Chunk(code: @[], name: name, lines: @[], constants: @[])
|
|
|
|
proc writeChunk*(ch: var Chunk, code: uint8, line: int) =
|
|
ch.code.add(code)
|
|
ch.lines.add(line)
|
|
|
|
proc writeChunk*(ch: var Chunk, code: OpCode, line: int) =
|
|
ch.code.add(code.uint8)
|
|
ch.lines.add(line)
|
|
|
|
proc writeChunk*(ch: var Chunk, code: DoubleUint8, line: int) =
|
|
for c in code:
|
|
ch.code.add(c)
|
|
ch.lines.add(line)
|
|
|
|
proc len*(ch: Chunk): int =
|
|
ch.code.len
|
|
|
|
proc toDU8*(integ: int): DoubleUint8 =
|
|
cast[ptr array[2, uint8]](integ.unsafeAddr)[]
|
|
|
|
proc toInt*(du8: DoubleUint8): int =
|
|
cast[uint16](du8).int
|
|
|
|
proc DU8ptrToInt*(du8: ptr uint8): int =
|
|
cast[ptr uint16](du8)[].int
|
|
|
|
proc findConstant(ch: var Chunk, constant: NdValue): int =
|
|
# TODO, if there are a lot of constants that are different, this could be possibly slow, so either use a table lookup or only check for the last x constants
|
|
if ch.constants.len() == 0:
|
|
return -1
|
|
for i in countup(0, ch.constants.high()):
|
|
let current = ch.constants[i]
|
|
if current == constant:
|
|
return i
|
|
return -1
|
|
|
|
proc addConstant*(ch: var Chunk, constant: NdValue): int =
|
|
let found = ch.findConstant(constant)
|
|
if found == -1:
|
|
ch.constants.add(constant)
|
|
ch.constants.high
|
|
else:
|
|
found
|
|
|
|
proc writeConstant*(ch: var Chunk, constant: NdValue, line: int): int =
|
|
result = ch.addConstant(constant)
|
|
ch.writeChunk(opConstant, line)
|
|
ch.writeChunk(result.toDU8, line)
|
|
|
|
const simpleInstructions = {
|
|
opReturn,
|
|
opPop,
|
|
opPrint,
|
|
opNegate, opNot,
|
|
opAdd, opSubtract, opMultiply, opDivide,
|
|
opEqual, opGreater, opLess,
|
|
opTrue, opFalse, opNil,
|
|
opLen, opSetIndex, opGetIndex,
|
|
opChr, opInt, opPutchar,
|
|
}
|
|
const constantInstructions = {
|
|
opConstant,
|
|
opDefineGlobal, opGetGlobal, opSetGlobal,
|
|
}
|
|
const shortArgInstructions = {
|
|
opPopSA,
|
|
opCall,
|
|
opCheckArity,
|
|
}
|
|
const argInstructions = {
|
|
opPopA,
|
|
opGetLocal, opSetLocal,
|
|
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop,
|
|
opFunctionDef, opClosure,
|
|
opCreateList, opCreateTable,
|
|
opGetUpvalue, opSetUpvalue,
|
|
}
|
|
|
|
|
|
proc disassembleChunk*(ch: Chunk) =
|
|
echo &"== Chunk {ch.name} begin =="
|
|
echo "index line instruction"
|
|
var c: int = 0
|
|
var lastLine = -1
|
|
while c < ch.code.len:
|
|
template instruction: uint8 = ch.code[c]
|
|
template line: int = ch.lines[c]
|
|
template double: DoubleUint8 = [ch.code[c+1], ch.code[c+2]]
|
|
template shortArg: uint8 = ch.code[c+1]
|
|
let cFmt = &"{c:04}"
|
|
let lineFmt = if lastLine == line: " | " else: &"{line:04}"
|
|
try:
|
|
write stdout, &"[{cFmt}] {lineFmt} {instruction.OpCode} ({instruction.toHex(2)}"
|
|
case instruction.OpCode:
|
|
of simpleInstructions:
|
|
write stdout, ")\n"
|
|
of shortArgInstructions:
|
|
write stdout, &" {shortArg.toHex(2)}"
|
|
c += 1
|
|
of argInstructions:
|
|
write stdout, &" {double[0].toHex(2)} {double[1].toHex(2)})\n"
|
|
c += 2
|
|
of constantInstructions:
|
|
let i = double.toInt
|
|
write stdout, &" {double[0].toHex(2)} {double[1].toHex(2)})\n"
|
|
echo &" points to constant {ch.constants[i]} (i: {i})"
|
|
c += 2
|
|
except:
|
|
echo &"[{cFmt}] {lineFmt} Unknown opcode {instruction}"
|
|
c.inc
|
|
echo &"== Chunk {ch.name} end =="
|
|
|