mirror of https://github.com/japl-lang/japl.git
Started to wire up the VM with the lexer and the parser/compiler
This commit is contained in:
parent
ee9444a3bf
commit
c4baf9b7bf
|
@ -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
|
|
@ -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] =
|
||||
|
|
68
nim/main.nim
68
nim/main.nim
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
95
nim/vm.nim
95
nim/vm.nim
|
@ -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) =
|
||||
|
|
Loading…
Reference in New Issue