Started to wire up the VM with the lexer and the parser/compiler

This commit is contained in:
nocturn9x 2020-08-08 16:19:44 +02:00
parent ee9444a3bf
commit c4baf9b7bf
7 changed files with 224 additions and 50 deletions

59
nim/compiler.nim Normal file
View File

@ -0,0 +1,59 @@
import lexer
import vm
import strformat
import meta/chunk
import meta/tokentype
import meta/tokenobject
type Compiler* = ref object
type Parser* = ref object
current: int
tokens: seq[Token]
hadError: bool
panicMode: bool
proc advance(self: var Parser): Token =
inc(self.current)
return self.tokens[self.current - 1]
proc peek(self: Parser): Token =
return self.tokens[self.current]
proc previous(self: Parser): Token =
return self.tokens[self.current - 1]
proc consume(self, expected: TokenType) =
if self.peek().kind == expected:
self.advance()
return
self.parseError(self.peek(), &"Found '{token.kind}' ('{token.lexeme}'), while parsing for {expected}")
proc parseError(self: var Parser, token: Token, message: string) =
if self.panicMode:
return
self.panicMode = true
echo &"ParseError at line {token.line}, at '{token.lexeme}' -> {message}"
proc initCompiler*(): Compiler =
result = Compiler()
proc initParser(tokens: seq[Token]): Parser =
result = Parser(current: 0, tokens: tokens, hadError: false, panicMode: false)
proc compile*(self: Compiler, source: string, chunk: Chunk): bool
var scanner = initLexer(source)
var tokens = scanner.lex()
var parser = initParser(tokens)
parser.advance()
parser.expression()
parser.consume(EOF, "Expecting end of file")
return not parser.hadError

View File

@ -1,7 +1,6 @@
import tables
import meta/tokentype
import meta/tokenobject
import meta/exceptions
import meta/valueobject
import system
import strutils
@ -91,7 +90,7 @@ proc parseString(self: var Lexer, delimiter: char) =
self.line = self.line + 1
discard self.step()
if self.done():
raise newException(ParseError, &"Unterminated string literal at {self.line}")
quit(&"Unterminated string literal at {self.line}")
discard self.step()
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)
@ -138,7 +137,7 @@ proc parseComment(self: var Lexer) =
break
discard self.step()
if self.done() and not closed:
raise newException(ParseError, &"Unexpected EOF at line {self.line}")
quit(&"Unexpected EOF at line {self.line}")
proc scanToken(self: var Lexer) =
@ -172,7 +171,7 @@ proc scanToken(self: var Lexer) =
else:
self.tokens.add(self.createToken(TOKENS[single], Value(kind: ValueTypes.CHAR, charValue: single)))
else:
raise newException(ParseError, &"Unexpected character '{single}' at {self.line}")
quit(&"Unexpected character '{single}' at {self.line}")
proc lex*(self: var Lexer): seq[Token] =

View File

