Working on the bytecode VM

This commit is contained in:
nocturn9x 2020-08-07 17:11:06 +02:00
parent 76eb517d98
commit ab6cb16e42
8 changed files with 192 additions and 28 deletions

BIN
nim/lexer

Binary file not shown.

View File

@ -93,7 +93,7 @@ proc parseString(self: var Lexer, delimiter: char) =
if self.done():
raise newException(ParseError, &"Unterminated string literal at {self.line}")
discard self.step()
let value = StrValue(value: self.source[self.start..<self.current - 1]) # Get the value between quotes
let value = Value(kind: ValueTypes.STRING, stringValue: self.source[self.start..<self.current - 1]) # Get the value between quotes
let token = self.createToken(STR, value)
self.tokens.add(token)
@ -105,11 +105,11 @@ proc parseNumber(self: var Lexer) =
discard self.step()
while self.peek().isDigit():
discard self.step()
var value = FloatValue(value: parseFloat(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(FLOAT, value))
var value = Value(kind: ValueTypes.FLOAT, floatValue: parseFloat(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(TokenType.FLOAT, value))
else:
var value = IntValue(value: parseInt(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(INT, value))
var value = Value(kind: ValueTypes.INT, intValue: parseInt(self.source[self.start..<self.current]))
self.tokens.add(self.createToken(TokenType.INT, value))
proc parseIdentifier(self: var Lexer) =
@ -118,9 +118,9 @@ proc parseIdentifier(self: var Lexer) =
var text: string = self.source[self.start..<self.current]
var keyword = text in RESERVED
if keyword:
self.tokens.add(self.createToken(RESERVED[text], StrValue(value: text)))
self.tokens.add(self.createToken(RESERVED[text], Value(kind: ValueTypes.STRING, stringValue: text)))
else:
self.tokens.add(self.createToken(ID, StrValue(value: text)))
self.tokens.add(self.createToken(ID, Value(kind: ValueTypes.STRING, stringValue: text)))
proc parseComment(self: var Lexer) =
@ -160,17 +160,17 @@ proc scanToken(self: var Lexer) =
elif single == '/' and self.match('*'):
self.parseComment()
elif single == '=' and self.match('='):
self.tokens.add(self.createToken(DEQ, StrValue(value: "==")))
self.tokens.add(self.createToken(DEQ, Value(kind: ValueTypes.STRING, stringValue: "==")))
elif single == '>' and self.match('='):
self.tokens.add(self.createToken(GE, StrValue(value: ">=")))
self.tokens.add(self.createToken(GE, Value(kind: ValueTypes.STRING, stringValue: ">=")))
elif single == '<' and self.match('='):
self.tokens.add(self.createToken(LE, StrValue(value: "<=")))
self.tokens.add(self.createToken(LE, Value(kind: ValueTypes.STRING, stringValue: "<=")))
elif single == '!' and self.match('='):
self.tokens.add(self.createToken(NE, StrValue(value: "!=")))
self.tokens.add(self.createToken(NE, Value(kind: ValueTypes.STRING, stringValue: "!=")))
elif single == '*' and self.match('*'):
self.tokens.add(self.createToken(POW, StrValue(value: "**")))
self.tokens.add(self.createToken(POW, Value(kind: ValueTypes.STRING, stringValue: "**")))
else:
self.tokens.add(self.createToken(TOKENS[single], CharValue(value: single)))
self.tokens.add(self.createToken(TOKENS[single], Value(kind: ValueTypes.CHAR, charValue: single)))
else:
raise newException(ParseError, &"Unexpected character '{single}' at {self.line}")
@ -179,10 +179,6 @@ proc lex*(self: var Lexer): seq[Token] =
while not self.done():
self.start = self.current
self.scanToken()
self.tokens.add(Token(kind: EOF, lexeme: "EOF", literal: IntValue(value: -1), line: self.line))
self.tokens.add(Token(kind: EOF, lexeme: "EOF", literal: Value(kind: CHAR, charValue: '\0'), line: self.line))
return self.tokens
var lexer = initLexer("print(1);")
var tokens = lexer.lex()
assert tokens[0].literal.value == "print"

View File

@ -0,0 +1,28 @@
import vm
import meta/chunk
import util/debug
import meta/valueobject
import strformat
proc main() =
echo "Initializing the JAPL virtual machine"
var bytecodeVM = initVM()
echo "Creating arbitrary chunk"
var chunk: Chunk = initChunk()
var index: int = chunk.addConstant(Value(kind: FLOAT, floatValue: 1.2))
chunk.writeChunk(uint8 OP_CONSTANT, 1)
chunk.writeChunk(uint8 index, 1)
chunk.writeChunk(uint8 OP_RETURN, 1)
echo "Disassembling chunk"
chunk.disassembleChunk("test chunk")
echo "Interpreting bytecode instructions"
echo fmt"Result: {bytecodeVM.interpret(chunk)}"
bytecodeVM.freeVM()
chunk.freeChunk()
when isMainModule:
main()

31
nim/meta/chunk.nim Normal file
View File

@ -0,0 +1,31 @@
import valueobject
type
OpCode* = enum
OP_CONSTANT = 0u8,
OP_CONSTANT_LONG,
OP_RETURN,
Chunk* = ref object
consts*: ValueArray
code*: seq[uint8]
lines*: seq[int]
proc initChunk*(): Chunk =
result = Chunk(consts: initValueArray(), code: @[], lines: @[])
proc writeChunk*(self: Chunk, byte: uint8, line: int) =
self.code.add(byte)
self.lines.add(line)
proc freeChunk*(self: var Chunk) =
self.consts = ValueArray(values: @[])
self.code = @[]
self.lines = @[]
proc addConstant*(chunk: var Chunk, constant: Value): int =
chunk.consts.values.add(constant)
return len(chunk.consts.values) - 1 # The index of the constant

View File

@ -1,13 +1,38 @@
# Value objects
import strformat
type
Value*[T] = ref object of RootObj
value*: T
StrValue* = ref object of Value
value*: string
CharValue* = ref object of Value
value*: char
IntValue* = ref object of Value
value*: int
FloatValue* = ref object of Value
value*: float
ValueTypes* = enum
FLOAT, INT, CHAR, STRING
Value* = ref object of RootObj
case kind*: ValueTypes
of CHAR:
charValue*: char
of STRING:
stringValue*: string
of FLOAT:
floatValue*: float
of INT:
intValue*: int
ValueArray* = ref object
values*: seq[Value]
proc initValueArray*(): ValueArray =
result = ValueArray(values: @[])
proc writeValueArray*(arr: var ValueArray, value: Value) =
arr.values.add(value)
proc printValue*(value: Value) =
case value.kind:
of FLOAT:
echo &"\tValue: {value.floatValue}\n\tKind: {value.kind}"
of STRING:
echo &"\tValue: {value.stringValue}\n\tKind: {value.kind}"
of INT:
echo &"\tValue: {value.intValue}\n\tKind: {value.kind}"
of CHAR:
echo &"\tValue: {value.charValue}\n\tKind: {value.kind}"

View File

39
nim/util/debug.nim Normal file
View File

@ -0,0 +1,39 @@
import ../meta/chunk
import ../meta/valueobject
import strformat
proc simpleInstruction(name: string, index: int): int =
var index = index
echo &"\tOpCode at offset: {name}"
echo ""
return index + 1
proc constantInstruction(name: string, chunk: Chunk, offset: int): int =
var constant = chunk.code[offset + 1]
echo &"\tOpCode at offset: {name}, points to index {constant}"
printValue(chunk.consts.values[constant])
echo ""
return offset + 2
proc disassembleInstruction*(chunk: Chunk, offset: int): int =
echo &"Current offset: {offset}\nCurrent line: {chunk.lines[offset]}"
var opcode = OpCode(chunk.code[offset])
if opcode == OP_RETURN:
simpleInstruction("OP_RETURN", offset)
elif opcode == OP_CONSTANT:
constantInstruction("OP_CONSTANT", chunk, offset)
else:
echo &"Unknown opcode {opcode} at index {offset}"
return offset + 1
proc disassembleChunk*(chunk: Chunk, name: string) =
echo &"==== JAPL VM Debugger - Chunk '{name}' ====\n"
var index = 0
while index < chunk.code.len:
index = disassembleInstruction(chunk, index)
echo &"==== Debug session ended - Chunk '{name}' ===="

View File

@ -0,0 +1,45 @@
import meta/chunk
type InterpretResult = enum
OK,
COMPILE_ERROR,
RUNTIME_ERROR
type VM = ref object
chunk: Chunk
ip: int
proc run(self: VM): InterpretResult =
template readByte: untyped =
inc(self.ip)
self.chunk.code[self.ip - 1]
var instruction: uint8
var opcode: OpCode
while true:
{.computedgoto.}
instruction = readByte()
opcode = OpCode(instruction)
case opcode:
of OP_RETURN:
return OK
of OP_CONSTANT:
return OK
of OP_CONSTANT_LONG:
return OK
proc interpret*(self: var VM, chunk: var Chunk): InterpretResult =
self.chunk = chunk
self.ip = 0
result = self.run()
proc initVM*(): VM =
result = VM(chunk: initChunk(), ip: 0)
proc freeVM*(self: VM) =
return