@ -1,31 +1,55 @@
import vm
import meta/chunk
import util/debug
import meta/valueobject
import strformat
import parseopt
import os
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))
var index2: array[3, uint8] = chunk.writeConstant(Value(kind: INT, intValue: 56))
chunk.writeChunk(uint8 OP_CONSTANT, 1)
chunk.writeChunk(uint8 index, 1)
chunk.writeChunk(uint8 OP_CONSTANT_LONG, 1)
chunk.writeChunk(index2, 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()
proc repl(debug: bool = false) =
return
proc main(file: string = "", debug: bool = false) =
if file == "":
repl(debug)
else:
var sourceFile: File
try:
sourceFile = open(filename=file)
except IOError:
echo &"Error: '{file}' could not be opened, probably the file doesn't exist or you don't have permission to read it"
return
var source: string
try:
source = readAll(sourceFile)
except IOError:
echo &"Error: '{file}' could not be read, probably you don't have the permission to read it"
var bytecodeVM = initVM()
if debug:
echo "Debug mode is enabled, bytecode will be disassembled"
var result = bytecodeVM.interpret(source)
if debug:
echo &"Result: {result}"
when isMainModule:
main()
var parser = initOptParser(commandLineParams())
var file: string = ""
var debug: bool = false
if paramCount() > 0:
if paramCount() notin 1..<3:
echo "usage: japl [filename] [--debug]"
quit()
for kind, key, value in parser.getopt():
case kind:
of cmdArgument:
file = key
of cmdLongOption:
if key == "debug":
debug = true
else:
echo &"Unkown option '{key}'"
quit()
else:
echo "usage: japl [filename] [--debug]"
quit()
main(file, debug)

View File

@ -6,6 +6,11 @@ type
OP_CONSTANT = 0u8,
OP_CONSTANT_LONG,
OP_RETURN,
OP_NEGATE,
OP_ADD,
OP_SUBTRACT,
OP_DIVIDE,
OP_MULTIPLY
Chunk* = ref object
consts*: ValueArray
code*: seq[uint8]
@ -21,7 +26,7 @@ proc writeChunk*(self: Chunk, byt: uint8, line: int) =
self.lines.add(line)
proc writeChunk*(self: Chunk, bytes: array[3, uint8], line: int) =
proc writeChunk*(self: Chunk, bytes: array[3, uint8], line: int) =
for byt in bytes:
self.writeChunk(byt, line)
@ -37,8 +42,6 @@ proc addConstant*(chunk: var Chunk, constant: Value): int =
return len(chunk.consts.values) - 1 # The index of the constant
proc writeConstant*(chunk: var Chunk, constant: Value): array[3, uint8] =
proc writeConstant*(chunk: var Chunk, constant: Value): array[3, uint8] =
let index = chunk.addConstant(constant)
result = cast[array[3, uint8]](index)

View File

@ -25,14 +25,14 @@ proc initValueArray*(): ValueArray =
proc writeValueArray*(arr: var ValueArray, value: Value) =
arr.values.add(value)
proc printValue*(value: Value) =
proc stringifyValue*(value: Value): string =
case value.kind:
of FLOAT:
echo &"\tValue: {value.floatValue}\n\tKind: {value.kind}"
result = $value.floatValue
of STRING:
echo &"\tValue: {value.stringValue}\n\tKind: {value.kind}"
result = value.stringValue
of INT:
echo &"\tValue: {value.intValue}\n\tKind: {value.kind}"
result = $value.intValue
of CHAR:
echo &"\tValue: {value.charValue}\n\tKind: {value.kind}"
result = &"{value.charValue}"

View File

@ -4,8 +4,7 @@ import strformat
proc simpleInstruction(name: string, index: int): int =
echo &"\tOpCode at offset: {name}"
echo ""
echo &"\tOpCode at offset: {name}\n"
return index + 1
@ -15,16 +14,16 @@ proc constantLongInstruction(name: string, chunk: Chunk, offset: int): int =
var constant: int
copyMem(constant.addr, unsafeAddr(constantArray), sizeof(constantArray))
echo &"\tOpCode at offset: {name}, points to {constant}"
printValue(chunk.consts.values[constant])
echo ""
let obj = chunk.consts.values[constant]
echo &"\tValue: {stringifyValue(obj)}\n\tKind: {obj.kind}\n"
return offset + 4
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 ""
let obj = chunk.consts.values[constant]
echo &"\tValue: {stringifyValue(obj)}\n\tKind: {obj.kind}\n"
return offset + 2
@ -37,10 +36,21 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int =
result = constantInstruction("OP_CONSTANT", chunk, offset)
elif opcode == OP_CONSTANT_LONG:
result = constantLongInstruction("OP_CONSTANT_LONG", chunk, offset)
elif opcode == OP_NEGATE:
result = simpleInstruction("OP_NEGATE", offset)
elif opcode == OP_ADD:
result = simpleInstruction("OP_ADD", offset)
elif opcode == OP_MULTIPLY:
result = simpleInstruction("OP_MULTIPLY", offset)
elif opcode == OP_DIVIDE:
result = simpleInstruction("OP_DIVIDE", offset)
elif opcode == OP_SUBTRACT:
result = simpleInstruction("OP_SUBTRACT", offset)
else:
echo &"Unknown opcode {opcode} at index {offset}"
result = offset + 1
proc disassembleChunk*(chunk: Chunk, name: string) =
echo &"==== JAPL VM Debugger - Chunk '{name}' ====\n"
var index = 0

View File

@ -1,4 +1,10 @@
import meta/chunk
import meta/valueobject
import util/debug
import compiler
import strutils
import strformat
import lenientops
type InterpretResult = enum
@ -6,39 +12,112 @@ type InterpretResult = enum
COMPILE_ERROR,
RUNTIME_ERROR
type VM = ref object
chunk: Chunk
ip: int
stack*: seq[Value]
stackTop*: int
proc run(self: VM): InterpretResult =
proc pop(self: VM): Value =
result = self.stack.pop()
self.stackTop = self.stackTop - 1
proc push*(self: VM, value: Value) =
self.stack.add(value)
self.stackTop = self.stackTop + 1
proc run(self: VM, debug: bool): InterpretResult =
template readByte: untyped =
inc(self.ip)
self.chunk.code[self.ip - 1]
template readConstant: Value =
self.chunk.consts.values[int(readByte())]
template readLongConstant: Value =
var arr = [readByte(), readByte(), readByte()]
var idx: int
copyMem(idx.addr, unsafeAddr(arr), sizeof(arr))
self.chunk.consts.values[idx]
template BinOp(op) =
var left = self.pop()
var right = self.pop()
if left.kind == FLOAT and right.kind == INT:
var res = `op`(left.floatValue, right.intValue)
self.push(Value(kind: FLOAT, floatValue: res))
elif left.kind == INT and right.kind == FLOAT:
var res = `op`(left.intValue, right.floatValue)
self.push(Value(kind: FLOAT, floatValue: res))
elif left.kind == INT and right.kind == INT:
var res = cast[int](`op`(left.intValue, right.intValue))
self.push(Value(kind: INT, intValue: res))
else:
var res = `op`(left.floatValue, right.floatValue)
self.push(Value(kind: FLOAT, floatValue: res))
var instruction: uint8
var opcode: OpCode
while true:
{.computedgoto.}
instruction = readByte()
opcode = OpCode(instruction)
if debug:
stdout.write("Current stack status: [")
for v in self.stack:
stdout.write(stringifyValue(v))
stdout.write(", ")
stdout.write("]\n")
discard disassembleInstruction(self.chunk, self.ip - 1)
case opcode:
of OP_RETURN:
return OK
of OP_CONSTANT:
return OK
var constant: Value = readConstant()
self.push(constant)
of OP_CONSTANT_LONG:
var constant: Value = readLongConstant()
self.push(constant)
of OP_NEGATE:
var cur = self.pop()
case cur.kind:
of FLOAT:
cur.floatValue = -cur.floatValue
self.push(cur)
of INT:
cur.intValue = -cur.intValue
self.push(cur)
else:
echo &"Unsupported unary operator '-' for object of type '{toLowerAscii($cur.kind)}'"
return RUNTIME_ERROR
of OP_ADD:
BinOp(`+`)
of OP_SUBTRACT:
BinOp(`-`)
of OP_DIVIDE:
BinOp(`/`)
of OP_MULTIPLY:
BinOp(`*`)
of OP_RETURN:
echo stringifyValue(self.pop())
return OK
proc interpret*(self: var VM, chunk: var Chunk): InterpretResult =
proc interpret*(self: var VM, source: string, debug: bool = false): InterpretResult =
var chunk = initChunk()
var compiler = initCompiler()
if not compiler.compile(source, chunk):
return COMPILE_ERROR
self.chunk = chunk
self.ip = 0
result = self.run()
result = vm.run(debug)
chunk.freeChunk()
proc resetStack*(self: VM) =
self.stackTop = 0
proc initVM*(): VM =
result = VM(chunk: initChunk(), ip: 0)
result = VM(chunk: initChunk(), ip: 0, stack: @[], stackTop: 0)
proc freeVM*(self: VM) =