diff --git a/package.json b/package.json index 2a40447..3e4128c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "eject": "react-scripts eject", "lint-no-fix": "eslint ./ --ignore-path .gitignore && prettier . -c", "lint": "eslint ./ --ignore-path .gitignore --fix && prettier . -c --write", - "generate-parser": "npx peggy --format es -o src/virtual-machine/parser/golang_parser.js src/virtual-machine/parser/parser.peggy", - "generate-parser-watch": "npx peggy -m -w --format es -o src/virtual-machine/parser/golang_parser.js src/virtual-machine/parser/parser.peggy" + "generate-parser": "npx peggy --format es -o src/go-virtual-machine-main/virtual-machine/parser/golang_parser.js src/go-virtual-machine-main/virtual-machine/parser/parser.peggy", + "generate-parser-watch": "npx peggy -m -w --format es -o src/go-virtual-machine-main/virtual-machine/parser/golang_parser.js src/go-virtual-machine-main/virtual-machine/parser/parser.peggy" }, "author": "", "license": "ISC", diff --git a/src/go-virtual-machine-main/virtual-machine/parser/golang_parser.d.ts b/src/go-virtual-machine-main/virtual-machine/parser/golang_parser.d.ts deleted file mode 100644 index 197c570..0000000 --- a/src/go-virtual-machine-main/virtual-machine/parser/golang_parser.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import peggy from 'peggy' - -declare module './golang_parser.js' { - const parse: peggy.Parser.parse - export { parse } -} diff --git a/src/virtual-machine/parser/golang_parser.ts b/src/go-virtual-machine-main/virtual-machine/parser/golang_parser.ts similarity index 100% rename from src/virtual-machine/parser/golang_parser.ts rename to src/go-virtual-machine-main/virtual-machine/parser/golang_parser.ts diff --git a/src/virtual-machine/compiler/environment.ts b/src/virtual-machine/compiler/environment.ts deleted file mode 100644 index 45467a6..0000000 --- a/src/virtual-machine/compiler/environment.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { JumpInstruction } from './instructions/control' - -class CompileEnvironment { - frames: string[][] - constructor(parent?: CompileEnvironment) { - if (!parent) { - this.frames = [[]] - } else { - this.frames = parent.frames.slice() - this.frames.push([]) - } - } - - find_var(name: string) { - let frame_idx = 0 - const frame_sz = this.frames.length - 1 - while (frame_sz >= frame_idx) { - let var_idx = this.frames[frame_sz - frame_idx].length - 1 - while (var_idx >= 0) { - if (this.frames[frame_sz - frame_idx][var_idx] === name) - return [frame_idx, var_idx] - var_idx-- - } - frame_idx++ - } - throw Error('Unable to find variable: ' + name) - } - - declare_var(name: string) { - const frame_idx = this.frames.length - 1 - for (const var_name of this.frames[frame_idx]) { - if (var_name === name) throw Error('Variable already declared') - } - const new_len = this.frames[frame_idx].push(name) - return [0, new_len - 1] - } - - get_frame() { - const frame_idx = this.frames.length - 1 - return this.frames[frame_idx] - } -} - -class LoopMarker { - break_instrs: JumpInstruction[] - cont_instrs: JumpInstruction[] - constructor() { - this.break_instrs = [] - this.cont_instrs = [] - } -} - -export class CompileContext { - env: CompileEnvironment - env_stack: CompileEnvironment[] - loop_stack: LoopMarker[] - constructor() { - this.env = new CompileEnvironment() - this.env_stack = [this.env] - this.loop_stack = [] - } - - push_env() { - const new_env = new CompileEnvironment(this.env) - this.env_stack.push(this.env) - this.env = new_env - } - - pop_env() { - const old_env = this.env_stack.pop() - if (!old_env) throw Error('Compile Env Stack Empty!') - this.env = old_env - } - - push_loop() { - this.loop_stack.push(new LoopMarker()) - } - - add_break(instr: JumpInstruction) { - this.loop_stack[this.loop_stack.length - 1].break_instrs.push(instr) - } - add_continue(instr: JumpInstruction) { - this.loop_stack[this.loop_stack.length - 1].cont_instrs.push(instr) - } - - pop_loop(pre_addr: number, post_addr: number) { - const old_loop = this.loop_stack.pop() - if (!old_loop) throw Error('Compile Loop Stack Empty!') - for (const instr of old_loop.cont_instrs) { - instr.set_addr(pre_addr) - } - for (const instr of old_loop.break_instrs) { - instr.set_addr(post_addr) - } - } -} diff --git a/src/virtual-machine/compiler/index.ts b/src/virtual-machine/compiler/index.ts deleted file mode 100644 index 4a4296e..0000000 --- a/src/virtual-machine/compiler/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Token, TokenLocation } from '../parser/tokens' - -import { TypeEnvironment } from './typing/type_environment' -import { CompileContext } from './environment' -import { DoneInstruction, Instruction } from './instructions' - -export class CompileError extends Error { - constructor(message: string, public sourceLocation: TokenLocation) { - super(message) - } -} - -export class Compiler { - instructions: Instruction[] = [] - symbols: (TokenLocation | null)[] = [] - context = new CompileContext() - type_environment = new TypeEnvironment() - - compile_program(token: Token) { - token.compile(this) - this.instructions.push(new DoneInstruction()) - } - - throwCompileError(message: string, sourceLocation: TokenLocation): never { - throw new CompileError(message, sourceLocation) - } -} - -const compile_tokens = (token: Token) => { - const compiler = new Compiler() - compiler.compile_program(token) - return { - instructions: compiler.instructions, - symbols: compiler.symbols, - } -} - -export { compile_tokens } diff --git a/src/virtual-machine/compiler/instructions/base.ts b/src/virtual-machine/compiler/instructions/base.ts deleted file mode 100644 index 55c0d82..0000000 --- a/src/virtual-machine/compiler/instructions/base.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Process } from '../../executor/process' - -export abstract class Instruction { - tag: string - - constructor(tag: string) { - this.tag = tag - } - - abstract execute(process: Process): void - - toString(): string { - return this.tag - } -} - -export class DoneInstruction extends Instruction { - constructor() { - super('DONE') - } - - static is(instr: Instruction): instr is DoneInstruction { - return instr.tag === 'DONE' - } - - override execute(_process: Process): void { - // Do nothing. - } -} - -export class PopInstruction extends Instruction { - constructor() { - super('POP') - } - - static is(instr: Instruction): instr is DoneInstruction { - return instr.tag === 'POP' - } - - override execute(process: Process): void { - process.context.popOS() - } -} diff --git a/src/virtual-machine/compiler/instructions/block.ts b/src/virtual-machine/compiler/instructions/block.ts deleted file mode 100644 index 76c90ff..0000000 --- a/src/virtual-machine/compiler/instructions/block.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Process } from '../../executor/process' -import { FrameNode } from '../../heap/types/environment' -import { Type } from '../typing' - -import { Instruction } from './base' - -export class BlockInstruction extends Instruction { - frame: Type[] = [] - identifiers: string[] = [] - constructor(public name: string, public for_block = false) { - super('BLOCK') - } - - set_frame(frame: Type[]) { - this.frame = [...frame] - } - - set_identifiers(identifiers: string[]) { - this.identifiers = [...identifiers] - } - - override toString(): string { - return super.toString() + ' ' + this.name - } - - override execute(process: Process): void { - const new_frame = FrameNode.create(this.frame.length, process.heap) - process.heap.temp_push(new_frame.addr) - for (let i = 0; i < this.frame.length; i++) { - const T = this.frame[i] - new_frame.set_idx(T.defaultNodeCreator()(process.heap), i) - } - const new_env = process.context - .E() - .extend_env(new_frame.addr, this.for_block).addr - process.context.pushRTS(new_env) - process.heap.temp_pop() - - if (process.debug_mode) { - process.debugger.env_alloc_map.set(new_env, process.runtime_count) - process.debugger.env_name_map.set(new_env, this.name) - const children = new_frame.get_children() - for (let i = 0; i < children.length; i++) { - process.debugger.identifier_map.set(children[i], this.identifiers[i]) - } - } - } -} -export class FuncBlockInstruction extends BlockInstruction { - constructor(public args: number) { - super('ANONY FUNC', false) - this.tag = 'FUNC_BLOCK' - } - - override toString(): string { - return this.tag - } - - override execute(process: Process): void { - super.execute(process) - for (let i = this.args - 1; i >= 0; i--) { - const src = process.context.popOS() - const dst = process.context.E().get_frame().get_idx(i) - process.heap.copy(dst, src) - } - // Pop function in stack - const id = process.context.popOS() - if (process.debug_mode) { - const identifier = process.debugger.identifier_map.get(id) - if (identifier) { - process.debugger.env_name_map.set(process.context.E().addr, identifier) - } - } - } -} - -export class ExitBlockInstruction extends Instruction { - constructor() { - super('EXIT_BLOCK') - } - - override execute(process: Process): void { - process.context.popRTS() - } -} diff --git a/src/virtual-machine/compiler/instructions/builtin.ts b/src/virtual-machine/compiler/instructions/builtin.ts deleted file mode 100644 index 9c2e0a8..0000000 --- a/src/virtual-machine/compiler/instructions/builtin.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Process } from '../../executor/process' -import { ArrayNode, SliceNode } from '../../heap/types/array' -import { IntegerNode } from '../../heap/types/primitives' - -import { Instruction } from './base' - -/** Takes an object address from the OS, and returns the length of that object. */ -export class BuiltinLenInstruction extends Instruction { - constructor() { - super('BUILTIN_LEN') - } - - override execute(process: Process): void { - const node = process.heap.get_value(process.context.popOS()) - if (node instanceof ArrayNode || node instanceof SliceNode) { - const length = node.length() - process.context.pushOS(IntegerNode.create(length, process.heap).addr) - } else { - throw new Error('Unreachable') - } - } -} - -/** Takes an object address from the OS, and returns the capacity of that object. */ -export class BuiltinCapInstruction extends Instruction { - constructor() { - super('BUILTIN_CAP') - } - - override execute(process: Process): void { - const node = process.heap.get_value(process.context.popOS()) - if (node instanceof ArrayNode || node instanceof SliceNode) { - const capacity = node.capacity() - process.context.pushOS(IntegerNode.create(capacity, process.heap).addr) - } else { - throw new Error('Unreachable') - } - } -} diff --git a/src/virtual-machine/compiler/instructions/concurrent.ts b/src/virtual-machine/compiler/instructions/concurrent.ts deleted file mode 100644 index 0a4f86b..0000000 --- a/src/virtual-machine/compiler/instructions/concurrent.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Process } from '../../executor/process' -import { ArrayNode } from '../../heap/types/array' -import { - ChannelNode, - ChannelReqNode, - ReqInfoNode, -} from '../../heap/types/channel' -import { IntegerNode } from '../../heap/types/primitives' - -import { Instruction } from './base' - -export class ForkInstruction extends Instruction { - addr: number - - constructor(addr = 0) { - super('FORK') - this.addr = addr - } - - set_addr(addr: number) { - this.addr = addr - } - - override execute(process: Process): void { - const new_context = process.context.fork().addr - process.contexts.push(new_context) - process.context.set_PC(this.addr) - if (process.debug_mode) { - process.debugger.context_id_map.set( - new_context, - process.debugger.context_id++, - ) - } - } -} - -export class LoadChannelInstruction extends Instruction { - constructor() { - super('LDCH') - } - - override toString(): string { - return 'LOAD CHANNEL' - } - - override execute(process: Process): void { - const buffer_sz = new IntegerNode( - process.heap, - process.context.popOS(), - ).get_value() - process.context.pushOS(ChannelNode.create(buffer_sz, process.heap).addr) - } -} - -export class LoadChannelReqInstruction extends Instruction { - constructor(public recv: boolean, public PC: number) { - super('LDCR') - } - - override toString(): string { - return 'LOAD CHAN ' + (this.recv ? 'RECV' : 'SEND') + ' REQ' - } - - override execute(process: Process): void { - const clone = process.heap.clone(process.context.peekOS()) - process.heap.temp_push(clone) - const req = ReqInfoNode.create( - clone, - process.context.addr, - this.PC, - this.recv, - process.heap, - ) - process.heap.temp_pop() - process.context.popOS() - process.heap.temp_push(req.addr) - const chan = new ChannelNode(process.heap, process.context.popOS()) - const chan_req = ChannelReqNode.create(chan.addr, req.addr, process.heap) - process.heap.temp_pop() - process.context.pushOS(chan_req.addr) - } -} - -export class TryChannelReqInstruction extends Instruction { - constructor() { - super('TRY_CHAN_REQ') - } - override execute(process: Process): void { - const chan_req = new ChannelReqNode(process.heap, process.context.popOS()) - process.heap.temp_push(chan_req.addr) - const chan = chan_req.channel() - const req = chan_req.req() - if (!chan.try(req)) { - process.context.set_waitlist(ArrayNode.create(2, process.heap).addr) - process.context.waitlist().set_child(0, chan.wait(req)) - process.context - .waitlist() - .set_child( - 1, - process.heap.blocked_contexts.push_back(process.context.addr), - ) - process.context.set_blocked(true) - } else { - process.context.set_PC(req.PC()) - if (req.is_recv()) process.context.pushOS(req.io()) - } - process.heap.temp_pop() - } -} - -export class SelectInstruction extends Instruction { - constructor(public cases: number, public default_case: boolean) { - super('SELECT CASES') - } - override execute(process: Process): void { - let pc = -1 - if (this.default_case) { - pc = new IntegerNode(process.heap, process.context.popOS()).get_value() - } - let cases = [] - for (let i = 0; i < this.cases; i++) { - cases.push(new ChannelReqNode(process.heap, process.context.peekOS())) - process.heap.temp_push(process.context.popOS()) - } - cases = cases - .map((a) => ({ sort: process.generator(), value: a })) - .sort((a, b) => a.sort - b.sort) - .map((a) => a.value) - let done = false - for (const cas of cases) { - const chan = cas.channel() - const req = cas.req() - if (chan.try(req)) { - done = true - process.context.set_PC(req.PC()) - if (req.is_recv()) process.context.pushOS(req.io()) - break - } - } - if (!done) { - if (pc !== -1) { - process.context.set_PC(pc) - } else { - process.context.set_blocked(true) - process.context.set_waitlist( - ArrayNode.create(cases.length + 1, process.heap).addr, - ) - for (let i = 0; i < cases.length; i++) { - const chan = cases[i].channel() - const req = cases[i].req() - process.context.waitlist().set_child(i, chan.wait(req)) - } - process.context - .waitlist() - .set_child( - cases.length, - process.heap.blocked_contexts.push_back(process.context.addr), - ) - } - } - for (let i = 0; i < cases.length; i++) process.heap.temp_pop() - } -} diff --git a/src/virtual-machine/compiler/instructions/control.ts b/src/virtual-machine/compiler/instructions/control.ts deleted file mode 100644 index 3dd547a..0000000 --- a/src/virtual-machine/compiler/instructions/control.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Process } from '../../executor/process' -import { BoolNode } from '../../heap/types/primitives' - -import { Instruction } from './base' - -export class JumpInstruction extends Instruction { - addr: number - - constructor(addr = 0) { - super('JUMP') - this.addr = addr - } - - override toString(): string { - return super.toString() + ' ' + this.addr.toString() - } - - set_addr(addr: number) { - this.addr = addr - } - - override execute(process: Process): void { - process.context.set_PC(this.addr) - } -} - -export class JumpIfFalseInstruction extends JumpInstruction { - constructor(addr = 0) { - super(addr) - this.tag = 'JUMP_IF_FALSE' - } - - override execute(process: Process): void { - const pred = ( - process.heap.get_value(process.context.popOS()) as BoolNode - ).get_value() - if (!pred) process.context.set_PC(this.addr) - } -} - -export class ExitLoopInstruction extends JumpInstruction { - constructor(addr = 0) { - super(addr) - this.tag = 'JUMP_LOOP' - } - - override execute(process: Process): void { - while (!process.context.E().if_for_block()) { - process.context.popRTS() - } - process.context.set_PC(this.addr) - } -} diff --git a/src/virtual-machine/compiler/instructions/funcs.ts b/src/virtual-machine/compiler/instructions/funcs.ts deleted file mode 100644 index f655545..0000000 --- a/src/virtual-machine/compiler/instructions/funcs.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Process } from '../../executor/process' -import { - CallRefNode, - DeferFuncNode, - DeferMethodNode, - FuncNode, - MethodNode, -} from '../../heap/types/func' - -import { Instruction } from './base' - -export class LoadFuncInstruction extends Instruction { - PC: number - constructor(PC: number) { - super('LF') - this.PC = PC - } - - override toString(): string { - return 'LOAD FUNC' - } - - static is(instr: Instruction): instr is LoadFuncInstruction { - return instr.tag === 'LF' - } - - override execute(process: Process): void { - process.context.pushOS( - FuncNode.create(this.PC, process.context.E().addr, process.heap).addr, - ) - } -} - -export class CallInstruction extends Instruction { - constructor(public args: number) { - super('CALL') - } - - override toString(): string { - return 'CALL ' + this.args.toString() + ' ARGS' - } - - static is(instr: Instruction): instr is CallInstruction { - return instr.tag === 'CALL' - } - - override execute(process: Process): void { - const func = process.heap.get_value(process.context.peekOSIdx(this.args)) - if (!(func instanceof FuncNode) && !(func instanceof MethodNode)) - throw Error('Stack does not contain closure') - - if (func instanceof FuncNode) { - process.context.pushDeferStack() - process.context.pushRTS( - CallRefNode.create(process.context.PC(), process.heap).addr, - ) - process.context.pushRTS(func.E()) - process.context.set_PC(func.PC()) - } else { - const receiver = func.receiver() - receiver.handleMethodCall(process, func.identifier(), this.args) - } - } -} - -export class DeferredCallInstruction extends Instruction { - constructor(public args: number) { - super('DEFERRED_CALL') - } - - override toString(): string { - return 'DEFER CALL ' + this.args.toString() + ' ARGS' - } - - static fromCallInstruction(call: CallInstruction): DeferredCallInstruction { - return new DeferredCallInstruction(call.args) - } - - static is(instr: Instruction): instr is DeferredCallInstruction { - return instr.tag === 'DEFERRED_CALL' - } - - override execute(process: Process): void { - const func = process.heap.get_value(process.context.peekOSIdx(this.args)) - if (!(func instanceof FuncNode) && !(func instanceof MethodNode)) - throw Error('Stack does not contain closure') - - let deferNode - if (func instanceof FuncNode) { - deferNode = DeferFuncNode.create(this.args, process) - } else { - deferNode = DeferMethodNode.create(this.args, process) - } - process.context.peekDeferStack().push(deferNode.addr) - } -} - -export class ReturnInstruction extends Instruction { - constructor() { - super('RET') - } - - static is(instr: Instruction): instr is ReturnInstruction { - return instr.tag === 'RET' - } - - override execute(process: Process): void { - // Clear remnant environment nodes on the RTS (e.g. from blocks). - while (!(process.context.peekRTS() instanceof CallRefNode)) { - process.context.popRTS() - } - - const defers = process.context.peekDeferStack() - if (defers.sz()) { - // There are still deferred calls to be carried out. - const deferNode = process.heap.get_value(defers.pop()) - if ( - !(deferNode instanceof DeferFuncNode) && - !(deferNode instanceof DeferMethodNode) - ) { - throw new Error('Unreachable') - } - - // Push everything back onto OS before resuming the call. - if (deferNode instanceof DeferFuncNode) { - process.context.pushOS(deferNode.funcAddr()) - while (deferNode.stack().sz()) { - process.context.pushOS(deferNode.stack().pop()) - } - process.context.pushDeferStack() - process.context.pushRTS( - CallRefNode.create(process.context.PC() - 1, process.heap).addr, - ) - process.context.pushRTS(deferNode.func().E()) - process.context.set_PC(deferNode.func().PC()) - } else { - const methodNode = deferNode.methodNode() - process.context.pushOS(methodNode.addr) - process.context.pushOS(methodNode.receiverAddr()) - const argCount = deferNode.stack().sz() - while (deferNode.stack().sz()) { - process.context.pushOS(deferNode.stack().pop()) - } - methodNode - .receiver() - .handleMethodCall(process, methodNode.identifier(), argCount) - - // Since methods are hardcoded and don't behave like functions, they don't jump back to an address. - // Manually decrement PC here so that the next executor step will return to this instruction. - process.context.set_PC(process.context.PC() - 1) - } - - // Return here to account for this as one instruction, - // to avoid hogging the CPU while going through deferred calls. - return - } else { - process.context.popDeferStack() - } - - const callRef = process.heap.get_value(process.context.popRTS()) - if (!(callRef instanceof CallRefNode)) throw new Error('Unreachable') - process.context.set_PC(callRef.PC()) - } -} diff --git a/src/virtual-machine/compiler/instructions/index.ts b/src/virtual-machine/compiler/instructions/index.ts deleted file mode 100644 index 86a5d3e..0000000 --- a/src/virtual-machine/compiler/instructions/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './base' -export * from './block' -export * from './builtin' -export * from './concurrent' -export * from './control' -export * from './funcs' -export * from './load' -export * from './operator' -export * from './store' diff --git a/src/virtual-machine/compiler/instructions/load.ts b/src/virtual-machine/compiler/instructions/load.ts deleted file mode 100644 index 59dca95..0000000 --- a/src/virtual-machine/compiler/instructions/load.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Process } from '../../executor/process' -import { ArrayNode, SliceNode } from '../../heap/types/array' -import { FmtPkgNode } from '../../heap/types/fmt' -import { - BoolNode, - FloatNode, - IntegerNode, - StringNode, -} from '../../heap/types/primitives' -import { BoolType, Float64Type, Int64Type, StringType, Type } from '../typing' - -import { Instruction } from './base' - -export class LoadConstantInstruction extends Instruction { - val: number | string | boolean - data_type: Type - constructor(val: number | string | boolean, data_type: Type) { - super('LDC') - this.val = val - this.data_type = data_type - } - - override toString(): string { - return 'LOAD ' + this.val.toString() - } - - static is(instr: Instruction): instr is LoadConstantInstruction { - return instr.tag === 'LDC' - } - - override execute(process: Process): void { - if (this.data_type instanceof BoolType) { - process.context.pushOS( - BoolNode.create(this.val as boolean, process.heap).addr, - ) - } else if (this.data_type instanceof Float64Type) { - process.context.pushOS( - FloatNode.create(this.val as number, process.heap).addr, - ) - } else if (this.data_type instanceof Int64Type) { - const temp = IntegerNode.create(this.val as number, process.heap).addr - process.context.pushOS(temp) - } else if (this.data_type instanceof StringType) { - process.context.pushOS( - StringNode.create(this.val as string, process.heap).addr, - ) - } - } -} - -/** Loads a default value of the given type onto the OS. */ -export class LoadDefaultInstruction extends Instruction { - constructor(public dataType: Type) { - super('LDD') - } - - override toString(): string { - return 'LOAD DEFAULT ' + this.dataType.toString() - } - - override execute(process: Process): void { - const defaultNodeAddress = this.dataType.defaultNodeCreator()(process.heap) - process.context.pushOS(defaultNodeAddress) - } -} - -/** - * Creates an array on the heap, with element addresses taken from the OS (starting from the back). - * Pushes the address of the array back onto the OS. - */ -export class LoadArrayInstruction extends Instruction { - constructor(public length: number) { - super('LDA') - } - - override toString(): string { - return 'LOAD ARRAY ' + this.length.toString() - } - - override execute(process: Process): void { - const arrayNode = ArrayNode.create(this.length, process.heap) - for (let i = this.length - 1; i >= 0; i--) { - arrayNode.set_child(i, process.context.popOS()) - } - process.context.pushOS(arrayNode.addr) - } -} - -/** Takes the index, then array from the heap, and loads the element at the index onto the OS. */ -export class LoadArrayElementInstruction extends Instruction { - constructor() { - super('LDAE') - } - - override toString(): string { - return 'LOAD ARRAY ENTRY' - } - - override execute(process: Process): void { - const indexNode = new IntegerNode(process.heap, process.context.popOS()) - const index = indexNode.get_value() - const array = new ArrayNode(process.heap, process.context.popOS()) - if (index < 0 || index >= array.length()) { - throw new Error( - `Index out of range [${index}] with length ${array.length()}`, - ) - } - const element = array.get_child(index) - process.context.pushOS(element) - } -} -/** Takes the index, then array from the heap, and loads the element at the index onto the OS. */ -export class LoadSliceElementInstruction extends Instruction { - constructor() { - super('LDAE') - } - - override execute(process: Process): void { - const index = process.context.popOSNode(IntegerNode).get_value() - const slice = process.context.popOSNode(SliceNode) - const array = slice.arrayNode() - if (index < 0 || index >= array.length()) { - throw new Error( - `Index out of range [${index}] with length ${array.length()}`, - ) - } - const element = array.get_child(index) - process.context.pushOS(element) - } -} - -/** - * Creates a slice on the heap, with the following arguments taken from the OS (bottom to top). - * - Array address - * - Start index of the slice. - * - End index of the slice. - * Pushes the address of the slice back onto the OS. - */ -export class LoadSliceInstruction extends Instruction { - constructor() { - super('LDS') - } - - override execute(process: Process): void { - const end = process.context.popOSNode(IntegerNode).get_value() - const start = process.context.popOSNode(IntegerNode).get_value() - const array = process.context.popOS() - const sliceNode = SliceNode.create(array, start, end, process.heap) - process.context.pushOS(sliceNode.addr) - } -} - -export class LoadVariableInstruction extends Instruction { - constructor( - public frame_idx: number, - public var_idx: number, - public id: string, - ) { - super('LD') - } - - override toString() { - return 'LOAD VAR ' + this.id - } - - override execute(process: Process): void { - process.context.pushOS( - process.context.E().get_var(this.frame_idx, this.var_idx), - ) - } -} - -/** - * Takes a package name (string literal) from the OS and loads the corresponding package node back onto the OS. - * Currently this is only implemented for `fmt`, as it is the only package requiring runtime values. - */ -export class LoadPackageInstruction extends Instruction { - constructor() { - super('LDP') - } - - override execute(process: Process): void { - const packageName = process.context.popOSNode(StringNode).get_value() - if (packageName !== 'fmt') throw new Error('Unreachable') - const packageNode = FmtPkgNode.default(process.heap) - process.context.pushOS(packageNode.addr) - } -} diff --git a/src/virtual-machine/compiler/instructions/operator.ts b/src/virtual-machine/compiler/instructions/operator.ts deleted file mode 100644 index 93e9730..0000000 --- a/src/virtual-machine/compiler/instructions/operator.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Process } from '../../executor/process' -import { ArrayNode, SliceNode } from '../../heap/types/array' -import { - IntegerNode, - PrimitiveNode, - StringNode, -} from '../../heap/types/primitives' - -import { Instruction } from './base' - -export abstract class OpInstruction extends Instruction { - op: string - - constructor(tag: string, op: string) { - super(tag) - this.op = op - } -} - -export class UnaryInstruction extends OpInstruction { - constructor(op: string) { - super('UNARY', op) - } - - override toString(): string { - return 'UNARY ' + this.op - } - - override execute(process: Process): void { - const arg1 = process.heap.get_value( - process.context.popOS(), - ) as PrimitiveNode - process.context.pushOS(arg1.apply_unary(this.op).addr) - } -} - -export class BinaryInstruction extends OpInstruction { - constructor(op: string) { - super('BINOP', op) - } - - override toString(): string { - return 'BINOP ' + this.op - } - - override execute(process: Process): void { - const arg2 = process.heap.get_value( - process.context.popOS(), - ) as PrimitiveNode - const arg1 = process.heap.get_value( - process.context.popOS(), - ) as PrimitiveNode - process.context.pushOS(arg1.apply_binop(arg2, this.op).addr) - } -} - -/** - * Takes its arguments from the OS, and pushes a new slice onto the OS. - * - Node address: Address of the node to slice. - * - Low: A number for the starting index (non-integer if the start). - * - High: A number for the ending index (non-integer if the end). - */ -export class SliceOperationInstruction extends Instruction { - constructor() { - super('SLICEOP') - } - - override execute(process: Process): void { - const highNode = process.heap.get_value(process.context.popOS()) - const lowNode = process.heap.get_value(process.context.popOS()) - const node = process.heap.get_value(process.context.popOS()) - const low = lowNode instanceof IntegerNode ? lowNode.get_value() : 0 - // If high is not provided, its default value will be resolved later on in the code. - const high = highNode instanceof IntegerNode ? highNode.get_value() : null - - if (node instanceof ArrayNode) { - process.context.pushOS(this.sliceArray(process, node, low, high)) - } else if (node instanceof SliceNode) { - process.context.pushOS(this.sliceSlice(process, node, low, high)) - } else { - throw new Error('Unreachable') - } - } - - private sliceArray( - process: Process, - array: ArrayNode, - low: number, - high: number | null, - ): number { - low ??= 0 - high ??= array.length() - this.checkSliceRange(low, high, array.length()) - const newSlice = SliceNode.create(array.addr, low, high, process.heap) - return newSlice.addr - } - - private sliceSlice( - process: Process, - slice: SliceNode, - low: number, - high: number | null, - ): number { - low ??= 0 - high ??= slice.capacity() - this.checkSliceRange(low, high, slice.capacity()) - const start = low + slice.start() - const end = high + slice.start() - const newSlice = SliceNode.create(slice.array(), start, end, process.heap) - return newSlice.addr - } - - /** Checks that the slice [low:high] is valid on an underlying container with given length. */ - private checkSliceRange(low: number, high: number, length: number) { - if (low < 0 || low > length || high < 0 || high > length || high < low) { - throw new Error('Slice bounds out of range') - } - } -} - -/** - * Takes its operand and identifier string from the OS, - * and selects the given identifier from the operand. - */ -export class SelectorOperationInstruction extends Instruction { - constructor() { - super('SELECTOP') - } - - override execute(process: Process): void { - const identifier = process.context.popOSNode(StringNode).get_value() - const node = process.heap.get_value(process.context.popOS()) - node.select(process, identifier) - } -} diff --git a/src/virtual-machine/compiler/instructions/store.ts b/src/virtual-machine/compiler/instructions/store.ts deleted file mode 100644 index 227acb1..0000000 --- a/src/virtual-machine/compiler/instructions/store.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Process } from '../../executor/process' - -import { Instruction } from './base' - -export class StoreInstruction extends Instruction { - constructor() { - super('STORE') - } - - override execute(process: Process): void { - const dst = process.context.popOS() - const src = process.context.popOS() - process.heap.copy(dst, src) - - if (process.debug_mode) { - process.debugger.modified_buffer.add(dst) - } - } -} diff --git a/src/virtual-machine/compiler/typing/index.ts b/src/virtual-machine/compiler/typing/index.ts deleted file mode 100644 index 9b3990d..0000000 --- a/src/virtual-machine/compiler/typing/index.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { Heap } from '../../heap' -import { ArrayNode, SliceNode } from '../../heap/types/array' -import { ChannelNode } from '../../heap/types/channel' -import { PkgNode } from '../../heap/types/fmt' -import { FuncNode } from '../../heap/types/func' -import { - BoolNode, - FloatNode, - IntegerNode, - StringNode, -} from '../../heap/types/primitives' - -export abstract class Type { - abstract isPrimitive(): boolean - abstract toString(): string - abstract equals(t: Type): boolean - - /** Returns true if `t` can be assigned to this type. */ - assignableBy(t: Type): boolean { - return this.equals(t) - } - - /** Returns a function that creates a default node of this type on the heap, and returns its address. */ - abstract defaultNodeCreator(): (heap: Heap) => number - - /** Returns the type of selecting an identifier on the given type. */ - select(identifier: string): Type { - throw new Error( - `undefined (type ${this} has no field or method ${identifier})`, - ) - } -} - -/** This type represents things that don't have an associated type, like statements. */ -export class NoType extends Type { - isPrimitive(): boolean { - return false - } - - toString(): string { - return '' - } - - override equals(t: Type): boolean { - return t instanceof NoType - } - - override defaultNodeCreator(): (heap: Heap) => number { - throw new Error('Cannot create values of type NoType') - } -} - -export class BoolType extends Type { - isPrimitive(): boolean { - return true - } - - toString(): string { - return 'bool' - } - - override equals(t: Type): boolean { - return t instanceof BoolType - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => BoolNode.default(heap).addr - } -} - -export class Int64Type extends Type { - isPrimitive(): boolean { - return true - } - - toString(): string { - return 'int64' - } - - override equals(t: Type): boolean { - return t instanceof Int64Type - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => IntegerNode.default(heap).addr - } -} - -export class Float64Type extends Type { - isPrimitive(): boolean { - return true - } - - toString(): string { - return 'float64' - } - - override equals(t: Type): boolean { - return t instanceof Float64Type - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => FloatNode.default(heap).addr - } -} - -export class StringType extends Type { - isPrimitive(): boolean { - return true - } - - toString(): string { - return 'string' - } - - override equals(t: Type): boolean { - return t instanceof StringType - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => StringNode.default(heap).addr - } -} - -export class ArrayType extends Type { - constructor(public element: Type, public length: number) { - super() - } - - isPrimitive(): boolean { - return false - } - - toString(): string { - return `[${this.length}]${this.element.toString()}` - } - - override equals(t: Type): boolean { - return ( - t instanceof ArrayType && - this.element.equals(t.element) && - this.length === t.length - ) - } - - override defaultNodeCreator(): (heap: Heap) => number { - const elementCreator = this.element.defaultNodeCreator() - return (heap) => ArrayNode.default(this.length, elementCreator, heap).addr - } -} - -export class SliceType extends Type { - constructor(public element: Type) { - super() - } - - isPrimitive(): boolean { - return false - } - - toString(): string { - return `[]${this.element.toString()}` - } - - override equals(t: Type): boolean { - return t instanceof SliceType && this.element.equals(t.element) - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => SliceNode.default(heap).addr - } -} - -export class ParameterType extends Type { - constructor(public identifier: string | null, public type: Type) { - super() - } - - override isPrimitive(): boolean { - return false - } - - toString(): string { - return this.identifier === null - ? `${this.type}` - : `${this.identifier} ${this.type}` - } - - override equals(t: Type): boolean { - return t instanceof ParameterType && this.type.equals(t.type) - } - - override defaultNodeCreator(): (heap: Heap) => number { - // Do nothing. - return (_) => 0 - } -} - -export class FunctionType extends Type { - constructor( - public parameters: ParameterType[], - public results: ReturnType, - public variadic: boolean = false, - ) { - super() - } - - override isPrimitive(): boolean { - return false - } - - toString(): string { - const parametersString = TypeUtility.arrayToString(this.parameters) - return `func(${parametersString}) ${this.results}` - } - - override equals(t: Type): boolean { - return ( - t instanceof FunctionType && - this.parameters.length === t.parameters.length && - this.parameters.every((p, index) => p.equals(t.parameters[index])) && - this.results.equals(t.results) - ) - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => FuncNode.default(heap).addr - } -} - -export class ChannelType extends Type { - constructor( - public element: Type, - public readable: boolean, - public writable: boolean, - ) { - super() - } - - override isPrimitive(): boolean { - return false - } - - override toString(): string { - if (this.readable && this.writable) { - return `chan ${this.element}` - } else if (this.readable) { - return `<-chan ${this.element}` - } else { - return `chan<- ${this.element}` - } - } - - override equals(t: Type): boolean { - return ( - t instanceof ChannelType && - this.readable === t.readable && - this.writable === t.writable && - this.element.equals(t.element) - ) - } - - override assignableBy(t: Type): boolean { - return ( - this.equals(t) || - (this.readable && - this.writable && - t instanceof ChannelType && - this.element.equals(t.element)) - ) - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => ChannelNode.default(heap).addr - } -} - -export class ReturnType extends Type { - constructor(public types: Type[]) { - super() - } - - override isPrimitive(): boolean { - return false - } - - override toString(): string { - return `(${TypeUtility.arrayToString(this.types)})` - } - - override equals(t: Type): boolean { - return ( - t instanceof ReturnType && - t.types.length === this.types.length && - this.types.every((r, index) => r.equals(t.types[index])) - ) - } - - override defaultNodeCreator(): (_heap: Heap) => number { - // Return values are pushed onto the OS, and should not be allocated. - throw Error('Unreachable') - } - - isVoid(): boolean { - return this.types.length === 0 - } -} - -export class PackageType extends Type { - constructor(public name: string, public types: Record) { - super() - } - - override isPrimitive(): boolean { - return false - } - - override toString(): string { - return `package ${this.name}` - } - - override equals(t: Type): boolean { - return t instanceof PackageType && t.name === this.name - } - - override defaultNodeCreator(): (_heap: Heap) => number { - return (heap) => PkgNode.default(heap).addr - } - - override select(identifier: string): Type { - if (!(identifier in this.types)) { - throw new Error(`undefined: ${this.name}.${identifier}`) - } - return this.types[identifier] - } -} - -export const TypeUtility = { - // Similar to Array.toString(), but adds a space after each comma. - arrayToString(types: Type[] | null) { - return (types ?? []).map((t) => t.toString()).join(', ') - }, -} diff --git a/src/virtual-machine/compiler/typing/packages.ts b/src/virtual-machine/compiler/typing/packages.ts deleted file mode 100644 index 4565dbe..0000000 --- a/src/virtual-machine/compiler/typing/packages.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Heap } from '../../heap' -import { WaitGroupNode } from '../../heap/types/waitGroup' -import { - LoadConstantInstruction, - LoadPackageInstruction, - LoadVariableInstruction, - StoreInstruction, -} from '../instructions' -import { Compiler } from '..' - -import { - FunctionType, - Int64Type, - PackageType, - ParameterType, - ReturnType, - StringType, - Type, -} from '.' - -export class WaitGroupType extends Type { - override isPrimitive(): boolean { - return false - } - - override toString(): string { - return `sync.WaitGroup` - } - - override equals(t: Type): boolean { - return t instanceof WaitGroupType - } - - override defaultNodeCreator(): (heap: Heap) => number { - return (heap) => WaitGroupNode.default(heap).addr - } - - override select(identifier: string): Type { - if (identifier === 'Add') { - return new FunctionType( - [new ParameterType(null, new Int64Type())], - new ReturnType([]), - ) - } else if (identifier === 'Done') { - return new FunctionType([], new ReturnType([])) - } else if (identifier === 'Wait') { - return new FunctionType([], new ReturnType([])) - } - throw new Error( - `.${identifier} undefined (type ${this} has no field or method ${identifier})`, - ) - } -} - -/** - * Builtin packages are functions that take in a single `compiler` argument, - * and does all the package setup within itself. - */ -export const builtinPackages = { - fmt: (compiler: Compiler): Type => { - const pkg = new PackageType('fmt', { - Println: new FunctionType([], new ReturnType([]), true), - }) - compiler.type_environment.addType('fmt', pkg) - const [frame_idx, var_idx] = compiler.context.env.declare_var('fmt') - compiler.instructions.push( - new LoadConstantInstruction('fmt', new StringType()), - new LoadPackageInstruction(), - new LoadVariableInstruction(frame_idx, var_idx, 'fmt'), - new StoreInstruction(), - ) - compiler.symbols.push(...Array(4).fill(null)) - return pkg - }, - sync: (compiler: Compiler): Type => { - const pkg = new PackageType('sync', { - WaitGroup: new WaitGroupType(), - }) - compiler.type_environment.addType('sync', pkg) - return pkg - }, -} diff --git a/src/virtual-machine/compiler/typing/type_environment.ts b/src/virtual-machine/compiler/typing/type_environment.ts deleted file mode 100644 index 232b10e..0000000 --- a/src/virtual-machine/compiler/typing/type_environment.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ReturnType, Type } from '.' - -export class TypeEnvironment { - parent?: TypeEnvironment - typings: Record - expectedReturn: ReturnType - - constructor(parent?: TypeEnvironment) { - this.parent = parent - this.typings = {} - this.expectedReturn = parent?.expectedReturn ?? new ReturnType([]) - } - - addType(name: string, type: Type) { - this.typings[name] = type - } - - /** Returns an extended type environment. */ - extend(): TypeEnvironment { - const newTypeEnvironment = new TypeEnvironment(this) - return newTypeEnvironment - } - - pop(): TypeEnvironment { - if (!this.parent) { - throw Error('Type environment stack is empty when popped.') - } - return this.parent - } - - /** Returns the type of the variable with the given name. */ - get(name: string): Type { - if (name in this.typings) { - return this.typings[name] - } - if (this.parent === undefined) { - throw Error(`Variable ${name} not found`) - } - return this.parent.get(name) - } - - updateReturnType(newType: ReturnType) { - this.expectedReturn = newType - } -} diff --git a/src/virtual-machine/executor/debugger.ts b/src/virtual-machine/executor/debugger.ts deleted file mode 100644 index e2c78ef..0000000 --- a/src/virtual-machine/executor/debugger.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { Instruction } from '../compiler/instructions' -import { Heap } from '../heap' -import { ContextNode } from '../heap/types/context' -import { EnvironmentNode } from '../heap/types/environment' -import { TokenLocation } from '../parser/tokens' - -export type OSInfo = { - val: string - addr: number - modified: boolean -} - -export type VarInfo = { - name: string - val: string - modified: boolean -} - -export type EnvironmentInfo = { - name: string - addr: number - vars: VarInfo[] - alloc_time: number - children: EnvironmentInfo[] - cur: boolean -} - -export type InstructionInfo = { - val: string - idx: number - cur: boolean -} - -export type ContextInfo = { - blocked: boolean - id: number - addr: number - OS: OSInfo[] - instrs: InstructionInfo[] - envs: EnvironmentInfo -} - -export type StateInfo = { - contexts: ContextInfo[] - output: string - location: TokenLocation | null -} - -export class Debugger { - // Maps addr of variables to identifier - identifier_map = new Map() - // Maps addr of environments to identifer - env_name_map = new Map() - // Maps addr of environment to allocation time - env_alloc_map = new Map() - // Maps context to thread id (starting from 0) - context_id_map = new Map() - context_id = 1 - data: StateInfo[] = [] - modified_buffer = new Set() - constructor( - public heap: Heap, - public instructions: Instruction[], - public symbols: (TokenLocation | null)[], - ) {} - - /** - * Finds all environments that can be reached from a given addr - * @param addr Current Address - * @param vis Set of visited addresses - * @param envs Output set of environments - * @returns - */ - get_all_env(addr: number, vis: Set, envs: Set) { - if (addr === -1) return - vis.add(addr) - const val = this.heap.get_value(addr) - if (val instanceof EnvironmentNode) envs.add(addr) - const children = val.get_children() - for (const child of children) { - if (!vis.has(child)) this.get_all_env(child, vis, envs) - } - } - - /** - * Traverse through environments constructing a tree-like object structure - * @param env Current Env - * @param adj Adjacency List of env addr => children env addrs - * @param cur Context active environment addr - * @returns - */ - dfs_env( - env: number, - adj: Map, - cur: number, - ): EnvironmentInfo { - // Sort the env by allocation time and get their envInfo - const child_envs = (adj.get(env) || []) - .map((child) => { - return [this.env_alloc_map.get(child) || -1, child] - }) - .sort((a, b) => a[0] - b[0]) - .map((x) => x[1]) - .map((child) => this.dfs_env(child, adj, cur)) - const env_node = new EnvironmentNode(this.heap, env) - const var_info = env_node - .get_frame() - .get_children() - .map((val) => { - return { - name: this.identifier_map.get(val), - val: this.heap.get_value(val).toString(), - modified: this.modified_buffer.has(val), - } as VarInfo - }) - return { - addr: env, - name: this.env_name_map.get(env), - vars: var_info, - alloc_time: this.env_alloc_map.get(env), - children: child_envs, - cur: env === cur, - } as EnvironmentInfo - } - - generate_state(pc: number, output: string) { - const contexts = [ - ...this.heap.contexts.list().get_children(), - ...this.heap.blocked_contexts.get_items(), - ].map((x) => new ContextNode(this.heap, x)) - const state: ContextInfo[] = [] - const prevContexts = new Set() - let first = true - for (const context of contexts) { - if (prevContexts.has(context.addr)) continue - prevContexts.add(context.addr) - /** - * Generate OS Info - */ - const OS = context - .OS() - .list() - .get_children() - .map((x) => { - const var_name = this.identifier_map.get(x) - return { - val: - (var_name ? var_name + ': ' : '') + - this.heap.get_value(x).toString(), - addr: x, - } as OSInfo - }) - - /** - * Check if the OS values are newly added - * - Iterate through previous os stack values check if addr differ - */ - if (this.data.length) { - const prev = this.data[this.data.length - 1].contexts - let prev_state = undefined - for (const ctx of prev) { - if (ctx.addr === context.addr) prev_state = ctx - } - if (prev_state) { - let same = true - for (let i = 0; i < OS.length; i++) { - if (i >= prev_state.OS.length) same = false - else if (prev_state.OS[i].addr !== OS[i].addr) same = false - if (!same) OS[i].modified = true - } - } else for (const os of OS) os.modified = true - } else { - for (const os of OS) os.modified = true - } - /** - * Generate Instruction Info - */ - const instrs = [] - const context_pc = first ? pc : context.PC() - first = false - let lo = 0, - hi = this.instructions.length - 1 - if (context_pc < 3) { - lo = 0 - hi = 6 - } else if (context_pc + 3 >= this.instructions.length) { - lo = this.instructions.length - 7 - hi = this.instructions.length - 1 - } else { - lo = context_pc - 3 - hi = context_pc + 3 - } - for (let i = lo; i <= hi; i++) { - instrs.push({ - val: this.instructions[i].toString(), - idx: i, - cur: i === context_pc, - }) - } - /** - * Generate Env Info from a traversal - * - Get environments reachable from context - * - Generate adjacancy list - * - Construct tree-like object tree - */ - const envs = new Set() - const vis = new Set() - this.get_all_env(context.addr, vis, envs) - const adj = new Map() - for (const env of envs) adj.set(env, []) - let global_env = 0 - for (const env of envs) { - const envNode = new EnvironmentNode(this.heap, env) - const par = envNode.get_parent(0) - if (par) adj.get(par.addr)?.push(envNode.addr) - else global_env = env - } - - const env_info = this.dfs_env(global_env, adj, context.E().addr) - state.push({ - OS, - id: this.context_id_map.get(context.addr) || -1, - addr: context.addr, - blocked: context.is_blocked(), - instrs, - envs: env_info, - }) - } - this.data.push({ - contexts: state, - output, - location: this.symbols[pc], - }) - this.modified_buffer.clear() - } -} diff --git a/src/virtual-machine/executor/index.ts b/src/virtual-machine/executor/index.ts deleted file mode 100644 index f49d23e..0000000 --- a/src/virtual-machine/executor/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Instruction } from '../compiler/instructions' -import { TokenLocation } from '../parser/tokens' - -import { Process } from './process' - -const execute_instructions = ( - instrs: Instruction[], - heapsize: number, - symbols: (TokenLocation | null)[], - visualisation = false, -) => { - const process = new Process(instrs, heapsize, symbols, visualisation) - return process.start() -} - -export { execute_instructions } diff --git a/src/virtual-machine/executor/ops.ts b/src/virtual-machine/executor/ops.ts deleted file mode 100644 index 4bdf5b8..0000000 --- a/src/virtual-machine/executor/ops.ts +++ /dev/null @@ -1,79 +0,0 @@ -type AnyToBoolFunc = ( - x: number | string | boolean, - y: number | string | boolean, -) => boolean -type BinaryOpToBoolType = Record - -export const AnyBinaryToBoolOp: BinaryOpToBoolType = { - equal: (x: number | string | boolean, y: number | string | boolean) => - x === y, - not_equal: (x: number | string | boolean, y: number | string | boolean) => - x !== y, -} - -type NumStrToBoolFunc = (x: number | string, y: number | string) => boolean -type NumStrBinaryOpToBoolType = Record - -export const NumStrBinaryToBoolOp: NumStrBinaryOpToBoolType = { - less: (x: number | string, y: number | string) => x < y, - less_or_equal: (x: number | string, y: number | string) => x <= y, - greater: (x: number | string, y: number | string) => x > y, - greater_or_equal: (x: number | string, y: number | string) => x >= y, - ...AnyBinaryToBoolOp, -} - -// ---------------- [ Number Ops] ------------------- - -type NumFunc = (x: number, y: number) => number -type NumBinaryOpType = Record - -export const NumBinaryOp: NumBinaryOpType = { - sum: (x: number, y: number) => x + y, - difference: (x: number, y: number) => x - y, - bitwise_or: (x: number, y: number) => x | y, - bitwise_xor: (x: number, y: number) => x ^ y, - product: (x: number, y: number) => x * y, - quotient: (x: number, y: number) => x / y, - remainder: (x: number, y: number) => x % y, - left_shift: (x: number, y: number) => x << y, - right_shift: (x: number, y: number) => x >> y, - bitwise_and: (x: number, y: number) => x & y, - bit_clear: (x: number, y: number) => x & ~y, -} - -// ---------------- [ Boolean Ops] ------------------- - -type BoolFunc = (x: boolean, y: boolean) => boolean -type BoolBinaryOpType = Record - -export const BoolBinaryOp: BoolBinaryOpType = { - conditional_or: (x: boolean, y: boolean) => x || y, - conditional_and: (x: boolean, y: boolean) => x && y, - ...AnyBinaryToBoolOp, -} - -// ---------------- [ String Ops] ------------------- - -type StrFunc = (x: string, y: string) => string -type StrBinaryOpType = Record - -export const StrBinaryOp: StrBinaryOpType = { - sum: (x: string, y: string) => x + y, -} - -type NumUnaryOpFunc = (x: number) => number -type NumUnaryOpType = Record - -// NOTE: Leaving out "indirection", "address" and "receive" unary op to be implemented as an exception in executor -export const NumUnaryOp: NumUnaryOpType = { - plus: (x: number) => x, - negation: (x: number) => -x, - bitwise_complement: (x: number) => ~x, -} - -type BoolUnaryOpFunc = (x: boolean) => boolean -type BoolUnaryOpType = Record - -export const BoolUnaryOp: BoolUnaryOpType = { - not: (x: boolean) => !x, -} diff --git a/src/virtual-machine/executor/process.ts b/src/virtual-machine/executor/process.ts deleted file mode 100644 index ee40b74..0000000 --- a/src/virtual-machine/executor/process.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as seedrandom from 'seedrandom' - -import { DoneInstruction, Instruction } from '../compiler/instructions' -import { Heap } from '../heap' -import { ContextNode } from '../heap/types/context' -import { EnvironmentNode, FrameNode } from '../heap/types/environment' -import { QueueNode } from '../heap/types/queue' -import { TokenLocation } from '../parser/tokens' - -import { Debugger, StateInfo } from './debugger' - -type ProcessOutput = { - stdout: string - visual_data: StateInfo[] - errorMessage?: string -} - -export class Process { - instructions: Instruction[] - heap: Heap - context: ContextNode - contexts: QueueNode - stdout: string - generator: seedrandom.PRNG - debug_mode: boolean - debugger: Debugger - runtime_count = 0 - constructor( - instructions: Instruction[], - heapsize: number, - symbols: (TokenLocation | null)[], - visualmode = false, - ) { - this.instructions = instructions - this.heap = new Heap(heapsize) - this.contexts = this.heap.contexts - this.context = new ContextNode(this.heap, this.contexts.peek()) - this.stdout = '' - console.log(this.heap.mem_left) - const base_frame = FrameNode.create(0, this.heap) - const base_env = EnvironmentNode.create( - base_frame.addr, - [], - false, - this.heap, - ) - this.context.set_E(base_env.addr) - const randomSeed = Math.random().toString(36).substring(2) - this.generator = seedrandom.default(randomSeed) - - this.debug_mode = visualmode - this.debugger = new Debugger(this.heap, this.instructions, symbols) - if (this.debug_mode) - this.debugger.context_id_map.set( - this.context.addr, - this.debugger.context_id++, - ) - this.heap.debugger = this.debugger - } - - start(): ProcessOutput { - try { - const time_quantum = 30 - this.runtime_count = 0 - let completed = false - const main_context = this.contexts.peek() - while (this.contexts.sz()) { - this.context = new ContextNode(this.heap, this.contexts.peek()) - let cur_time = 0 - while (!DoneInstruction.is(this.instructions[this.context.PC()])) { - if (cur_time >= time_quantum) { - // Context Switch - this.contexts.push(this.context.addr) - break - } - const pc = this.context.PC() - const instr = this.instructions[this.context.incr_PC()] - // console.log('ctx:', this.context.addr) - // console.log('Instr:', instr, this.context.PC() - 1) - instr.execute(this) - // this.context.printOS() - // this.context.printRTS() - // this.context.heap.print_freelist() - this.runtime_count += 1 - cur_time += 1 - if (this.debug_mode) this.debugger.generate_state(pc, this.stdout) - if (this.context.is_blocked()) break - } - if ( - DoneInstruction.is(this.instructions[this.context.PC()]) && - this.context.addr === main_context - ) { - completed = true - break - } - this.contexts.pop() - // console.log('%c SWITCH!', 'background: #F7FF00; color: #FF0000') - if (this.runtime_count > 10 ** 5) throw Error('Time Limit Exceeded!') - // console.log('PC', this.contexts.get_vals()) - } - if (!completed && !this.heap.blocked_contexts.is_empty()) - throw Error('All threads are blocked!') - - return { - stdout: this.stdout, - visual_data: this.debug_mode ? this.debugger.data : [], - } - } catch (err) { - console.warn(err) - let errorMessage: string | undefined = undefined - if (err instanceof Error) errorMessage = 'Execution Error: ' + err.message - - return { - stdout: 'An Error Occurred!', - visual_data: this.debug_mode ? this.debugger.data : [], - errorMessage, - } - } - } - - print(string: string) { - this.stdout += string - } -} diff --git a/src/virtual-machine/heap/index.ts b/src/virtual-machine/heap/index.ts deleted file mode 100644 index 148b02c..0000000 --- a/src/virtual-machine/heap/index.ts +++ /dev/null @@ -1,439 +0,0 @@ -import { Debugger } from '../executor/debugger' - -import { ArrayNode, SliceNode } from './types/array' -import { ChannelNode, ChannelReqNode, ReqInfoNode } from './types/channel' -import { ContextNode } from './types/context' -import { EnvironmentNode, FrameNode } from './types/environment' -import { FmtPkgNode, PkgNode } from './types/fmt' -import { - CallRefNode, - DeferFuncNode, - DeferMethodNode, - FuncNode, - MethodNode, -} from './types/func' -import { LinkedListEntryNode, LinkedListNode } from './types/linkedlist' -import { - BoolNode, - FloatNode, - IntegerNode, - StringListNode, - StringNode, - UnassignedNode, -} from './types/primitives' -import { QueueListNode, QueueNode } from './types/queue' -import { StackListNode, StackNode } from './types/stack' -import { WaitGroupNode } from './types/waitGroup' -import { Memory } from './memory' - -export enum TAG { - UNKNOWN = 0, - BOOLEAN = 1, - NUMBER = 2, - CONTEXT = 3, - FRAME = 4, - ENVIRONMENT = 5, - FLOAT = 6, - STRING = 7, - STRING_LIST = 8, - STACK = 9, - STACK_LIST = 10, - FUNC = 11, - CALLREF = 12, - ARRAY = 13, - QUEUE = 14, - QUEUE_LIST = 15, - LINKED_LIST = 16, - LINKED_LIST_ENTRY = 17, - CHANNEL = 18, - CHANNEL_REQ = 19, - REQ_INFO = 20, - SLICE = 21, - WAIT_GROUP = 22, - METHOD = 23, - DEFER_FUNC = 24, - DEFER_METHOD = 25, - PKG = 26, - FMT_PKG = 27, -} - -export const word_size = 4 - -export class Heap { - // Assume memory is an array of 8 byte words - memory: Memory - size: number - UNASSIGNED: UnassignedNode - freelist: number[] - max_level: number - temp_roots: StackNode - contexts: QueueNode - blocked_contexts: LinkedListNode - mem_left: number - temp = -1 - debugger: Debugger | undefined - constructor(size: number) { - this.size = size - this.mem_left = size - if (this.size % 2 === 1) this.size -= 1 - if (this.size < 34) throw Error('Insufficient Memory') - this.memory = new Memory(size, word_size) - this.max_level = Math.floor(Math.log2(size)) + 1 - this.freelist = [] - for (let i = 0; i < this.max_level; i++) this.freelist.push(-1) - let cur_addr = 0 - while (cur_addr < size) { - this.set_free(cur_addr, true) - const lvl = Math.floor(Math.log2(size - cur_addr)) - this.add_list(cur_addr, lvl) - cur_addr += 2 ** lvl - } - this.UNASSIGNED = UnassignedNode.create(this) - this.temp_roots = StackNode.create(this) - this.contexts = QueueNode.create(this) - this.blocked_contexts = LinkedListNode.create(this) - const context = ContextNode.create(this) - this.contexts.push(context.addr) - } - - get_value(addr: number) { - const tag = this.get_tag(addr) - switch (tag) { - case TAG.UNKNOWN: - return new UnassignedNode(this, addr) - case TAG.NUMBER: - return new IntegerNode(this, addr) - case TAG.FLOAT: - return new FloatNode(this, addr) - case TAG.STRING: - return new StringNode(this, addr) - case TAG.STRING_LIST: - return new StringListNode(this, addr) - case TAG.BOOLEAN: - return new BoolNode(this, addr) - case TAG.CONTEXT: - return new ContextNode(this, addr) - case TAG.FRAME: - return new FrameNode(this, addr) - case TAG.ENVIRONMENT: - return new EnvironmentNode(this, addr) - case TAG.STACK_LIST: - return new StackListNode(this, addr) - case TAG.STACK: - return new StackNode(this, addr) - case TAG.FUNC: - return new FuncNode(this, addr) - case TAG.CALLREF: - return new CallRefNode(this, addr) - case TAG.ARRAY: - return new ArrayNode(this, addr) - case TAG.SLICE: - return new SliceNode(this, addr) - case TAG.QUEUE: - return new QueueNode(this, addr) - case TAG.QUEUE_LIST: - return new QueueListNode(this, addr) - case TAG.LINKED_LIST: - return new LinkedListNode(this, addr) - case TAG.LINKED_LIST_ENTRY: - return new LinkedListEntryNode(this, addr) - case TAG.CHANNEL: - return new ChannelNode(this, addr) - case TAG.CHANNEL_REQ: - return new ChannelReqNode(this, addr) - case TAG.REQ_INFO: - return new ReqInfoNode(this, addr) - case TAG.WAIT_GROUP: - return new WaitGroupNode(this, addr) - case TAG.METHOD: - return new MethodNode(this, addr) - case TAG.DEFER_FUNC: - return new DeferFuncNode(this, addr) - case TAG.DEFER_METHOD: - return new DeferMethodNode(this, addr) - case TAG.PKG: - return new PkgNode(this, addr) - case TAG.FMT_PKG: - return new FmtPkgNode(this, addr) - default: - // return new UnassignedNode(this, addr) - throw Error('Unknown Data Type') - } - } - - // [********** Linked List Helper Funcs ****************] - - /** - * Doubly Linked List Implementation for LogN Freelists - * A Node is the first node if prev_node = cur_addr - * Similarly a node is the last node if next_node = cur_addr - */ - - print_freelist() { - for (let lvl = 0; lvl < this.freelist.length; lvl++) { - let cur = this.freelist[lvl] - const arr = [] - while (cur !== -1) { - arr.push(cur) - const nex = this.get_next(cur) - if (nex === cur) break - cur = nex - } - console.log('LEVEL', lvl, arr) - } - } - - add_list(addr: number, lvl: number) { - this.set_level(addr, lvl) - this.set_prev(addr, addr) - if (this.freelist[lvl] === -1) { - this.set_next(addr, addr) - } else { - this.set_next(addr, this.freelist[lvl]) - this.set_prev(this.freelist[lvl], addr) - } - this.freelist[lvl] = addr - } - - pop_list(addr: number) { - const lvl = this.get_level(addr) - const prev_addr = this.get_prev(addr) - const next_addr = this.get_next(addr) - if (prev_addr === addr) { - // Is head - this.freelist[lvl] = next_addr === addr ? -1 : next_addr - } else { - this.set_next(prev_addr, next_addr === addr ? prev_addr : next_addr) - } - if (next_addr !== addr) { - this.set_prev(next_addr, prev_addr === addr ? next_addr : prev_addr) - } - this.memory.set_word(0, addr) - } - - get_prev(addr: number) { - return this.memory.get_bits(addr, 29, 6) * 2 - } - - set_prev(addr: number, val: number) { - this.memory.set_bits(val / 2, addr, 29, 6) - } - - get_next(addr: number) { - return this.memory.get_bits(addr + 1, 29, 3) * 2 - } - - set_next(addr: number, val: number) { - this.memory.set_bits(val / 2, addr + 1, 29, 3) - } - - set_level(addr: number, lvl: number) { - this.memory.set_bits(lvl, addr, 5, 1) - } - - get_level(addr: number) { - return this.memory.get_bits(addr, 5, 1) - } - - get_size(addr: number) { - return 2 ** this.get_level(addr) - } - - is_free(addr: number) { - return this.memory.get_bits(addr, 1) === 1 - } - - set_free(addr: number, free: boolean) { - this.memory.set_bits(free ? 1 : 0, addr, 1) - } - - // [********** Buddy Block Allocation + Free-ing ****************] - - allocate(size: number) { - const try_allocate = () => { - const lvl = Math.max(1, this.calc_level(size)) - for (let cur_lvl = lvl; cur_lvl < this.freelist.length; cur_lvl++) { - if (this.freelist[cur_lvl] !== -1) { - const addr = this.freelist[cur_lvl] - this.pop_list(addr) - this.set_free(addr, false) - while (cur_lvl > lvl) { - cur_lvl-- - const sibling = addr + 2 ** cur_lvl - this.set_free(sibling, true) - this.add_list(sibling, cur_lvl) - } - this.set_level(addr, lvl) - return addr - } - } - return -1 - } - - let addr = try_allocate() - if (addr === -1) { - this.mark_and_sweep() - addr = try_allocate() - } - if (addr === -1) throw Error('Ran out of memory!') - size = this.get_size(addr) - this.mem_left -= size - return addr - } - - free(addr: number) { - let lvl = this.get_level(addr) - this.mem_left += 2 ** lvl - while (lvl < this.freelist.length) { - const sibling = addr ^ (1 << lvl) - if ( - sibling >= this.size || - !this.is_free(sibling) || - this.get_level(sibling) !== lvl - ) - break - this.set_free(sibling, false) - this.pop_list(sibling) - addr = Math.min(addr, sibling) - lvl++ - } - this.set_free(addr, true) - this.add_list(addr, lvl) - - this.debugger?.identifier_map.delete(addr) - return addr + (1 << lvl) - } - calc_level(x: number) { - return Math.ceil(Math.log2(x)) - } - - temp_push(addr: number) { - this.temp = addr - this.temp_roots.push(addr) - this.temp = -1 - } - - temp_pop() { - this.temp_roots.pop() - } - - // [********** Garbage Collection: Mark and Sweep ****************] - - is_marked(addr: number) { - return this.memory.get_bits(addr, 1, 6) === 1 - } - - set_mark(addr: number, mark: boolean) { - this.memory.set_bits(mark ? 1 : 0, addr, 1, 6) - } - - get_child(addr: number, index: number) { - return this.memory.get_word(addr + index) - } - - set_child(val: number, addr: number, index: number) { - this.memory.set_word(val, addr + index) - } - - set_end_child(addr: number, index: number) { - this.memory.set_number(-1, addr + index) - } - - set_children(addr: number, children: number[], offset = 1) { - const max_size = this.get_size(addr) + addr - addr += offset - if (children.length + addr > max_size) throw Error('Too many children!') - for (let i = 0; i < children.length; i++) { - this.set_child(children[i], addr, i) - } - if (children.length + addr < max_size) { - this.set_end_child(addr, children.length) - } - } - - get_children(addr: number, offset = 1) { - const max_size = this.get_size(addr) + addr - addr += offset - const children: number[] = [] - let idx = 0 - while (idx + addr < max_size) { - if (this.get_child(addr, idx) === -1) break - children.push(this.get_child(addr, idx)) - idx++ - } - return children - } - - mark(addr: number) { - if (addr === -1) return - if (this.is_marked(addr)) return - this.set_mark(addr, true) - const val = this.get_value(addr) - const children = val.get_children() - for (const child of children) { - this.mark(child) - } - } - - mark_and_sweep() { - console.log('CLEAN') - // console.trace() - const roots: number[] = [ - this.contexts.addr, - this.blocked_contexts.addr, - this.temp_roots.addr, - this.UNASSIGNED.addr, - this.temp, - ] - for (const root of roots) { - this.mark(root) - } - for (let cur_addr = 0; cur_addr < this.size; ) { - if (!this.is_free(cur_addr) && !this.is_marked(cur_addr)) { - cur_addr = this.free(cur_addr) - } else { - if (this.is_marked(cur_addr)) this.set_mark(cur_addr, false) - cur_addr += this.get_size(cur_addr) - } - } - return - } - - copy(dst: number, src: number) { - if (dst === -1) return - const sz = this.get_size(src) - for (let i = 0; i < sz; i++) { - this.memory.set_word(this.memory.get_word(src + i), dst + i) - } - } - - clone(addr: number) { - const sz = 2 ** this.get_level(addr) - const res = this.allocate(sz) - // console.log("clone", res) - this.copy(res, addr) - return res - } - - /** - * [ Word Format ] - * - * Free Node: [1 bit free bit] [5 bits Level data] [29 bits Prev Node] [29 bits Next Node] - * Not-Free Node: [1 bit free bit] [5 bits Level data] [1 bit Mark & Sweep] [1 bit Used] - * [1 Byte Type Tag] [2 Bytes Payload - Depends on type] - * - * Assumptions: - * - Address space is 2^32 bytes or 2^29 words max (Browser Memory Limit is 64 GB) - * - Nodes that store data in their adjacent nodes have no children - * Notes: - * - We can actually store the children in ceiling(children/2) words instead - */ - - set_tag(addr: number, tag: number) { - this.memory.set_bytes(tag, addr, 1, 1) - } - - get_tag(addr: number) { - return this.memory.get_bytes(addr, 1, 1) - } -} diff --git a/src/virtual-machine/heap/memory.ts b/src/virtual-machine/heap/memory.ts deleted file mode 100644 index 410a58e..0000000 --- a/src/virtual-machine/heap/memory.ts +++ /dev/null @@ -1,151 +0,0 @@ -const bytes_in_int = 4 // Number of bytes in int -const bits_in_byte = 8 // Number of bits in byte -const bits_in_int = bytes_in_int * bits_in_byte - -export class Memory { - array: ArrayBuffer - view: DataView - word_size: number - /** - * Constructor for memory - * @param size Number of bytes in memory - * @param word_size How many bytes in a word - */ - constructor(size: number, word_size = 4) { - if (!Number.isInteger(Math.log(word_size) / Math.log(2))) - throw Error('Word Size must be power of 2') - this.word_size = word_size - this.array = new ArrayBuffer(size * word_size) - this.view = new DataView(this.array) - } - - check_valid(num_bits: number, bit_offset: number) { - if (bit_offset >= bits_in_int || num_bits < 0 || bit_offset < 0) - throw Error('Invalid number of bits') - } - - /** - * @param addr Starting Byte of the Memory - * @param num_bits Number of bits to retrieve - * @param bit_offset Bit offset within the byte ([0 - 31]: Defaults to 0) - * @returns Number which is the value at the requested position - */ - get_bits(addr: number, num_bits: number, bit_offset = 0) { - this.check_valid(num_bits, bit_offset) - let val = 0 - let carry = 1 - while (num_bits > 0) { - const effective_bits = Math.min(num_bits, bits_in_int - bit_offset) - const mask = (2 ** num_bits - 1) * 2 ** bit_offset - val += - Math.floor( - ((mask & this.view.getUint32(addr * 4)) >>> 0) / 2 ** bit_offset, - ) * carry - carry *= 2 ** effective_bits - bit_offset = 0 - num_bits -= effective_bits - addr += 1 - } - return val - } - - /** - * @param val Value to update - * @param addr Starting Word of the Memory - * @param num_bits Number of bits to retrieve - * @param bit_offset Bit offset within the byte ([0 - 31]: Defaults to 0) - */ - set_bits(val: number, addr: number, num_bits: number, bit_offset = 0) { - this.check_valid(num_bits, bit_offset) - while (num_bits > 0) { - const effective_bits = Math.min(num_bits, bits_in_int - bit_offset) - const mask = ~((2 ** effective_bits - 1) * 2 ** bit_offset) - const val_mask = - ((2 ** num_bits - 1) & val % 2 ** effective_bits) * 2 ** bit_offset - const temp_val = (this.view.getUint32(addr * 4) & mask) | val_mask - this.view.setUint32(addr * 4, temp_val) - bit_offset = 0 - val = Math.floor(val / 2 ** effective_bits) - num_bits -= effective_bits - addr += 1 - } - } - - /** - * @param val Value to update - * @param addr Starting Word - * @param num_of_bytes Number of bytes to modify - */ - set_bytes(val: number, addr: number, num_of_bytes: number, bytes_offset = 0) { - this.set_bits( - val, - addr, - bits_in_byte * num_of_bytes, - bytes_offset * bits_in_byte, - ) - } - - /** - * @param addr Starting Word - * @param num_of_bytes Number of bytes to retrieve - */ - get_bytes(addr: number, num_of_bytes: number, bytes_offset = 0) { - return this.get_bits( - addr, - bits_in_byte * num_of_bytes, - bytes_offset * bits_in_byte, - ) - } - - /** - * @param val Value to update - * @param addr Starting word index - */ - set_word(val: number, addr: number) { - this.view.setInt32(addr * 4, val >>> 0) - } - - /** - * @param addr Starting word index - */ - get_word(addr: number) { - return this.view.getInt32(addr * 4) - } - - /** - * Print out Heap - */ - print() { - let heap_str = '' - const idx_max_len = this.view.byteLength / 4 - for (let i = 0; i < this.view.byteLength; i += 4) { - let str = (this.view.getUint32(i) >>> 0).toString(2) - if (str.length < bits_in_byte * bytes_in_int) { - str = '0'.repeat(bits_in_byte * bytes_in_int - str.length) + str - } - let idx_str = (i / 4).toString() - if (idx_str.length < idx_max_len) { - idx_str = ' '.repeat(idx_max_len - idx_str.length) + idx_str - } - - heap_str += idx_str + ': ' + str + '\n' - } - console.log(heap_str) - } - - get_number(addr: number) { - return this.view.getInt32(addr * this.word_size) - } - - set_number(val: number, addr: number) { - return this.view.setInt32(addr * this.word_size, val) - } - - get_float(addr: number) { - return this.view.getFloat32(addr * this.word_size) - } - - set_float(val: number, addr: number) { - return this.view.setFloat32(addr * this.word_size, val) - } -} diff --git a/src/virtual-machine/heap/types/array.ts b/src/virtual-machine/heap/types/array.ts deleted file mode 100644 index 1edd4cd..0000000 --- a/src/virtual-machine/heap/types/array.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Heap, TAG } from '..' - -import { BaseNode } from './base' - -/** - * Each ArrayNode occupies (2 + `length`) words. - * Word 0: Array tag. - * Word 1: Length of array. - * Remaining `length` words: Each word is the address of an element. - */ -export class ArrayNode extends BaseNode { - static create(length: number, heap: Heap): ArrayNode { - const addr = heap.allocate(2 + length) - heap.set_tag(addr, TAG.ARRAY) - heap.memory.set_number(length, addr + 1) - for (let i = 0; i < length; i++) heap.memory.set_number(-1, addr + i + 2) - return new ArrayNode(heap, addr) - } - - /** - * `defaultCreator` is a function that allocates a default element on the heap, - * and returns its address. - */ - static default( - length: number, - defaultCreator: (heap: Heap) => number, - heap: Heap, - ) { - const addr = heap.allocate(2 + length) - heap.set_tag(addr, TAG.ARRAY) - heap.memory.set_number(length, addr + 1) - heap.temp_push(addr) - for (let i = 0; i < length; i++) heap.memory.set_number(-1, addr + i + 2) - for (let i = 0; i < length; i++) { - heap.memory.set_word(defaultCreator(heap), addr + 2 + i) - } - heap.temp_pop() - return new ArrayNode(heap, addr) - } - - length(): number { - return this.heap.memory.get_number(this.addr + 1) - } - - capacity(): number { - return this.length() - } - - set_child(index: number, address: number) { - this.heap.memory.set_word(address, this.addr + 2 + index) - } - - get_child(index: number): number { - return this.heap.memory.get_word(this.addr + 2 + index) - } - - override get_children(): number[] { - return [...Array(this.length()).keys()].map((x) => this.get_child(x)) - } - - override toString(): string { - const length = this.length() - const elements = [] - for (let i = 0; i < length; i++) { - elements.push(this.heap.get_value(this.get_child(i)).toString()) - } - return `[${elements.join(' ')}]` - } -} - -/** - * Each SliceNode occupies 4 words. - * Word 0: Slice tag. - * Word 1: Underlying array address. - * Word 2: Start (a number), the starting index in the array. - * Word 3: End (a number), the ending index in the array. - */ -export class SliceNode extends BaseNode { - static create( - array: number, - start: number, - end: number, - heap: Heap, - ): SliceNode { - const addr = heap.allocate(5) - heap.set_tag(addr, TAG.SLICE) - heap.memory.set_word(array, addr + 1) - heap.memory.set_number(start, addr + 2) - heap.memory.set_number(end, addr + 3) - return new SliceNode(heap, addr) - } - - static default(heap: Heap): SliceNode { - return SliceNode.create(0, 0, 0, heap) - } - - array(): number { - return this.heap.memory.get_word(this.addr + 1) - } - - arrayNode(): ArrayNode { - return new ArrayNode(this.heap, this.array()) - } - - start(): number { - return this.heap.memory.get_number(this.addr + 2) - } - - end(): number { - return this.heap.memory.get_number(this.addr + 3) - } - - length(): number { - return this.end() - this.start() - } - - capacity(): number { - return this.arrayNode().length() - this.start() - } - - get_child(index: number): number { - return this.arrayNode().get_child(this.start() + index) - } - - set_child(index: number, address: number) { - this.arrayNode().set_child(this.start() + index, address) - } - - override get_children(): number[] { - return [this.array()] - } - - override toString(): string { - const length = this.length() - const elements = [] - for (let i = 0; i < length; i++) { - elements.push(this.heap.get_value(this.get_child(i)).toString()) - } - return `[${elements.join(' ')}]` - } -} diff --git a/src/virtual-machine/heap/types/base.ts b/src/virtual-machine/heap/types/base.ts deleted file mode 100644 index 8149019..0000000 --- a/src/virtual-machine/heap/types/base.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Process } from '../../executor/process' -import { Heap } from '..' - -export abstract class BaseNode { - addr = 0 - heap: Heap - constructor(heap: Heap, addr: number) { - this.heap = heap - this.addr = addr - } - - get_children(): number[] { - return [] - } - - // Calls the select operator on this node. - select(_process: Process, _identifier: string): void { - throw new Error('Unreachable') - } - - // Calls the method of this node, with arguments on the OS. - handleMethodCall( - _process: Process, - _identifier: string, - _argCount: number, - ): void { - throw new Error('Unreachable') - } -} diff --git a/src/virtual-machine/heap/types/channel.ts b/src/virtual-machine/heap/types/channel.ts deleted file mode 100644 index 477531d..0000000 --- a/src/virtual-machine/heap/types/channel.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { Heap, TAG } from '..' - -import { BaseNode } from './base' -import { ContextNode } from './context' -import { LinkedListEntryNode, LinkedListNode } from './linkedlist' -import { QueueNode } from './queue' - -export class ChannelNode extends BaseNode { - static create(buffer: number, heap: Heap) { - const addr = heap.allocate(5) - heap.set_tag(addr, TAG.CHANNEL) - heap.memory.set_number(buffer, addr + 1) - heap.temp_push(addr) - for (let i = 2; i <= 4; i++) heap.memory.set_number(-1, addr + i) - const buffer_queue = QueueNode.create(heap) - heap.memory.set_word(buffer_queue.addr, addr + 2) - const recv_wait_queue = LinkedListNode.create(heap) - heap.memory.set_number(recv_wait_queue.addr, addr + 3) - const send_wait_queue = LinkedListNode.create(heap) - heap.memory.set_number(send_wait_queue.addr, addr + 4) - heap.temp_pop() - return new ChannelNode(heap, addr) - } - static default(heap: Heap) { - return ChannelNode.create(0, heap) - } - - buffer() { - return new QueueNode(this.heap, this.heap.memory.get_number(this.addr + 2)) - } - - wait_queue(recv: boolean) { - return new LinkedListNode( - this.heap, - this.heap.memory.get_number(this.addr + 3 + (recv ? 0 : 1)), - ) - } - - get_buffer_sz() { - return this.heap.memory.get_number(this.addr + 1) - } - - try(req: ReqInfoNode) { - if (req.is_recv()) { - if (this.buffer().sz()) { - // Buffer have entries - const src = this.buffer().pop() - this.heap.copy(req.io(), src) - if (!this.wait_queue(false).is_empty()) { - // If wait queue contain send reqs should unblock since there is space - const send_req = new ReqInfoNode( - this.heap, - this.wait_queue(false).pop_front(), - ) - this.buffer().push(send_req.io()) - send_req.unblock() - } - return true - } - if (!this.wait_queue(false).is_empty()) { - // Case where buffer size is 0 and send reqs in wait queue - const send_req = new ReqInfoNode( - this.heap, - this.wait_queue(false).pop_front(), - ) - this.heap.copy(req.io(), send_req.io()) - send_req.unblock() - return true - } - } else { - if (!this.wait_queue(true).is_empty()) { - // Exist matching recv request (Note assumes wait queue contains no recv req) - const recv_req = new ReqInfoNode( - this.heap, - this.wait_queue(true).pop_front(), - ) - this.heap.copy(recv_req.io(), req.io()) - recv_req.unblock() - return true - } - if (this.buffer().sz() < this.get_buffer_sz()) { - this.buffer().push(req.io()) - return true - } - } - return false - } - wait(req: ReqInfoNode) { - return this.wait_queue(req.is_recv()).push_back(req.addr) - } - - override get_children(): number[] { - return [ - this.buffer().addr, - this.wait_queue(true).addr, - this.wait_queue(false).addr, - ] - } - - override toString(): string { - return 'CHANNEL ' + this.addr.toString() - } -} - -export class ReqInfoNode extends BaseNode { - static create( - io_addr: number, - context: number, - pc: number, - recv: boolean, - heap: Heap, - ) { - const addr = heap.allocate(4) - heap.set_tag(addr, TAG.REQ_INFO) - heap.memory.set_bits(recv ? 1 : 0, addr, 1, 16) - heap.memory.set_number(io_addr, addr + 1) - heap.memory.set_number(context, addr + 2) - heap.memory.set_number(pc, addr + 3) - return new ReqInfoNode(heap, addr) - } - - is_recv() { - return this.heap.memory.get_bits(this.addr, 1, 16) === 1 - } - - io() { - return this.heap.memory.get_number(this.addr + 1) - } - - PC() { - return this.heap.memory.get_number(this.addr + 3) - } - - context() { - return new ContextNode( - this.heap, - this.heap.memory.get_number(this.addr + 2), - ) - } - - unblock() { - const context = this.context() - context.set_PC(this.PC()) - if (this.is_recv()) context.pushOS(this.io()) - const wait_nodes = context.waitlist().get_children() - for (const wait_node of wait_nodes) { - const node = new LinkedListEntryNode(this.heap, wait_node) - node.del() - } - context.set_blocked(false) - this.heap.contexts.push(context.addr) - } - - override get_children(): number[] { - return [this.context().addr, this.io()] - } - - override toString(): string { - return ( - 'CHAN ' + - (this.is_recv() ? 'RECV' : 'SEND') + - '\n' + - this.heap.get_value(this.io()).toString() - ) - } -} - -export class ChannelReqNode extends BaseNode { - static create(channel: number, req: number, heap: Heap) { - const addr = heap.allocate(3) - heap.set_tag(addr, TAG.CHANNEL_REQ) - heap.memory.set_number(channel, addr + 1) - heap.memory.set_number(req, addr + 2) - return new ChannelReqNode(heap, addr) - } - - channel() { - return new ChannelNode( - this.heap, - this.heap.memory.get_number(this.addr + 1), - ) - } - - req() { - return new ReqInfoNode( - this.heap, - this.heap.memory.get_number(this.addr + 2), - ) - } - - override get_children(): number[] { - return [this.channel().addr, this.req().addr] - } - - override toString() { - return this.channel().toString() + '\n' + this.req().toString() - } -} diff --git a/src/virtual-machine/heap/types/context.ts b/src/virtual-machine/heap/types/context.ts deleted file mode 100644 index 70e00ff..0000000 --- a/src/virtual-machine/heap/types/context.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Heap, TAG } from '..' - -import { ArrayNode } from './array' -import { BaseNode } from './base' -import { EnvironmentNode } from './environment' -import { CallRefNode } from './func' -import { PrimitiveNode } from './primitives' -import { StackNode } from './stack' - -export class ContextNode extends BaseNode { - // [metadata | blocked?] [PC] [OS] [RTS] [WaitLists] [DeferStack] - static create(heap: Heap) { - const addr = heap.allocate(6) - heap.set_tag(addr, TAG.CONTEXT) - heap.memory.set_number(0, addr + 1) - heap.temp_push(addr) - for (let i = 2; i <= 5; i++) heap.memory.set_number(-1, addr + i) - heap.memory.set_word(StackNode.create(heap).addr, addr + 2) - heap.memory.set_word(StackNode.create(heap).addr, addr + 3) - heap.memory.set_word(StackNode.create(heap).addr, addr + 5) - heap.temp_pop() - return new ContextNode(heap, addr) - } - - is_blocked() { - return this.heap.memory.get_bits(this.addr, 1, 16) === 1 - } - - set_blocked(val: boolean) { - this.heap.memory.set_bits(val ? 1 : 0, this.addr, 1, 16) - } - - set_PC(PC: number) { - this.heap.memory.set_number(PC, this.addr + 1) - } - - PC() { - return this.heap.memory.get_number(this.addr + 1) - } - - OS() { - return new StackNode(this.heap, this.heap.memory.get_word(this.addr + 2)) - } - - E(): EnvironmentNode { - return this.heap.get_value(this.RTS().peek()) as EnvironmentNode - } - - set_E(addr: number) { - this.pushRTS(addr) - } - - RTS() { - return new StackNode(this.heap, this.heap.memory.get_word(this.addr + 3)) - } - - incr_PC() { - const pc = this.PC() - this.set_PC(pc + 1) - return pc - } - - pushOS(addr: number) { - this.heap.temp_push(addr) - this.OS().push(addr) - this.heap.temp_pop() - } - - peekOS() { - return this.OS().peek() - } - - /** - * @param val 0-indexed from the back - * @returns - */ - peekOSIdx(val: number) { - const sz = this.OS().sz() - return this.OS().get_idx(sz - val - 1) - } - - popOS() { - return this.OS().pop() - } - - /** Pops the OS and constructs a node with its address. */ - popOSNode(nodeType: new (heap: Heap, addr: number) => T) { - return new nodeType(this.heap, this.OS().pop()) - } - - printOS() { - console.log('OS:') - for (let i = 0; i < this.OS().sz(); i++) { - const val = this.heap.get_value(this.OS().get_idx(i)) as PrimitiveNode - console.log(val) - // console.log(val.get_value()) - } - } - - pushRTS(addr: number) { - this.heap.temp_push(addr) - this.RTS().push(addr) - this.heap.temp_pop() - } - - popRTS(): number { - const old_E = this.RTS().pop() - return old_E - } - - peekRTS(): EnvironmentNode | CallRefNode { - return this.heap.get_value(this.RTS().peek()) as - | EnvironmentNode - | CallRefNode - } - - printRTS() { - console.log('RTS:') - for (let i = 0; i < this.RTS().sz(); i++) { - const addr = this.RTS().get_idx(i) - const val = addr === -1 ? -1 : this.heap.get_value(addr) - // console.log(val) - let for_block = false - if (val instanceof EnvironmentNode && val.if_for_block()) for_block = true - console.log( - val, - val === -1 ? -1 : val.get_children().slice(1), - for_block ? 'for block' : '', - ) - } - } - - fork() { - const newContext = ContextNode.create(this.heap) - newContext.set_PC(this.PC()) - newContext.set_E(this.E().addr) - return newContext - } - - set_waitlist(addr: number) { - this.heap.memory.set_number(addr, this.addr + 4) - } - - waitlist() { - return new ArrayNode(this.heap, this.heap.memory.get_number(this.addr + 4)) - } - - deferStack(): StackNode { - return new StackNode(this.heap, this.heap.memory.get_number(this.addr + 5)) - } - - pushDeferStack(): void { - const stack = StackNode.create(this.heap) - this.heap.temp_push(stack.addr) - this.deferStack().push(stack.addr) - this.heap.temp_pop() - } - - peekDeferStack(): StackNode { - return new StackNode(this.heap, this.deferStack().peek()) - } - - popDeferStack(): StackNode { - return new StackNode(this.heap, this.deferStack().pop()) - } - - override get_children(): number[] { - const children = [ - this.RTS().addr, - this.OS().addr, - this.waitlist().addr, - this.deferStack().addr, - ] - return children - } -} diff --git a/src/virtual-machine/heap/types/environment.ts b/src/virtual-machine/heap/types/environment.ts deleted file mode 100644 index 634546b..0000000 --- a/src/virtual-machine/heap/types/environment.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Heap, TAG } from '..' - -import { BaseNode } from './base' - -export class FrameNode extends BaseNode { - static create(frame_size: number, heap: Heap) { - const addr = heap.allocate(frame_size + 1) - - heap.set_tag(addr, TAG.FRAME) - heap.set_children(addr, Array(frame_size).fill(heap.UNASSIGNED), 1) - return new FrameNode(heap, addr) - } - - set_idx(addr: number, index: number) { - this.heap.set_child(addr, this.addr + 1, index) - } - - get_idx(index: number) { - return this.heap.get_child(this.addr + 1, index) - } - - override get_children(): number[] { - return this.heap.get_children(this.addr, 1) - } -} - -export class EnvironmentNode extends BaseNode { - // [metadata] [frame] [...parents] - // Note parents is terminated by -1 in the heap or by end of node block capacity - static create( - frame: number, - parents: number[], - for_block: boolean, - heap: Heap, - ) { - const addr = heap.allocate(parents.length + 2) - heap.set_tag(addr, TAG.ENVIRONMENT) - heap.memory.set_bits(for_block ? 1 : 0, addr, 1, 16) - heap.memory.set_word(frame, addr + 1) - heap.set_children(addr, parents, 2) - return new EnvironmentNode(heap, addr) - } - - extend_env(frame: number, for_block = false) { - const parents = [this.addr] - let cur_par = new EnvironmentNode(this.heap, this.addr) - let new_par = cur_par.get_parent(parents.length - 1) - while (new_par) { - parents.push(new_par.addr) - cur_par = new_par - new_par = cur_par.get_parent(parents.length - 1) - } - return EnvironmentNode.create(frame, parents, for_block, this.heap) - } - - get_frame() { - return new FrameNode(this.heap, this.heap.memory.get_word(this.addr + 1)) - } - - get_frame_idx(idx: number): FrameNode { - if (idx === 0) return this.get_frame() - const lvl = Math.floor(Math.log2(idx)) - const par = this.get_parent(lvl) - if (!par) throw Error('Execution Error: Invalid frame index') - return par.get_frame_idx(idx - 2 ** lvl) - } - - get_parent(idx: number) { - if ( - idx + 2 >= this.heap.get_size(this.addr) || - this.heap.memory.get_number(this.addr + idx + 2) === -1 - ) - return undefined - else - return new EnvironmentNode( - this.heap, - this.heap.memory.get_number(this.addr + idx + 2), - ) - } - - get_var(frame_idx: number, var_idx: number) { - return this.get_frame_idx(frame_idx).get_idx(var_idx) - } - - if_for_block() { - return this.heap.memory.get_bits(this.addr, 1, 16) === 1 - } - - override get_children(): number[] { - return [this.get_frame().addr, ...this.heap.get_children(this.addr, 2)] - } -} diff --git a/src/virtual-machine/heap/types/fmt.ts b/src/virtual-machine/heap/types/fmt.ts deleted file mode 100644 index 03705d3..0000000 --- a/src/virtual-machine/heap/types/fmt.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Process } from '../../executor/process' -import { Heap, TAG } from '..' - -import { BaseNode } from './base' -import { MethodNode } from './func' - -/** - * This node represents an uninitialized package. It only occupies one word, its tag. - */ -export class PkgNode extends BaseNode { - static create(heap: Heap): PkgNode { - const addr = heap.allocate(1) - heap.set_tag(addr, TAG.PKG) - return new PkgNode(heap, addr) - } - - static default(heap: Heap): PkgNode { - return PkgNode.create(heap) - } - - override toString(): string { - return 'PKG' - } -} - -/** - * This node represents the `fmt` package. It only occupies one word, its tag. - */ -export class FmtPkgNode extends BaseNode { - static create(heap: Heap): FmtPkgNode { - const addr = heap.allocate(1) - heap.set_tag(addr, TAG.FMT_PKG) - return new FmtPkgNode(heap, addr) - } - - static default(heap: Heap): FmtPkgNode { - return FmtPkgNode.create(heap) - } - - override select(process: Process, identifier: string): void { - process.context.pushOS( - MethodNode.create(this.addr, identifier, this.heap).addr, - ) - } - - /** Arguments to builtin methods should be on the OS. Remember to pop the receiver from OS. */ - override handleMethodCall( - process: Process, - identifier: string, - argCount: number, - ) { - if (identifier === 'Println') { - this.handlePrintln(process, argCount) - } - } - - handlePrintln(process: Process, argCount: number): void { - const argAddresses = [] - for (let i = 0; i < argCount; i++) { - argAddresses.push(process.context.popOS()) - } - for (let i = argCount - 1; i >= 0; i--) { - const string = process.heap.get_value(argAddresses[i]).toString() - process.print(string) - process.print(i > 0 ? ' ' : '\n') - } - process.context.popOS() - } - - override get_children(): number[] { - return [] - } - - override toString(): string { - return 'FMT PACKAGE' - } -} diff --git a/src/virtual-machine/heap/types/func.ts b/src/virtual-machine/heap/types/func.ts deleted file mode 100644 index d084396..0000000 --- a/src/virtual-machine/heap/types/func.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { Process } from '../../executor/process' -import { Heap, TAG } from '..' - -import { BaseNode } from './base' -import { StringNode } from './primitives' -import { StackNode } from './stack' - -export class FuncNode extends BaseNode { - static create(PC: number, env: number, heap: Heap) { - const addr = heap.allocate(3) - - heap.set_tag(addr, TAG.FUNC) - heap.memory.set_word(PC, addr + 1) - heap.memory.set_word(env, addr + 2) - return new FuncNode(heap, addr) - } - - static default(heap: Heap) { - return FuncNode.create(-1, -1, heap) - } - - PC() { - return this.heap.memory.get_word(this.addr + 1) - } - - E() { - return this.heap.memory.get_word(this.addr + 2) - } - - override get_children(): number[] { - return [this.E()] - } - - override toString(): string { - return 'CLOSURE' - } -} - -export class CallRefNode extends BaseNode { - static create(PC: number, heap: Heap) { - const addr = heap.allocate(2) - - heap.set_tag(addr, TAG.CALLREF) - heap.memory.set_word(PC, addr + 1) - return new CallRefNode(heap, addr) - } - PC() { - return this.heap.memory.get_word(this.addr + 1) - } -} - -/** - * Represents a hardcoded method. - * Word 0 - MethodNode tag. - * Word 1 - Receiver address. - * Word 2 - String literal address, representing the method name. - * */ -export class MethodNode extends BaseNode { - static create(receiver: number, identifier: string, heap: Heap): MethodNode { - const addr = heap.allocate(3) - heap.set_tag(addr, TAG.METHOD) - heap.temp_push(addr) - heap.memory.set_number(-1, addr + 2) - heap.memory.set_word(receiver, addr + 1) - heap.memory.set_word(StringNode.create(identifier, heap).addr, addr + 2) - heap.temp_pop() - return new MethodNode(heap, addr) - } - - receiverAddr(): number { - return this.heap.memory.get_word(this.addr + 1) - } - - receiver(): BaseNode { - return this.heap.get_value(this.receiverAddr()) - } - - identifierAddr(): number { - return this.heap.memory.get_word(this.addr + 2) - } - - identifier(): string { - return new StringNode(this.heap, this.identifierAddr()).get_value() - } - - override get_children(): number[] { - return [this.receiverAddr(), this.identifierAddr()] - } - - override toString(): string { - return this.identifier() - } -} - -/** - * Stores the function literal and arguments of a deferred function call. - * Word 0: DeferFuncNode tag. - * Word 1: Function literal address. - * Word 2: Address of a stack containing all the arguments (first argument at the top). - */ -export class DeferFuncNode extends BaseNode { - static create(argCount: number, process: Process): DeferFuncNode { - const addr = process.heap.allocate(3) - process.heap.temp_push(addr) - process.heap.set_tag(addr, TAG.DEFER_FUNC) - process.heap.memory.set_word(-1, addr + 2) - - const stack = StackNode.create(process.heap) - process.heap.memory.set_word(stack.addr, addr + 2) - for (let i = 0; i < argCount; i++) stack.push(process.context.popOS()) - - process.heap.memory.set_word(process.context.popOS(), addr + 1) - - process.heap.temp_pop() - return new DeferFuncNode(process.heap, addr) - } - - funcAddr(): number { - return this.heap.memory.get_word(this.addr + 1) - } - - func(): FuncNode { - return new FuncNode(this.heap, this.funcAddr()) - } - - argCount(): number { - return this.heap.memory.get_number(this.addr + 2) - } - - stackAddr(): number { - return this.heap.memory.get_word(this.addr + 3) - } - - stack(): StackNode { - return new StackNode(this.heap, this.stackAddr()) - } - - override get_children(): number[] { - return [this.funcAddr(), this.stackAddr()] - } - - override toString(): string { - return 'DEFER ' + this.func().toString() - } -} - -/** - * Stores the MethodNode and arguments of a deferred method call. - * Word 0: DeferMethodNode tag. - * Word 1: MethodNode address. - * Word 2: Address of a stack containing all the arguments (first argument at the top). - */ -export class DeferMethodNode extends BaseNode { - static create(argCount: number, process: Process): DeferMethodNode { - const addr = process.heap.allocate(3) - process.heap.set_tag(addr, TAG.DEFER_METHOD) - process.heap.temp_push(addr) - process.heap.memory.set_word(-1, addr + 1) - process.heap.memory.set_word(-1, addr + 2) - - const stack = StackNode.create(process.heap) - process.heap.memory.set_word(stack.addr, addr + 2) - for (let i = 0; i < argCount; i++) stack.push(process.context.popOS()) - - const methodNode = process.context.popOS() - process.heap.memory.set_word(methodNode, addr + 1) - - process.heap.temp_pop() - return new DeferMethodNode(process.heap, addr) - } - - methodAddr(): number { - return this.heap.memory.get_word(this.addr + 1) - } - - methodNode(): MethodNode { - return this.heap.get_value(this.methodAddr()) as MethodNode - } - - stackAddr(): number { - return this.heap.memory.get_word(this.addr + 2) - } - - stack(): StackNode { - return this.heap.get_value(this.stackAddr()) as StackNode - } - - override get_children(): number[] { - return [this.methodAddr(), this.stackAddr()] - } - - override toString(): string { - return 'DEFER ' + this.methodNode().toString() - } -} diff --git a/src/virtual-machine/heap/types/linkedlist.ts b/src/virtual-machine/heap/types/linkedlist.ts deleted file mode 100644 index 82eb333..0000000 --- a/src/virtual-machine/heap/types/linkedlist.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { Heap, TAG } from '..' - -import { BaseNode } from './base' - -export class LinkedListNode extends BaseNode { - static create(heap: Heap) { - const addr = heap.allocate(3) - heap.set_tag(addr, TAG.LINKED_LIST) - heap.temp_push(addr) - for (let i = 1; i <= 2; i++) heap.memory.set_number(-1, addr + i) - const head = LinkedListEntryNode.create(-1, heap) - heap.memory.set_number(head.addr, addr + 1) - const tail = LinkedListEntryNode.create(-1, heap) - heap.memory.set_number(tail.addr, addr + 2) - heap.temp_pop() - head.set_next(tail.addr) - tail.set_prev(head.addr) - return new LinkedListNode(heap, addr) - } - - head() { - return new LinkedListEntryNode( - this.heap, - this.heap.memory.get_number(this.addr + 1), - ) - } - tail() { - return new LinkedListEntryNode( - this.heap, - this.heap.memory.get_number(this.addr + 2), - ) - } - - is_empty() { - return this.head().next().addr === this.tail().addr - } - - push_back(addr: number) { - const newNode = LinkedListEntryNode.create(addr, this.heap) - const tail = this.tail() - const pre = tail.prev() - pre.set_next(newNode.addr) - tail.set_prev(newNode.addr) - newNode.set_next(tail.addr) - newNode.set_prev(pre.addr) - return newNode.addr - } - - push_front(addr: number) { - const newNode = LinkedListEntryNode.create(addr, this.heap) - const head = this.head() - const nex = head.next() - head.set_next(newNode.addr) - nex.set_prev(newNode.addr) - newNode.set_next(nex.addr) - newNode.set_prev(head.addr) - return newNode.addr - } - - pop_front() { - if (this.is_empty()) throw Error('Linkedlist Empty') - const node = this.tail().prev() - node.del() - return node.get_val() - } - - pop_back() { - if (this.is_empty()) throw Error('Linkedlist Empty') - const node = this.head().next() - node.del() - return node.get_val() - } - - override get_children(): number[] { - return [this.head().addr, this.tail().addr] - } - - get_items() { - const vals = [] - let cur = this.head().next() - while (cur.addr !== this.tail().addr) { - vals.push(cur.get_val()) - cur = cur.next() - } - return vals - } - - print() { - this.head().next().print() - } -} - -export class LinkedListEntryNode extends BaseNode { - static create(val: number, heap: Heap) { - const addr = heap.allocate(4) - heap.set_tag(addr, TAG.LINKED_LIST_ENTRY) - heap.memory.set_number(addr, addr + 1) - heap.memory.set_number(addr, addr + 2) - heap.memory.set_number(val, addr + 3) - return new LinkedListEntryNode(heap, addr) - } - - set_prev(val: number) { - this.heap.memory.set_number(val, this.addr + 1) - } - - prev() { - return new LinkedListEntryNode( - this.heap, - this.heap.memory.get_number(this.addr + 1), - ) - } - - set_next(val: number) { - this.heap.memory.set_number(val, this.addr + 2) - } - - next() { - return new LinkedListEntryNode( - this.heap, - this.heap.memory.get_number(this.addr + 2), - ) - } - - get_val() { - return this.heap.memory.get_number(this.addr + 3) - } - - del() { - const pre = this.prev() - const nex = this.next() - pre.set_next(nex.addr) - nex.set_prev(pre.addr) - } - - override get_children(): number[] { - return [this.prev().addr, this.next().addr, this.get_val()] - } - - print() { - if (this.get_val() === -1) return - const val = this.heap.get_value(this.get_val()) - console.log(val) - this.next().print() - } -} diff --git a/src/virtual-machine/heap/types/primitives.ts b/src/virtual-machine/heap/types/primitives.ts deleted file mode 100644 index a717368..0000000 --- a/src/virtual-machine/heap/types/primitives.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { - BoolBinaryOp, - BoolUnaryOp, - NumBinaryOp, - NumStrBinaryToBoolOp, - NumUnaryOp, - StrBinaryOp, -} from '../../executor/ops' -import { Heap, TAG, word_size } from '..' - -import { BaseNode } from './base' - -export abstract class PrimitiveNode extends BaseNode { - abstract apply_binop(operand: PrimitiveNode, operator: string): PrimitiveNode - abstract apply_unary(operator: string): PrimitiveNode - abstract get_value(): number | boolean | string - - override toString(): string { - return this.get_value().toString() - } -} - -export class IntegerNode extends PrimitiveNode { - static create(num: number, heap: Heap) { - const addr = heap.allocate(2) - heap.set_tag(addr, TAG.NUMBER) - heap.memory.set_number(num, addr + 1) - return new IntegerNode(heap, addr) - } - static default(heap: Heap) { - return IntegerNode.create(0, heap) - } - - get_value() { - return this.heap.memory.get_number(this.addr + 1) - } - - override apply_binop(operand: IntegerNode, operator: string): PrimitiveNode { - if (NumBinaryOp[operator]) { - return IntegerNode.create( - NumBinaryOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - if (NumStrBinaryToBoolOp[operator]) { - return BoolNode.create( - NumStrBinaryToBoolOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - throw Error('Invalid Operation') - } - - override apply_unary(operator: string): PrimitiveNode { - if (NumUnaryOp[operator]) { - return IntegerNode.create( - NumUnaryOp[operator](this.get_value()), - this.heap, - ) - } - throw Error('Invalid Operation') - } -} - -export class FloatNode extends PrimitiveNode { - static create(num: number, heap: Heap) { - const addr = heap.allocate(2) - heap.set_tag(addr, TAG.FLOAT) - heap.memory.set_float(num, addr + 1) - return new FloatNode(heap, addr) - } - static default(heap: Heap) { - return FloatNode.create(0.0, heap) - } - - get_value() { - return this.heap.memory.get_float(this.addr + 1) - } - - override apply_binop(operand: FloatNode, operator: string): PrimitiveNode { - if (NumBinaryOp[operator]) { - return FloatNode.create( - NumBinaryOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - if (NumStrBinaryToBoolOp[operator]) { - return BoolNode.create( - NumStrBinaryToBoolOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - throw Error('Invalid Operation') - } - override apply_unary(operator: string): PrimitiveNode { - if (NumUnaryOp[operator]) { - return FloatNode.create(NumUnaryOp[operator](this.get_value()), this.heap) - } - throw Error('Invalid Operation') - } -} - -export class BoolNode extends PrimitiveNode { - static create(val: boolean, heap: Heap) { - const addr = heap.allocate(1) - heap.set_tag(addr, TAG.BOOLEAN) - heap.memory.set_bits(val ? 1 : 0, addr, 1, 16) - return new BoolNode(heap, addr) - } - static default(heap: Heap) { - return BoolNode.create(false, heap) - } - - get_value() { - return this.heap.memory.get_bits(this.addr, 1, 16) === 1 - } - override apply_binop(operand: BoolNode, operator: string): PrimitiveNode { - if (BoolBinaryOp[operator]) { - return BoolNode.create( - BoolBinaryOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - throw Error('Invalid Operation') - } - override apply_unary(operator: string): PrimitiveNode { - if (BoolUnaryOp[operator]) { - return BoolNode.create(BoolUnaryOp[operator](this.get_value()), this.heap) - } - throw Error('Invalid Operation') - } -} - -export class StringNode extends PrimitiveNode { - static create(str: string, heap: Heap) { - const addr = heap.allocate(2) - heap.set_tag(addr, TAG.STRING) - heap.temp_push(addr) - heap.memory.set_number(-1, addr + 1) - const list_addr = heap.allocate(Math.ceil((str.length + 1) / word_size) + 1) - heap.set_tag(list_addr, TAG.STRING_LIST) - heap.memory.set_word(list_addr, addr + 1) - heap.temp_pop() - for (let i = 0; i <= str.length; i++) { - let val = 0 - if (i < str.length) val = str.charCodeAt(i) - heap.memory.set_bytes( - val, - Math.floor(i / word_size) + list_addr + 1, - 1, - i % word_size, - ) - } - return new StringNode(heap, addr) - } - - static default(heap: Heap) { - return StringNode.create('', heap) - } - - get_list() { - return this.heap.memory.get_word(this.addr + 1) - } - - get_value() { - let res = '' - let idx = 0 - const max_sz = (this.heap.get_size(this.get_list()) - 1) * word_size - while (idx < max_sz) { - const val = this.heap.memory.get_bytes( - Math.floor(idx / word_size) + this.get_list() + 1, - 1, - idx % word_size, - ) - if (val === 0) break - res += String.fromCharCode(val) - idx++ - } - return res - } - - override get_children(): number[] { - return [this.get_list()] - } - - override apply_binop(operand: StringNode, operator: string): PrimitiveNode { - if (StrBinaryOp[operator]) { - return StringNode.create( - StrBinaryOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - if (NumStrBinaryToBoolOp[operator]) { - return BoolNode.create( - NumStrBinaryToBoolOp[operator](this.get_value(), operand.get_value()), - this.heap, - ) - } - throw Error('Invalid Operation') - } - override apply_unary(_operator: string): PrimitiveNode { - throw Error('Invalid Opeartion') - } -} -export class StringListNode extends BaseNode {} - -export class UnassignedNode extends BaseNode { - static create(heap: Heap) { - const addr = heap.allocate(1) - heap.set_tag(addr, TAG.UNKNOWN) - return new UnassignedNode(heap, addr) - } -} diff --git a/src/virtual-machine/heap/types/queue.ts b/src/virtual-machine/heap/types/queue.ts deleted file mode 100644 index e24bf3b..0000000 --- a/src/virtual-machine/heap/types/queue.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { Heap, TAG } from '..' - -import { BaseNode } from './base' - -export class QueueNode extends BaseNode { - static create(heap: Heap) { - const addr = heap.allocate(2) - heap.set_tag(addr, TAG.QUEUE) - heap.temp_push(addr) - heap.memory.set_number(-1, addr + 1) - const list = QueueListNode.create(heap) - heap.temp_pop() - heap.memory.set_word(list.addr, addr + 1) - return new QueueNode(heap, addr) - } - - list() { - return new QueueListNode( - this.heap, - this.heap.memory.get_word(this.addr + 1), - ) - } - - push(addr: number) { - const list = this.list() - list.push(addr) - this.heap.memory.set_word(list.addr, this.addr + 1) - } - pop() { - const list = this.list() - const res = list.pop() - this.heap.memory.set_word(list.addr, this.addr + 1) - return res - } - peek() { - return this.list().peek() - } - - sz() { - return this.list().get_sz() - } - - get_vals() { - return this.list().get_children() - } - - override get_children(): number[] { - return [this.heap.memory.get_word(this.addr + 1)] - } -} - -export class QueueListNode extends BaseNode { - static init_sz = 8 - static create(heap: Heap) { - const addr = heap.allocate(this.init_sz) - heap.set_tag(addr, TAG.QUEUE_LIST) - heap.memory.set_number(0, addr + 1) - heap.memory.set_number(0, addr + 2) - heap.memory.set_number(0, addr + 3) - return new QueueListNode(heap, addr) - } - - resize(new_size: number) { - const new_pos = this.heap.allocate(new_size) - this.heap.set_tag(new_pos, TAG.QUEUE_LIST) - const newQueueList = new QueueListNode(this.heap, new_pos) - newQueueList.set_sz(this.get_sz()) - newQueueList.set_start(0) - newQueueList.set_end(this.get_sz()) - const start = this.get_start() - const cap = this.get_cap() - for (let x = 0; x < this.get_sz(); x++) { - newQueueList.set_idx(this.get_idx((start + x) % cap), x) - } - this.addr = new_pos - } - - get_cap() { - return this.heap.get_size(this.addr) - 4 - } - - get_sz() { - return this.heap.memory.get_number(this.addr + 1) - } - - set_sz(val: number) { - this.heap.memory.set_number(val, this.addr + 1) - } - - get_start() { - return this.heap.memory.get_number(this.addr + 2) - } - - set_start(val: number) { - this.heap.memory.set_number(val, this.addr + 2) - } - - get_end() { - return this.heap.memory.get_number(this.addr + 3) - } - - set_end(val: number) { - this.heap.memory.set_number(val, this.addr + 3) - } - - push(addr: number) { - const sz = this.get_sz() - const node_sz = this.heap.get_size(this.addr) - if (sz + 5 > node_sz) this.resize(node_sz * 2) - this.set_idx(addr, this.get_end()) - this.set_end((this.get_end() + 1) % this.get_cap()) - this.set_sz(sz + 1) - } - - pop() { - const sz = this.get_sz() - if (sz === 0) throw Error('List Empty!') - const node_sz = this.heap.get_size(this.addr) - const val = this.get_idx(this.get_start()) - this.set_start((this.get_start() + 1) % this.get_cap()) - this.set_sz(sz - 1) - if (4 * (sz + 3) < node_sz) this.resize(node_sz / 2) - return val - } - - peek() { - const sz = this.get_sz() - if (sz === 0) throw Error('Queue List is Empty!') - return this.get_idx(this.get_start()) - } - - get_idx(index: number) { - return this.heap.memory.get_word(this.addr + 4 + index) - } - - set_idx(val: number, index: number) { - return this.heap.memory.set_word(val, this.addr + 4 + index) - } - - override get_children(): number[] { - const sz = this.get_sz() - const start = this.get_start() - const cap = this.get_cap() - return [...Array(sz).keys()].map((x) => this.get_idx((start + x) % cap)) - } -} diff --git a/src/virtual-machine/heap/types/stack.ts b/src/virtual-machine/heap/types/stack.ts deleted file mode 100644 index 62218c8..0000000 --- a/src/virtual-machine/heap/types/stack.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Heap, TAG } from '..' - -import { BaseNode } from './base' - -export class StackNode extends BaseNode { - static create(heap: Heap) { - const addr = heap.allocate(2) - heap.set_tag(addr, TAG.STACK) - if (heap.temp_roots) heap.temp_push(addr) - heap.memory.set_number(-1, addr + 1) - const list = StackListNode.create(heap) - if (heap.temp_roots) heap.temp_pop() - heap.memory.set_word(list.addr, addr + 1) - return new StackNode(heap, addr) - } - - list() { - return new StackListNode( - this.heap, - this.heap.memory.get_word(this.addr + 1), - ) - } - - push(addr: number) { - const list = this.list() - list.push(addr) - this.heap.memory.set_word(list.addr, this.addr + 1) - } - pop() { - const list = this.list() - const res = list.pop() - this.heap.memory.set_word(list.addr, this.addr + 1) - return res - } - peek() { - return this.list().peek() - } - get_idx(idx: number) { - return this.list().get_idx(idx) - } - sz() { - return this.list().get_sz() - } - override get_children(): number[] { - return [this.list().addr] - } -} - -export class StackListNode extends BaseNode { - static init_sz = 4 - static create(heap: Heap) { - const addr = heap.allocate(this.init_sz) - heap.set_tag(addr, TAG.STACK_LIST) - heap.memory.set_number(0, addr + 1) - return new StackListNode(heap, addr) - } - - resize(new_size: number) { - const new_pos = this.heap.allocate(new_size) - this.heap.set_tag(new_pos, TAG.STACK_LIST) - const new_list = new StackListNode(this.heap, new_pos) - const sz = this.get_sz() - new_list.set_sz(sz) - for (let i = 0; i < sz; i++) { - new_list.set_idx(this.get_idx(i), i) - } - this.addr = new_pos - } - - get_sz() { - return this.heap.memory.get_number(this.addr + 1) - } - - set_sz(val: number) { - this.heap.memory.set_number(val, this.addr + 1) - } - - push(addr: number) { - const sz = this.get_sz() - const capacity = this.heap.get_size(this.addr) - if (sz + 3 > capacity) this.resize(capacity * 2) - this.set_idx(addr, sz) - this.set_sz(sz + 1) - } - - pop() { - const sz = this.get_sz() - if (sz === 0) throw Error('List Empty!') - const capacity = this.heap.get_size(this.addr) - const val = this.get_idx(sz - 1) - this.set_sz(sz - 1) - if (4 * (sz + 1) < capacity) this.resize(capacity / 2) - return val - } - - peek() { - const sz = this.get_sz() - if (sz === 0) throw Error('List Empty!') - return this.get_idx(sz - 1) - } - - get_idx(index: number) { - return this.heap.memory.get_word(this.addr + 2 + index) - } - - set_idx(val: number, index: number) { - return this.heap.memory.set_word(val, this.addr + 2 + index) - } - - override get_children(): number[] { - const sz = this.get_sz() - return [...Array(sz).keys()].map((x) => this.get_idx(x)) - } -} diff --git a/src/virtual-machine/heap/types/waitGroup.ts b/src/virtual-machine/heap/types/waitGroup.ts deleted file mode 100644 index 7aed023..0000000 --- a/src/virtual-machine/heap/types/waitGroup.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Process } from '../../executor/process' -import { Heap, TAG } from '..' - -import { ArrayNode } from './array' -import { BaseNode } from './base' -import { ContextNode } from './context' -import { MethodNode } from './func' -import { LinkedListEntryNode } from './linkedlist' -import { IntegerNode } from './primitives' -import { QueueNode } from './queue' - -/** - * Each WaitGroupNode occupies 3 words. - * Word 0: Wait Group tag. - * Word 1: A non-negative number, representing how number of .Add - number of .Done calls. - * Word 2: The address to a queue of waiting contexts. - */ -export class WaitGroupNode extends BaseNode { - static create(heap: Heap): WaitGroupNode { - const addr = heap.allocate(3) - heap.set_tag(addr, TAG.WAIT_GROUP) - heap.temp_push(addr) - heap.memory.set_number(-1, addr + 2) - heap.memory.set_number(0, addr + 1) - heap.memory.set_word(QueueNode.create(heap).addr, addr + 2) - heap.temp_pop() - return new WaitGroupNode(heap, addr) - } - - static default(heap: Heap): WaitGroupNode { - return WaitGroupNode.create(heap) - } - - count(): number { - return this.heap.memory.get_number(this.addr + 1) - } - - set_count(new_count: number): void { - if (new_count < 0) { - throw new Error('sync: negative WaitGroup counter.') - } - this.heap.memory.set_number(new_count, this.addr + 1) - } - - queue(): QueueNode { - return new QueueNode(this.heap, this.heap.memory.get_word(this.addr + 2)) - } - - override select(process: Process, identifier: string): void { - process.context.pushOS( - MethodNode.create(this.addr, identifier, this.heap).addr, - ) - } - - /** Arguments to builtin methods should be on the OS. Remember to pop the receiver from OS. */ - override handleMethodCall( - process: Process, - identifier: string, - _argCount: number, - ) { - if (identifier === 'Add') { - this.handleAdd(process) - } else if (identifier === 'Done') { - this.handleDone(process) - } else if (identifier === 'Wait') { - this.handleWait(process) - } - } - - handleAdd(process: Process): void { - const amount = process.context.popOSNode(IntegerNode).get_value() - process.context.popOS() - this.set_count(this.count() + amount) - } - - handleDone(process: Process): void { - process.context.popOS() - this.set_count(this.count() - 1) - if (this.count() === 0) { - while (this.queue().sz()) { - const context = new ContextNode(this.heap, this.queue().pop()) - const wait_nodes = context.waitlist().get_children() - for (const wait_node of wait_nodes) { - const node = new LinkedListEntryNode(this.heap, wait_node) - node.del() - } - context.set_blocked(false) - this.heap.contexts.push(context.addr) - } - } - } - - handleWait(process: Process): void { - process.context.popOS() - if (this.count() === 0) return - this.queue().push(process.context.addr) - process.context.set_waitlist(ArrayNode.create(1, process.heap).addr) - process.context - .waitlist() - .set_child( - 0, - process.heap.blocked_contexts.push_back(process.context.addr), - ) - process.context.set_blocked(true) - } - - override get_children(): number[] { - return [this.queue().addr] - } - - override toString(): string { - return 'WG COUNT ' + this.count().toString() - // throw new Error('Unimplemented') - } -} diff --git a/src/virtual-machine/index.ts b/src/virtual-machine/index.ts deleted file mode 100644 index 2b1d206..0000000 --- a/src/virtual-machine/index.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Instruction } from './compiler/instructions' -import { StateInfo } from './executor/debugger' -import parser from './parser/parser' -import { SourceFileToken, TokenLocation } from './parser/tokens' -import { compile_tokens, CompileError } from './compiler' -import { execute_instructions } from './executor' - -interface InstructionData { - val: string -} - -interface ProgramData { - output?: string - instructions: InstructionData[] - error?: { - message: string - type: 'parse' | 'compile' | 'runtime' - details?: Error | string - } - visualData: StateInfo[] -} - -const runCode = ( - source_code: string, - heapsize: number, - visualisation = true, -): ProgramData => { - // Parsing. - let tokens: SourceFileToken - try { - tokens = parser.parse(source_code) as SourceFileToken - console.log(tokens) - } catch (err) { - const message = (err as Error).message - return { - instructions: [], - output: 'Syntax Error!', - error: { - message, - type: 'parse', - details: err as string, - }, - visualData: [], - } - } - - // Compilation. - let instructions: Instruction[] = [] - let symbols: (TokenLocation | null)[] = [] - try { - const temp = compile_tokens(tokens) - instructions = temp.instructions - symbols = temp.symbols - console.log(instructions) - } catch (err) { - const message = (err as CompileError).message - return { - instructions: [], - output: 'Compilation Error!', - error: { - message, - type: 'compile', - details: err as CompileError, - }, - visualData: [], - } - } - - // Execution. - const result = execute_instructions( - instructions, - heapsize, - symbols, - visualisation, - ) - if (result.errorMessage) { - console.warn(result.errorMessage) - return { - instructions: [], - output: 'Runtime Error!', - error: { - message: result.errorMessage, - type: 'runtime', - details: result.errorMessage, - }, - visualData: [], - } - } - - return { - instructions: [], - output: result.stdout, - visualData: result.visual_data, - error: undefined, - } -} - -export { type InstructionData, type ProgramData, runCode } diff --git a/src/virtual-machine/parser/parser.peggy b/src/virtual-machine/parser/parser.peggy deleted file mode 100644 index 0157473..0000000 --- a/src/virtual-machine/parser/parser.peggy +++ /dev/null @@ -1,500 +0,0 @@ -{{ - import { - IntegerLiteralToken, - FloatLiteralToken, - StringLiteralToken, - BinaryOperator, - UnaryOperator, - SourceFileToken, - ImportToken, - FunctionDeclarationToken, - VariableDeclarationToken, - ConstantDeclarationToken, - PrimaryExpressionToken, - AssignmentStatementToken, - IdentifierToken, - ShortVariableDeclarationToken, - IncDecStatementToken, - ReturnStatementToken, - BreakStatementToken, - ContinueStatementToken, - FallthroughStatementToken, - IfStatementToken, - BlockToken, - ForStatementToken, - DeferStatementToken, - PrimitiveTypeToken, - ArrayTypeToken, - SliceTypeToken, - FunctionTypeToken, - MapTypeToken, - ChannelTypeToken, - SelectorToken, - IndexToken, - SliceToken, - CallToken, - SwitchCaseToken, - SwitchStatementToken, - FunctionLiteralToken, - GoStatementToken, - SendStatementToken, - ReceiveStatementToken, - CommunicationClauseToken, - SelectStatementToken, - BuiltinCallToken, - LiteralValueToken, - ArrayLiteralToken, - SliceLiteralToken, - QualifiedIdentifierToken, - } from './tokens' - - // Returns an AST with left to right precedence - function leftPrecTree(rest, right) { - if (!rest.length) return right - let last = rest.pop() - return last[2](leftPrecTree(rest, last[0]), right) - } -}} - -/* -The result of parsing a source file should be a single SourceFileToken. -A token may be composed of many other tokens. -The structure of what each token looks like can be found in each Token class. -*/ - -//* =============== Root =============== -start = _ @SourceFile _ - - -//* =============== Whitespace =============== -// By convention, _ is used to eat up whitespace. -_ = ([ \t\r\n] / comment) * - - - - - -//* =============== Source Code Representation =============== -//* Characters -newline = "\n" -unicode_char = char:. &{ return char != "\n" } -unicode_letter = char:. &{ return char.match(/^\p{L}$/u) } -unicode_digit = char:. &{ return char.match(/^\p{N}$/u) } - -//* Letters and Digits -letter = unicode_letter / "_" -decimal_digit = [0-9] -binary_digit = [0-1] -octal_digit = [0-7] -hex_digit = [0-9a-fA-F] - - - - - -//* =============== Lexical Elements =============== -comment = "//" unicode_char* newline - / "/*" (!"*/" .)* "*/" - -//* Identifiers -identifier = iden:$(letter (letter / unicode_digit)*) &{ return IdentifierToken.isValidIdentifier(iden) } - { return new IdentifierToken(location(), iden) } - -//* Integer Literals -int_lit = number:binary_lit { return IntegerLiteralToken.fromSource(location(), number, 2) } / - number:octal_lit { return IntegerLiteralToken.fromSource(location(), number, 8) } / - number:decimal_lit { return IntegerLiteralToken.fromSource(location(), number, 10) } / - number:hex_lit { return IntegerLiteralToken.fromSource(location(), number, 16) } -binary_lit = "0" "b" "_"? $binary_digits -octal_lit = "0" [oO]? "_"? $octal_digits -decimal_lit = $([1-9] "_"? decimal_digits?) / - "0" -hex_lit = "0" [xX] "_"? $hex_digits - -binary_digits = binary_digit ("_"? binary_digit)* -octal_digits = octal_digit ("_"? octal_digit)* -decimal_digits = decimal_digit ("_"? decimal_digit)* -hex_digits = hex_digit ("_"? hex_digit)* - -//* Floating-point Literals -//! TODO (P5): Support hexadecimal floating points. -float_lit = number:$decimal_float_lit { return FloatLiteralToken.fromSource(location(), number); } - -decimal_float_lit = decimal_digits "." decimal_digits? decimal_exponent? / - decimal_digits decimal_exponent / - "." decimal_digits decimal_exponent? -decimal_exponent = [eE] ("+" / "-")? decimal_digits - -//! TODO (P5): Support imaginary literals. -//! TODO (P3): Support rune literals. - -//* String Literals -string_lit = raw_string_lit / interpreted_string_lit -raw_string_lit = "`" str:$[^`]* "`" { return StringLiteralToken.fromSourceRaw(location(), str) } -interpreted_string_lit = '"' str:$[^\n"]* '"' { return StringLiteralToken.fromSourceInterpreted(location(), str) } -//! TODO (P3): Interpreted string literals should interpret rune literals. - - - - - -//* =============== Types =============== -// Note: `TypeName TypeArgs?` is reduced to `TypeName` as generics are not supported. -Type = @TypeName / TypeLit / "(" _ Type _ ")" -//! TODO (P1): Support qualified identifiers for TypeName. -TypeName = iden:identifier &{ return PrimitiveTypeToken.isPrimitive(iden.identifier) } - { return new PrimitiveTypeToken(location(), iden.identifier) } - / @QualifiedIdent -TypeLit = ArrayType / StructType / PointerType / FunctionType / InterfaceType / SliceType / MapType / ChannelType - -//* Array Types -ArrayType = "[" _ length:ArrayLength _ "]" element:ElementType { return new ArrayTypeToken(location(), element, length) } -// Note: ArrayLength is actually an Expression in Golang specification, but we didn't want to implement -// expression evaluation INSIDE the compiler. Hence, we chose to only allow int_lit. -ArrayLength = int_lit -ElementType = Type - -//* Slice Types -SliceType = "[" "]" element:ElementType { return new SliceTypeToken(location(), element) } - -//* Struct Types -StructType = "struct" _ "{" _ (_ FieldDecl _ ";"?)* _ "}" -FieldDecl = (IdentifierList Type / EmbeddedField) Tag? -EmbeddedField = "*"? TypeName -Tag = string_lit - -//* Pointer Types -PointerType = "*" BaseType -BaseType = Type - -//* Function Types -FunctionType = "func" _ @Signature -Signature = params:Parameters _ result:Result? { return new FunctionTypeToken(location(), params, result) } -Result = @Parameters / type:Type { return [{ type }] } -// Parameters is an array of { identifier?: string, type: TypeToken }. -Parameters = "(" _ parameters:ParameterList? _ ","? _ ")" { return parameters ?? [] } -ParameterList = head:ParameterDecl tail:(_ "," _ @ParameterDecl)* { return [head, ...tail].flat()} -// Note: ParameterDecl does not support variadic parameters (i.e. no `xs ...int`). -ParameterDecl = identifiers:IdentifierList? _ type:Type - { return identifiers == null || identifiers.length === 0 - ? [{ identifier: null, type }] - : identifiers.map(iden => ({ identifier: iden.identifier, type })) } - / type:Type { return [{ identifier: null, type }] } - -//* Interface Types -InterfaceType = "interface" _ "{" _ (_ InterfaceElem _ ";"?)* _ "}" -InterfaceElem = MethodElem / TypeElem -MethodElem = MethodName _ Signature -MethodName = identifier -TypeElem = TypeTerm _ ("|" _ TypeTerm _)* -TypeTerm = Type / UnderlyingType -UnderlyingType = "~" Type - -//* Map Types -MapType = "map" _ "[" _ key:KeyType _ "]" _ element:ElementType { return new MapTypeToken(location(), key, element) } -KeyType = Type - -//* Channel Types -ChannelType = "chan" _ "<-" _ element:ElementType { return new ChannelTypeToken(location(), element, false, true) } - / "<-" _ "chan" _ element:ElementType { return new ChannelTypeToken(location(), element, true, false) } - / "chan" _ element:ElementType { return new ChannelTypeToken(location(), element, true, true) } - - - - - -//* =============== Blocks =============== -Block = "{" _ statements:StatementList _ "}" { return new BlockToken(location(), statements) } -StatementList = (_ @Statement _ ";"?)* - - - - - -//* =============== Declarations and Scope =============== -//! TODO (P5): Tokenize TypeDecl. -Declaration = ConstDecl / TypeDecl / VarDecl -//! TODO (P4): Tokenize MethodDecl. -TopLevelDecl = Declaration / FunctionDecl / MethodDecl - -//* Constant Declarations -//! TODO (P5): Multiple declarations (optionally semicolon-separated) is not supported. -ConstDecl = "const" _ @ConstSpec -ConstSpec = identifiers:IdentifierList _ rest:( - varType:Type? _ "=" _ expressions:ExpressionList - { return { varType, expressions } } - ) - { return new ConstantDeclarationToken(location(), identifiers, rest.expressions, rest.varType) } - -IdentifierList = head:identifier tail:(_ "," _ @identifier _)* { return [head, ...tail] } -ExpressionList = head:Expression tail:(_ "," _ @Expression _)* { return [head, ...tail] } - -//* Type Declarations -TypeDecl = "type" _ (TypeSpec / "(" _ (_ TypeSpec _ ";"?)* _ ")") -TypeSpec = AliasDecl / TypeDef -AliasDecl = identifier _ "=" _ Type -TypeDef = identifier _ TypeParameters? _ Type -TypeParameters = "[" _ TypeParamList _ ","? _ "]" -TypeParamList = TypeParamDecl (_ "," _ TypeParamDecl)* -TypeParamDecl = IdentifierList _ TypeConstraint -TypeConstraint = TypeElem - -//* Variable Declarations -//! TODO (P5): Multiple declarations (optionally semicolon-separated) is not supported. -VarDecl = "var" _ @VarSpec -VarSpec = identifiers:IdentifierList _ rest:( - varType:Type expressions:(_ "=" _ @ExpressionList)? - { return {varType, expressions} } - / - _ "=" _ expressions:ExpressionList - { return {varType: undefined, expressions} } - ) - { return new VariableDeclarationToken(location(), identifiers, rest.varType, rest.expressions) } - -//* Short Variable Declarations -ShortVarDecl = identifiers:IdentifierList _ ":=" _ expressions:ExpressionList - { return new ShortVariableDeclarationToken(location(), identifiers, expressions) } - -//* Function Declarations -// Note: TypeParameters? is omitted from FunctionDecl as we do not support generics. -FunctionDecl = "func" _ name:FunctionName _ signature:Signature _ body:Block? - { return new FunctionDeclarationToken(location(), name, new FunctionLiteralToken(location(), signature, body)) } -FunctionName = identifier -FunctionBody = Block - -//* Method Declarations -MethodDecl = "func" _ Receiver _ MethodName _ Signature _ FunctionBody? -Receiver = Parameters - - - - - -//* =============== Expressions =============== -//* Operands (Partial) -// OperandName [ TypeArgs ] is excluded as generics are not supported -Operand = Literal / OperandName / "(" _ @Expression _ ")" -Literal = BasicLit / CompositeLit / FunctionLit -//! TODO (P3): Add support for imaginary_lit. -//! TODO (P5): Add support for rune_lit. -BasicLit = float_lit / int_lit / string_lit -OperandName = identifier / QualifiedIdent - -//* Qualified Identifiers -QualifiedIdent = pkg:PackageName "." iden:identifier { return new QualifiedIdentifierToken(location(), pkg.identifier, iden.identifier) } - -//* Composite Literals -CompositeLit = type:ArrayType _ value:LiteralValue { return new ArrayLiteralToken(location(), type, value) } - / type:SliceType _ value:LiteralValue { return new SliceLiteralToken(location(), type, value) } -LiteralValue = "{" elements:(@ElementList _ ","? _)? "}" { return new LiteralValueToken(location(), elements ?? []) } -ElementList = head:KeyedElement rest:(_ "," _ @KeyedElement)* { return [head, ...rest] } -// Ironically, KeyedElement will not support having a key for our implementation. -KeyedElement = Element -Element = Expression / LiteralValue - -//* Function Literals -FunctionLit = "func" _ signature:Signature _ body:FunctionBody { return new FunctionLiteralToken(location(), signature, body) } - -//* Primary Expressions -//! TODO (P5): MethodExpr and Conversion are not supported. -PrimaryExpr = operand:(@BuiltinCall / @Operand) _ rest:PrimaryExprModifier* { return new PrimaryExpressionToken(location(), operand, rest) } -// This PrimaryExprTerm is added to fix left recursion. -PrimaryExprModifier = Selector / Index / Slice / TypeAssertion / Arguments -Selector = "." identifier:identifier { return new SelectorToken(location(), identifier.identifier) } -Index = "[" _ expr:Expression (_ ",")? _ "]" { return new IndexToken(location(), expr) } -// Note: Full slice expressions are not supported. -Slice = "[" _ from:Expression? _ ":" _ to:Expression? _ "]" { return new SliceToken(location(), from, to) } -TypeAssertion = "." _ "(" _ Type _ ")" -// Note: Variadic arguments are not supported. -// Note: Golang specs allow a Type to be the first argument, but it is only used for certain builtin functions. -// We split BuiltinCall out to a separate token to allow parsing types as first argument. -Arguments = "(" _ exprs:ExpressionList? _ ","? _ ")" { return new CallToken(location(), exprs) } - -//* Builtin Call (This is not in Golang specifications) -BuiltinCall = name:$[a-zA-Z]+ _ "(" _ type:Type _ args:("," _ @ExpressionList)? _ ")" - &{ return BuiltinCallToken.validNames.includes(name) && BuiltinCallToken.namesThatTakeType.includes(name) } - { return new BuiltinCallToken(location(), name, type, args ?? []) } - / name:$[a-zA-Z]+ _ "(" _ args:ExpressionList? _ ")" - &{ return BuiltinCallToken.validNames.includes(name) && !BuiltinCallToken.namesThatTakeType.includes(name) } - { return new BuiltinCallToken(location(), name, null, args ?? []) } - -//* Method Expressions -MethodExpr = ReceiverType "." MethodName -ReceiverType = Type - -//* Operators -Expression = rest:(ConjExpr _ disjunct_op _)* right:ConjExpr { return leftPrecTree(rest, right) } / - ConjExpr -ConjExpr = rest:(RelExpr _ conjunct_op _)* right:RelExpr { return leftPrecTree(rest, right) } / - RelExpr -RelExpr = rest:(AddExpr _ rel_op _)* right:AddExpr { return leftPrecTree(rest, right) } / - AddExpr -AddExpr = rest:(MulExpr _ add_op _)* right:MulExpr { return leftPrecTree(rest, right) } / - MulExpr -MulExpr = rest:(UnaryExpr _ mul_op _)* right:UnaryExpr { return leftPrecTree(rest, right) } / - UnaryExpr -UnaryExpr = PrimaryExpr / - op:unary_op _ expr:UnaryExpr { return op(expr) } - -// Operators are parsed into a function, that can be applied on operands to construct a token. -rel_op = "==" { return BinaryOperator.fromSource(location(), "equal") } / - "!=" { return BinaryOperator.fromSource(location(), "not_equal") } / - "<=" { return BinaryOperator.fromSource(location(), "less_or_equal") } / - ">=" { return BinaryOperator.fromSource(location(), "greater_or_equal") } / - "<" !"-" { return BinaryOperator.fromSource(location(), "less") } / - ">" { return BinaryOperator.fromSource(location(), "greater") } -add_op = "+" !"+" { return BinaryOperator.fromSource(location(), "sum") } / - "-" !"-" { return BinaryOperator.fromSource(location(), "difference") } / - "|" !"|" { return BinaryOperator.fromSource(location(), "bitwise_or") } / - "^" !"^" { return BinaryOperator.fromSource(location(), "bitwise_xor") } -mul_op = "*" !"*" { return BinaryOperator.fromSource(location(), "product") } / - "/" !"/" { return BinaryOperator.fromSource(location(), "quotient") } / - "%" { return BinaryOperator.fromSource(location(), "remainder") } / - "<<" { return BinaryOperator.fromSource(location(), "left_shift") } / - ">>" { return BinaryOperator.fromSource(location(), "right_shift") } / - "&" !"&" { return BinaryOperator.fromSource(location(), "bitwise_and") } / - "&^" { return BinaryOperator.fromSource(location(), "bit_clear") } -disjunct_op = "||" { return BinaryOperator.fromSource(location(), "conditional_or") } -conjunct_op = "&&" { return BinaryOperator.fromSource(location(), "conditional_and") } - -unary_op = "+" { return UnaryOperator.fromSource(location(), "plus") } / // Note: This operator is unnamed in Golang specs. - "-" { return UnaryOperator.fromSource(location(), "negation") } / - "!" { return UnaryOperator.fromSource(location(), "not") } / - "^" { return UnaryOperator.fromSource(location(), "bitwise_complement") } / - "*" { return UnaryOperator.fromSource(location(), "indirection") } / - "&" { return UnaryOperator.fromSource(location(), "address") } / - "<-" { return UnaryOperator.fromSource(location(), "receive") } - -//* Conversions -Conversion = Type _ "(" _ Expression _ ")" _ ","? _ ")" - - - - - -//* =============== Statements =============== -//* Statements -//! TODO (P5): LabeledStmt, GotoStmt are not tokenized as they're probably not so important. -//! Note that labels do not work for now. -Statement = Declaration / LabeledStmt / SimpleStmt / - GoStmt / ReturnStmt / BreakStmt / ContinueStmt / GotoStmt / - FallthroughStmt / Block / IfStmt / SwitchStmt / SelectStmt / ForStmt / - DeferStmt -// Note that EmptyStmt is removed from SimpleStmt to simplify parsing. -// Instead, users of Statement should allow it to be empty. -SimpleStmt = SendStmt / IncDecStmt / Assignment / ShortVarDecl / ExpressionStmt - -//* Labeled Statements -LabeledStmt = Label _ ":" _ Statement -Label = identifier - -//* Expression Statements -ExpressionStmt = Expression - -//* Send Statements -SendStmt = channel:Channel _ "<-" _ value:Expression { return new SendStatementToken(location(), channel, value) } -// For simplicity, we only allow channels to be identifiers instead of Expression -Channel = identifier - -//* IncDec Statements -IncDecStmt = expression:Expression _ op:("++" / "--") - { return new IncDecStatementToken(location(), expression, op) } - -//* Assignment Statements -Assignment = left:ExpressionList _ op:$assign_op _ right:ExpressionList - { return new AssignmentStatementToken(location(), left, op, right) } -assign_op = [\*\+]? "=" - -//* If Statements -IfStmt = "if" _ init:(@SimpleStmt _ ";")? _ pred:Expression _ cons:Block _ - alt:("else" _ @(IfStmt / Block))? - { return new IfStatementToken(location(), init, pred, cons, alt) } - -//* Switch Statements -SwitchStmt = ExprSwitchStmt /** / TypeSwitchStmt */ - -ExprSwitchStmt = "switch" _ init:(@SimpleStmt _ ";")? _ expr:Expression? _ "{" _ cases:(_ @ExprCaseClause _)* "}" - { return new SwitchStatementToken(location(), init, expr, cases ) } -ExprCaseClause = "case" _ "default" _ ":" _ statements:StatementList { return new SwitchCaseToken(location(), null, statements) } - / "case" _ exprs:ExpressionList _ ":" _ statements:StatementList { return new SwitchCaseToken(location(), exprs, statements) } - -// Note: TypeSwitchStmt will most likely not be supported. -// TypeSwitchStmt = "switch" _ (SimpleStmt _ ";")? _ TypeSwitchGuard _ "{" _ (_ TypeCaseClause _)* _ ")" -// TypeSwitchGuard = (identifier _ ":=")? _ PrimaryExpr _ "." _ "(" _ "type" _ ")" -// TypeCaseClause = TypeSwitchCase _ ":" _ StatementList -// TypeSwitchCase = "case" _ TypeList / "default" - -//* For Statements -//! TODO (P4): Support RangeClause in ForStmt. -ForStmt = "for" _ - clause:(ForClause / - cond:Condition { return {init: undefined, cond, post: undefined }} - )? - _ body:Block - { return new ForStatementToken(location(), clause?.init, clause?.cond, clause?.post, body)} -Condition = Expression - -ForClause = init:InitStmt? _ ";" _ cond:Condition? _ ";" _ post:PostStmt? - { return { init: init ?? undefined, cond: cond ?? undefined, post: post ?? undefined } } -InitStmt = SimpleStmt -PostStmt = SimpleStmt - -RangeClause = (ExpressionList _ "=" / IdentifierList _ ":=")? _ "range" _ Expression - -//* Go Statements -GoStmt = "go" _ expr:PrimaryExpr &{ return GoStatementToken.isValidGoroutine(expr) } - { return new GoStatementToken(location(), expr) } - -//* Select Statements -SelectStmt = "select" _ "{" _ clauses:(_ @CommClause _)* _ "}" { return new SelectStatementToken(location(), clauses ?? []) } -CommClause = predicate:CommCase _ ":" _ body:StatementList - { return new CommunicationClauseToken(location(), predicate, body) } -CommCase = "case" _ @(RecvStmt / SendStmt) / @"default" -// For simplicity, we disallow an ExpressionList with assignment here. -RecvStmt = identifiers:IdentifierList _ ":=" _ expr:RecvExpr - &{ return ReceiveStatementToken.isReceiveStatement(identifiers) } - { return new ReceiveStatementToken(location(), true, identifiers, expr) } - / identifiers:(@IdentifierList _ "=")? _ expr:RecvExpr - &{ return ReceiveStatementToken.isReceiveStatement(identifiers) } - { return new ReceiveStatementToken(location(), false, identifiers, expr) } -RecvExpr = expr:Expression &{ return expr instanceof UnaryOperator && expr.name === 'receive' } { return expr } - -//* Return Statements -ReturnStmt = "return" _ returns:ExpressionList? - { return new ReturnStatementToken(location(), returns) } - -//* Break Statements -//! TODO (P5): Labels are not tokenized. -BreakStmt = "break" _ Label? { return new BreakStatementToken(location()) } - -//* Continue Statements -//! TODO (P5): Labels are not tokenized. -ContinueStmt = "continue" _ Label? { return new ContinueStatementToken(location()) } - -//* Goto Statements -GotoStmt = "goto" _ Label - -//* Fallthrough Statements -FallthroughStmt = "fallthrough" { return new FallthroughStatementToken(location()) } - -//* Defer Statements -DeferStmt = "defer" _ expression:Expression { return new DeferStatementToken(location(), expression) } - - - - - -//* =============== Packages =============== -//* Source File -SourceFile = pkg:PackageClause _ ";"? _ imports:(_ @ImportDecl _ ";"? _)* declarations:(_ @TopLevelDecl _ ";"?)* - { return new SourceFileToken(location(), pkg, imports, declarations) } - -//* Package Clause -PackageClause = "package" _ @PackageName -PackageName = identifier - -//* Import Declarations -ImportDecl = "import" _ @(ImportSpec / "(" _ (_ ImportSpec _ ";"?)* _ ")") -ImportSpec = name:("." / PackageName)? _ path:ImportPath - { return new ImportToken(location(), path, name) } -ImportPath = string_lit \ No newline at end of file diff --git a/src/virtual-machine/parser/parser.ts b/src/virtual-machine/parser/parser.ts deleted file mode 100644 index f774d77..0000000 --- a/src/virtual-machine/parser/parser.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { parse } from './golang_parser.js' - -const parser = { - parse, -} - -export default parser diff --git a/src/virtual-machine/parser/tokens.ts b/src/virtual-machine/parser/tokens.ts deleted file mode 100644 index 4cb5695..0000000 --- a/src/virtual-machine/parser/tokens.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './tokens/base' -export * from './tokens/block' -export * from './tokens/declaration' -export * from './tokens/expressions' -export * from './tokens/identifier' -export * from './tokens/literals' -export * from './tokens/operator' -export * from './tokens/source_file' -export * from './tokens/statement' -export * from './tokens/type' diff --git a/src/virtual-machine/parser/tokens/base.ts b/src/virtual-machine/parser/tokens/base.ts deleted file mode 100644 index b164975..0000000 --- a/src/virtual-machine/parser/tokens/base.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CompileError, Compiler } from '../../compiler' -import { Instruction } from '../../compiler/instructions' -import { Type } from '../../compiler/typing' - -export type TokenLocation = { - start: { offset: number; line: number; column: number } - end: { offset: number; line: number; column: number } -} - -export abstract class Token { - constructor(public type: string, public sourceLocation: TokenLocation) {} - - abstract compileUnchecked(compiler: Compiler): Type - - pushInstruction(compiler: Compiler, ...instr: Instruction[]) { - compiler.instructions.push(...instr) - compiler.symbols.push(...Array(instr.length).fill(this.sourceLocation)) - } - - compile(compiler: Compiler): Type { - try { - return this.compileUnchecked(compiler) - } catch (err) { - if (err instanceof CompileError) throw err - - // Error originated from this token. - compiler.throwCompileError((err as Error).message, this.sourceLocation) - } - } -} diff --git a/src/virtual-machine/parser/tokens/block.ts b/src/virtual-machine/parser/tokens/block.ts deleted file mode 100644 index c213a6b..0000000 --- a/src/virtual-machine/parser/tokens/block.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Compiler } from '../../compiler' -import { - BlockInstruction, - ExitBlockInstruction, -} from '../../compiler/instructions' -import { NoType, ReturnType, Type } from '../../compiler/typing' - -import { Token, TokenLocation } from './base' -import { StatementToken } from './statement' - -export class BlockToken extends Token { - constructor( - sourceLocation: TokenLocation, - public statements: StatementToken[], - public name = 'BLOCK', - ) { - super('block', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - compiler.context.push_env() - const block_instr = new BlockInstruction(this.name) - this.pushInstruction(compiler, block_instr) - compiler.type_environment = compiler.type_environment.extend() - let hasReturn = false - for (const sub_token of this.statements) { - const statementType = sub_token.compile(compiler) - hasReturn ||= statementType instanceof ReturnType - } - const blockType = hasReturn - ? compiler.type_environment.expectedReturn - : new NoType() - - const vars = compiler.context.env.get_frame() - block_instr.set_frame( - vars.map((name) => compiler.type_environment.get(name)), - ) - block_instr.set_identifiers(vars) - compiler.type_environment = compiler.type_environment.pop() - compiler.context.pop_env() - - this.pushInstruction(compiler, new ExitBlockInstruction()) - - return blockType - } -} diff --git a/src/virtual-machine/parser/tokens/declaration.ts b/src/virtual-machine/parser/tokens/declaration.ts deleted file mode 100644 index cf1087e..0000000 --- a/src/virtual-machine/parser/tokens/declaration.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Compiler } from '../../compiler' -import { - LoadVariableInstruction, - StoreInstruction, -} from '../../compiler/instructions' -import { NoType, Type } from '../../compiler/typing' - -import { Token, TokenLocation } from './base' -import { ExpressionToken } from './expressions' -import { IdentifierToken } from './identifier' -import { FunctionLiteralToken } from './literals' -import { TypeToken } from './type' - -export type TopLevelDeclarationToken = - | DeclarationToken - | FunctionDeclarationToken - -export class FunctionDeclarationToken extends Token { - constructor( - sourceLocation: TokenLocation, - public name: IdentifierToken, - public func: FunctionLiteralToken, - ) { - super('function_declaration', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const [frame_idx, var_idx] = compiler.context.env.declare_var( - this.name.identifier, - ) - //! TODO (P5): There is a double compilation of func.signature, once here and - //! once in the func.compile() call. Not really an issue as compiling types has - //! no side effects, but would be nice to fix. - compiler.type_environment.addType( - this.name.identifier, - this.func.signature.compile(compiler), - ) - this.func.compile(compiler) - this.pushInstruction( - compiler, - new LoadVariableInstruction(frame_idx, var_idx, this.name.identifier), - ) - this.pushInstruction(compiler, new StoreInstruction()) - return new NoType() - } -} - -export abstract class DeclarationToken extends Token {} - -export class ShortVariableDeclarationToken extends DeclarationToken { - constructor( - sourceLocation: TokenLocation, - public identifiers: IdentifierToken[], - public expressions: ExpressionToken[], - ) { - super('short_variable_declaration', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const { identifiers, expressions } = this - for (let i = 0; i < identifiers.length; i++) { - const var_name = identifiers[i].identifier - const expr = expressions[i] - const [frame_idx, var_idx] = compiler.context.env.declare_var(var_name) - const expressionType = expr.compile(compiler) - compiler.type_environment.addType(var_name, expressionType) - this.pushInstruction( - compiler, - new LoadVariableInstruction(frame_idx, var_idx, var_name), - ) - this.pushInstruction(compiler, new StoreInstruction()) - } - return new NoType() - } -} - -export class VariableDeclarationToken extends DeclarationToken { - constructor( - sourceLocation: TokenLocation, - public identifiers: IdentifierToken[], - // Note: A variable declaration must have at least one of varType / expressions. - public varType?: TypeToken, - public expressions?: ExpressionToken[], - ) { - super('variable_declaration', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const { identifiers, varType, expressions } = this - if (varType === undefined && expressions === undefined) { - //! TODO (P5): Golang implements this as a syntax error. Unfortunately, our current parsing - //! is unable to detect this error. A correct parser should require one of them to be present. - throw Error( - 'Either variable type or assignment value(s) must be defined in variable declaration.', - ) - } - - // Add identifiers to environment. - for (const identifier of identifiers) { - compiler.context.env.declare_var(identifier.identifier) - } - - const expectedType = varType ? varType.compile(compiler) : undefined - - // Compile and add identifiers to type environment. - if (expressions) { - if (identifiers.length !== expressions.length) { - throw Error( - `Assignment mismatch: ${identifiers.length} variable(s) but ${expressions.length} value(s).`, - ) - } - for (let i = 0; i < identifiers.length; i++) { - const identifier = identifiers[i].identifier - const expression = expressions[i] - const [frame_idx, var_idx] = compiler.context.env.find_var(identifier) - const expressionType = expression.compile(compiler) - if (expectedType && !expectedType.assignableBy(expressionType)) { - throw Error( - `Cannot use ${expressionType} as ${expectedType} in variable declaration`, - ) - } - compiler.type_environment.addType(identifier, expressionType) - this.pushInstruction( - compiler, - new LoadVariableInstruction(frame_idx, var_idx, identifier), - ) - this.pushInstruction(compiler, new StoreInstruction()) - } - } else { - // Variables are uninitialized, but their type is given. - for (const identifier of identifiers) { - compiler.type_environment.addType( - identifier.identifier, - expectedType as Type, - ) - } - } - return new NoType() - } -} - -export class ConstantDeclarationToken extends DeclarationToken { - constructor( - sourceLocation: TokenLocation, - public identifiers: IdentifierToken[], - public expressions: ExpressionToken[], - public varType?: TypeToken, - ) { - super('const_declaration', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - /** - * TODO: Handle Const separately, several different methods - * 1. Runtime Const and const tag to variable to make it immutable - * 2. Compile Time Const: Replace each reference to variable with Expression Token - * 3. Compile Time Const: Evaluate Expression Token literal value and replace each reference (Golang only allow compile time const) - */ - const { identifiers, varType, expressions } = this - const expectedType = varType ? varType.compile(compiler) : undefined - for (let i = 0; i < identifiers.length; i++) { - const var_name = identifiers[i].identifier - const expr = expressions[i] - const [frame_idx, var_idx] = compiler.context.env.declare_var(var_name) - const expressionType = expr.compile(compiler) - if (expectedType && !expressionType.assignableBy(expectedType)) { - throw Error( - `Cannot use ${expressionType} as ${expectedType} in constant declaration`, - ) - } - compiler.type_environment.addType(var_name, expressionType) - this.pushInstruction( - compiler, - new LoadVariableInstruction(frame_idx, var_idx, var_name), - ) - this.pushInstruction(compiler, new StoreInstruction()) - } - return new NoType() - } -} diff --git a/src/virtual-machine/parser/tokens/expressions.ts b/src/virtual-machine/parser/tokens/expressions.ts deleted file mode 100644 index ae2336b..0000000 --- a/src/virtual-machine/parser/tokens/expressions.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { Compiler } from '../../compiler' -import { - BuiltinCapInstruction, - BuiltinLenInstruction, - Instruction, - LoadArrayElementInstruction, - LoadChannelInstruction, - LoadConstantInstruction, - LoadSliceElementInstruction, - SelectorOperationInstruction, - SliceOperationInstruction, -} from '../../compiler/instructions' -import { CallInstruction } from '../../compiler/instructions/funcs' -import { - ArrayType, - BoolType, - ChannelType, - FunctionType, - Int64Type, - NoType, - SliceType, - StringType, - Type, - TypeUtility, -} from '../../compiler/typing' - -import { Token, TokenLocation } from './base' -import { IdentifierToken } from './identifier' -import { LiteralToken } from './literals' -import { BinaryOperator, UnaryOperator } from './operator' -import { TypeToken } from './type' - -export type ExpressionToken = - | LiteralToken - | UnaryOperator - | BinaryOperator - | PrimaryExpressionToken - | BuiltinCallToken - | EmptyExpressionToken - -export type OperandToken = IdentifierToken | ExpressionToken - -export class EmptyExpressionToken extends Token { - constructor(sourceLocation: TokenLocation, public argType: Type) { - super('empty_expression', sourceLocation) - } - - override compileUnchecked(_compiler: Compiler): Type { - // Does nothing - Intended - return this.argType - } -} -export class PrimaryExpressionToken extends Token { - constructor( - sourceLocation: TokenLocation, - public operand: OperandToken, - /** The remaining modifier that is applied to the current operand. E.g. selector / index etc. */ - public rest: PrimaryExpressionModifierToken[] | null, - ) { - super('primary_expression', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - // TODO: Figure what this does for non-trivial ops like array access and selector - let operandType = this.operand.compile(compiler) - for (const modifier of this.rest ?? []) { - operandType = modifier.compile(compiler, operandType) - } - return operandType - } -} - -// Note: The reason this class DOES NOT extend from Token, is because each modifier -// requires type information about the previous operand in the chain in order to compile. -// Hence, its compilation method must take in an extra argument. Idk if this is the correct way -// to fix, but it doesn't make sense to force them to follow the structure of Token. -export abstract class PrimaryExpressionModifierToken { - constructor(public type: string, public sourceLocation: TokenLocation) {} - abstract compile(compiler: Compiler, operandType: Type): Type - - pushInstruction(compiler: Compiler, ...instr: Instruction[]) { - compiler.instructions.push(...instr) - compiler.symbols.push(...Array(instr.length).fill(this.sourceLocation)) - } -} - -export class SelectorToken extends PrimaryExpressionModifierToken { - constructor(sourceLocation: TokenLocation, public identifier: string) { - super('selector', sourceLocation) - } - - override compile(compiler: Compiler, operandType: Type): Type { - const resultType = operandType.select(this.identifier) - this.pushInstruction( - compiler, - new LoadConstantInstruction(this.identifier, new StringType()), - new SelectorOperationInstruction(), - ) - return resultType - } -} - -export class IndexToken extends PrimaryExpressionModifierToken { - constructor( - sourceLocation: TokenLocation, - public expression: ExpressionToken, - ) { - super('index', sourceLocation) - } - - override compile(compiler: Compiler, operandType: Type): Type { - if (operandType instanceof ArrayType) { - this.compileIndex(compiler) - this.pushInstruction(compiler, new LoadArrayElementInstruction()) - return operandType.element - } else if (operandType instanceof SliceType) { - this.compileIndex(compiler) - this.pushInstruction(compiler, new LoadSliceElementInstruction()) - return operandType.element - } else { - throw Error( - `Invalid operation: Cannot index a variable of type ${operandType}`, - ) - } - } - - private compileIndex(compiler: Compiler) { - const indexType = this.expression.compile(compiler) - if (!(indexType instanceof Int64Type)) { - throw new Error( - `Invalid argument: Index has type ${indexType} but must be an integer`, - ) - } - } -} - -export class SliceToken extends PrimaryExpressionModifierToken { - constructor( - sourceLocation: TokenLocation, - public from: ExpressionToken | null, - public to: ExpressionToken | null, - ) { - super('slice', sourceLocation) - } - - override compile(compiler: Compiler, operandType: Type): Type { - if (operandType instanceof ArrayType || operandType instanceof SliceType) { - this.compileIndex(compiler, this.from) - this.compileIndex(compiler, this.to) - this.pushInstruction(compiler, new SliceOperationInstruction()) - return new SliceType(operandType.element) - } - throw new Error(`Invalid operation: Cannot slice ${operandType}`) - } - - private compileIndex(compiler: Compiler, index: ExpressionToken | null) { - if (index) index.compile(compiler) - else { - // Use a non integer type to represent the default value for the index. - this.pushInstruction( - compiler, - new LoadConstantInstruction(false, new BoolType()), - ) - } - } -} - -export class CallToken extends PrimaryExpressionModifierToken { - expressions: ExpressionToken[] - - constructor( - sourceLocation: TokenLocation, - expressions: ExpressionToken[] | null, - ) { - super('call', sourceLocation) - this.expressions = expressions ?? [] - } - - override compile(compiler: Compiler, operandType: Type): Type { - if (!(operandType instanceof FunctionType)) { - throw Error( - `Invalid operation: cannot call non-function (of type ${operandType})`, - ) - } - - const argumentTypes = this.expressions.map((e) => e.compile(compiler)) - this.pushInstruction(compiler, new CallInstruction(this.expressions.length)) - - // We only implement variadic functions that accept any number of any type of arguments, - // so variadic functions do not require type checking. - if (!operandType.variadic) { - if (argumentTypes.length < operandType.parameters.length) { - throw Error( - `Not enough arguments in function call\n` + - `have (${TypeUtility.arrayToString(argumentTypes)})\n` + - `want (${TypeUtility.arrayToString(operandType.parameters)})`, - ) - } - if (argumentTypes.length > operandType.parameters.length) { - throw Error( - `Too many arguments in function call\n` + - `have (${TypeUtility.arrayToString(argumentTypes)})\n` + - `want (${TypeUtility.arrayToString(operandType.parameters)})`, - ) - } - - for (let i = 0; i < argumentTypes.length; i++) { - if (argumentTypes[i].assignableBy(operandType.parameters[i].type)) - continue - throw Error( - `Cannot use ${argumentTypes[i]} as ${operandType.parameters[i]} in argument to function call`, - ) - } - } - - if (operandType.results.isVoid()) { - return new NoType() - } - if (operandType.results.types.length === 1) { - // A return type with a single value can be unwrapped. - return operandType.results.types[0] - } - return operandType.results - } -} - -type BuiltinFunctionName = (typeof BuiltinCallToken.validNames)[number] -// The following builtin functions are omitted: new, panic, recover. -// This does not extend from PrimaryExpression because its parsing is completely separate: -// Certain builtin functions take in a type as the first argument (as opposed to a value). -export class BuiltinCallToken extends Token { - static validNames = [ - 'append', - 'clear', - 'close', - 'delete', - 'len', - 'cap', - 'make', - 'min', - 'max', - ] as const - - static namesThatTakeType = ['make'] as const - - constructor( - sourceLocation: TokenLocation, - public name: BuiltinFunctionName, - /** The first argument if it is a type. */ - public firstTypeArg: TypeToken | null, - public args: ExpressionToken[], - ) { - super('builtin', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - if (this.name === 'make') return this.compileMake(compiler) - else if (this.name === 'len') return this.compileLen(compiler) - else if (this.name === 'cap') return this.compileCap(compiler) - else { - throw new Error(`Builtin function ${this.name} is not yet implemented.`) - } - } - - private compileCap(compiler: Compiler): Type { - if (this.args.length !== 1) { - this.throwArgumentLengthError('cap', 1, this.args.length) - } - const argType = this.args[0].compile(compiler) - if (argType instanceof ArrayType || argType instanceof SliceType) { - this.pushInstruction(compiler, new BuiltinCapInstruction()) - } else { - this.throwArgumentTypeError('cap', argType) - } - return new Int64Type() - } - - private compileLen(compiler: Compiler): Type { - if (this.args.length !== 1) { - this.throwArgumentLengthError('len', 1, this.args.length) - } - const argType = this.args[0].compile(compiler) - if (argType instanceof ArrayType || argType instanceof SliceType) { - this.pushInstruction(compiler, new BuiltinLenInstruction()) - } else { - this.throwArgumentTypeError('len', argType) - } - return new Int64Type() - } - - private compileMake(compiler: Compiler): Type { - const typeArg = (this.firstTypeArg as TypeToken).compile(compiler) - if (!(typeArg instanceof SliceType || typeArg instanceof ChannelType)) { - throw new Error( - `Invalid argument: cannot make ${typeArg}; type must be slice, map, or channel`, - ) - } - if (typeArg instanceof ChannelType) { - if (this.args.length === 0) - this.pushInstruction( - compiler, - new LoadConstantInstruction(0, new Int64Type()), - ) - else { - const buffer_sz = this.args[0].compile(compiler) - if (!(buffer_sz instanceof Int64Type)) { - throw new Error(`Non-integer condition in channel buffer size`) - } - } - } - // !TODO Make for slice - this.pushInstruction(compiler, new LoadChannelInstruction()) - return typeArg - } - - private throwArgumentLengthError( - name: string, - expectedNum: number, - actualNum: number, - ) { - const errorMessage = - expectedNum < actualNum - ? `Invalid operation: too many arguments for ${name} (expected ${expectedNum}, found ${actualNum})` - : `Invalid operation: not enough arguments for ${name} (expected ${expectedNum}, found ${actualNum})` - throw new Error(errorMessage) - } - - private throwArgumentTypeError(name: string, type: Type) { - const errorMessage = `Invalid argument: (${type}) for ${name}` - throw new Error(errorMessage) - } -} diff --git a/src/virtual-machine/parser/tokens/identifier.ts b/src/virtual-machine/parser/tokens/identifier.ts deleted file mode 100644 index 2b8d441..0000000 --- a/src/virtual-machine/parser/tokens/identifier.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Compiler } from '../../compiler' -import { LoadVariableInstruction } from '../../compiler/instructions' -import { PackageType, Type } from '../../compiler/typing' - -import { Token, TokenLocation } from './base' - -export class IdentifierToken extends Token { - constructor(sourceLocation: TokenLocation, public identifier: string) { - super('identifier', sourceLocation) - } - - static isValidIdentifier(identifier: string): boolean { - const reservedKeywords = [ - 'break', - 'case', - 'chan', - 'const', - 'continue', - 'default', - 'defer', - 'else', - 'fallthrough', - 'for', - 'func', - 'go', - 'goto', - 'if', - 'import', - 'interface', - 'map', - 'package', - 'range', - 'return', - 'select', - 'struct', - 'switch', - 'type', - 'var', - ] - return !reservedKeywords.includes(identifier) - } - - override compileUnchecked(compiler: Compiler): Type { - const [frame_idx, var_idx] = compiler.context.env.find_var(this.identifier) - this.pushInstruction( - compiler, - new LoadVariableInstruction(frame_idx, var_idx, this.identifier), - ) - return compiler.type_environment.get(this.identifier) - } -} - -/** - * Note that qualified identifiers in our implementation are only used for types, - * as our parser cannot distinguish `package.identifier` from `variable.field`, - * hence all values (not types) of the form `x.y` are handled by selector operator instead. - */ -export class QualifiedIdentifierToken extends Token { - constructor( - sourceLocation: TokenLocation, - public pkg: string, - public identifier: string, - ) { - super('qualified_identifier', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const pkg = compiler.type_environment.get(this.pkg) - if (!(pkg instanceof PackageType)) { - throw new Error(`${this} is not a type`) - } - return pkg.select(this.identifier) - } - - override toString(): string { - return `${this.pkg}.${this.identifier}` - } -} diff --git a/src/virtual-machine/parser/tokens/literals.ts b/src/virtual-machine/parser/tokens/literals.ts deleted file mode 100644 index b93727c..0000000 --- a/src/virtual-machine/parser/tokens/literals.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { Compiler } from '../../compiler' -import { - FuncBlockInstruction, - JumpInstruction, - LoadArrayInstruction, - LoadConstantInstruction, - LoadDefaultInstruction, - LoadFuncInstruction, - LoadSliceInstruction, - ReturnInstruction, -} from '../../compiler/instructions' -import { - ArrayType, - Float64Type, - FunctionType, - Int64Type, - ReturnType, - SliceType, - StringType, - Type, -} from '../../compiler/typing' - -import { Token, TokenLocation } from './base' -import { BlockToken } from './block' -import { ExpressionToken } from './expressions' -import { ArrayTypeToken, FunctionTypeToken, SliceTypeToken } from './type' - -export abstract class LiteralToken extends Token { - constructor(sourceLocation: TokenLocation, public value: number | string) { - super('literal', sourceLocation) - } - - static is(token: Token): token is LiteralToken { - return token.type === 'literal' - } -} - -export class IntegerLiteralToken extends LiteralToken { - /** Tokenize an integer literal in the given base. */ - static fromSource(sourceLocation: TokenLocation, str: string, base: number) { - // Golang numbers can be underscore delimited. - const value = parseInt(str.replace('_', ''), base) - return new IntegerLiteralToken(sourceLocation, value) - } - - getValue(): number { - return this.value as number - } - - override compileUnchecked(compiler: Compiler): Type { - this.pushInstruction( - compiler, - new LoadConstantInstruction(this.value, new Int64Type()), - ) - return new Int64Type() - } -} - -export class FloatLiteralToken extends LiteralToken { - /** Tokenize a float literal. */ - static fromSource(sourceLocation: TokenLocation, str: string) { - const value = parseFloat(str) - return new FloatLiteralToken(sourceLocation, value) - } - - getValue(): number { - return this.value as number - } - - override compileUnchecked(compiler: Compiler): Type { - this.pushInstruction( - compiler, - new LoadConstantInstruction(this.value, new Float64Type()), - ) - return new Float64Type() - } -} - -export class StringLiteralToken extends LiteralToken { - /** Tokenize a raw string literal. */ - static fromSourceRaw(sourceLocation: TokenLocation, str: string) { - // Carriage returns are discarded from raw strings. - str = str.replaceAll('\r', '') - return new StringLiteralToken(sourceLocation, str) - } - - /** Tokenize an interpreted string literal. */ - static fromSourceInterpreted(sourceLocation: TokenLocation, str: string) { - return new StringLiteralToken(sourceLocation, str) - } - - getValue(): string { - return this.value as string - } - - override compileUnchecked(compiler: Compiler): Type { - this.pushInstruction( - compiler, - new LoadConstantInstruction(this.value, new StringType()), - ) - return new StringType() - } -} - -export class FunctionLiteralToken extends Token { - constructor( - sourceLocation: TokenLocation, - public signature: FunctionTypeToken, - public body: BlockToken, - ) { - super('function_literal', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - compiler.context.push_env() - const jump_instr = new JumpInstruction() - this.pushInstruction(compiler, jump_instr) - const func_start = compiler.instructions.length - const block_instr = new FuncBlockInstruction( - this.signature.parameters.length, - ) - this.pushInstruction(compiler, block_instr) - - const signatureType = this.signature.compile(compiler) as FunctionType - compiler.type_environment = compiler.type_environment.extend() - compiler.type_environment.updateReturnType(signatureType.results) - - let cnt = 0 - for (const param of this.signature.parameters) { - const name = param.identifier || (cnt++).toString() - compiler.context.env.declare_var(name) - compiler.type_environment.addType(name, param.type.compile(compiler)) - } - - let hasReturn = false - for (const sub_token of this.body.statements) { - const statementType = sub_token.compile(compiler) - hasReturn ||= statementType instanceof ReturnType - } - const vars = compiler.context.env.get_frame() - block_instr.set_frame( - vars.map((name) => compiler.type_environment.get(name)), - ) - block_instr.set_identifiers(vars) - compiler.type_environment = compiler.type_environment.pop() - compiler.context.pop_env() - - this.pushInstruction(compiler, new ReturnInstruction()) - jump_instr.set_addr(compiler.instructions.length) - this.pushInstruction(compiler, new LoadFuncInstruction(func_start)) - - if (!hasReturn && signatureType.results.types.length > 0) { - throw new Error(`Missing return.`) - } - return signatureType - } -} - -export class LiteralValueToken extends Token { - constructor( - sourceLocation: TokenLocation, - public elements: (LiteralValueToken | ExpressionToken)[], - ) { - super('literal_value', sourceLocation) - } - - override compileUnchecked(_compiler: Compiler): Type { - throw new Error( - 'Do not use LiteralValueToken.compile, instead use LiteralValueToken.compileWithType', - ) - } - - //! TODO (P5): It is extremely weird to define a separate compilation method, - //! but we need the extra type information here. How to fix this? - compileWithType(compiler: Compiler, type: Type) { - if (type instanceof ArrayType) { - if (this.elements.length > type.length) { - throw new Error( - `Array literal has ${this.elements.length} elements but only expected ${type.length}, in type ${type}.`, - ) - } - - for (const element of this.elements) { - this.compileElement(compiler, type.element, element, 'array literal') - } - for (let i = 0; i < type.length - this.elements.length; i++) { - // Ran out of literal values, use the default values. - this.pushInstruction(compiler, new LoadDefaultInstruction(type.element)) - } - - this.pushInstruction(compiler, new LoadArrayInstruction(type.length)) - } else if (type instanceof SliceType) { - for (const element of this.elements) { - this.compileElement(compiler, type.element, element, 'slice literal') - } - const sliceLength = this.elements.length - this.pushInstruction( - compiler, - new LoadArrayInstruction(sliceLength), - new LoadConstantInstruction(0, new Int64Type()), - new LoadConstantInstruction(sliceLength, new Int64Type()), - new LoadSliceInstruction(), - ) - } else { - throw new Error('Parser Bug: Type of literal value is not supported.') - } - } - - /** Compile an element and check that it matches the given type. - * typeName is the name of the structure (e.g. array literal) for the error message. */ - private compileElement( - compiler: Compiler, - type: Type, - element: LiteralValueToken | ExpressionToken, - typeName: string, - ) { - if (element instanceof LiteralValueToken) { - element.compileWithType(compiler, type) - } else { - const actualType = element.compile(compiler) - if (!type.equals(actualType)) { - throw new Error( - `Cannot use ${actualType} as ${type} value in ${typeName}.`, - ) - } - } - } -} - -export class ArrayLiteralToken extends Token { - constructor( - sourceLocation: TokenLocation, - public arrayType: ArrayTypeToken, - public body: LiteralValueToken, - ) { - super('array_literal', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const type = this.arrayType.compile(compiler) - this.body.compileWithType(compiler, type) - return type - } -} - -export class SliceLiteralToken extends Token { - constructor( - sourceLocation: TokenLocation, - public sliceType: SliceTypeToken, - public body: LiteralValueToken, - ) { - super('slice_literal', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const type = this.sliceType.compile(compiler) - this.body.compileWithType(compiler, type) - return type - } -} diff --git a/src/virtual-machine/parser/tokens/operator.ts b/src/virtual-machine/parser/tokens/operator.ts deleted file mode 100644 index 46547d8..0000000 --- a/src/virtual-machine/parser/tokens/operator.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Compiler } from '../../compiler' -import { - BinaryInstruction, - LoadChannelReqInstruction, - LoadDefaultInstruction, - TryChannelReqInstruction, - UnaryInstruction, -} from '../../compiler/instructions' -import { BoolType, ChannelType, Type } from '../../compiler/typing' - -import { Token, TokenLocation } from './base' - -abstract class Operator extends Token { - name: string - children: Token[] - - constructor( - type: string, - sourceLocation: TokenLocation, - name: string, - children: Token[], - ) { - super(type, sourceLocation) - this.name = name - this.children = children - } -} - -export class UnaryOperator extends Operator { - constructor(sourceLocation: TokenLocation, name: string, child: Token) { - super('unary_operator', sourceLocation, name, [child]) - } - - /** Returns a function that can be applied on its child token to create a UnaryOperator. */ - static fromSource(sourceLocation: TokenLocation, name: string) { - return (child: Token) => { - return new UnaryOperator(sourceLocation, name, child) - } - } - - override compileUnchecked(compiler: Compiler): Type { - const expressionType = this.children[0].compile(compiler) - if (this.name === 'receive') { - if (!(expressionType instanceof ChannelType)) - throw Error('Receive Expression not chan') - const recvType = expressionType.element - this.pushInstruction(compiler, new LoadDefaultInstruction(recvType)) - this.pushInstruction( - compiler, - new LoadChannelReqInstruction(true, compiler.instructions.length + 2), - ) - this.pushInstruction(compiler, new TryChannelReqInstruction()) - return recvType - } else { - this.pushInstruction(compiler, new UnaryInstruction(this.name)) - return expressionType - } - } -} - -export class BinaryOperator extends Operator { - constructor( - sourceLocation: TokenLocation, - name: string, - left: Token, - right: Token, - ) { - super('binary_operator', sourceLocation, name, [left, right]) - } - - /** Returns a function that can be applied on its children tokens to create a BinaryOperator. */ - static fromSource(sourceLocation: TokenLocation, name: string) { - return (left: Token, right: Token) => { - return new BinaryOperator(sourceLocation, name, left, right) - } - } - - override compileUnchecked(compiler: Compiler): Type { - const leftType = this.children[0].compile(compiler) - const rightType = this.children[1].compile(compiler) - if (!leftType.equals(rightType)) { - throw Error( - `Invalid operation (mismatched types ${leftType} and ${rightType})`, - ) - } - this.pushInstruction(compiler, new BinaryInstruction(this.name)) - const logical_operators = [ - 'equal', - 'not_equal', - 'less', - 'less_or_equal', - 'greater', - 'greater_or_equal', - 'conditional_or', - 'conditional_and', - ] - return logical_operators.includes(this.name) ? new BoolType() : leftType - } -} diff --git a/src/virtual-machine/parser/tokens/source_file.ts b/src/virtual-machine/parser/tokens/source_file.ts deleted file mode 100644 index 9ed29b3..0000000 --- a/src/virtual-machine/parser/tokens/source_file.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Compiler } from '../../compiler' -import { - BlockInstruction, - CallInstruction, - LoadConstantInstruction, - LoadVariableInstruction, - StoreInstruction, -} from '../../compiler/instructions' -import { BoolType, NoType, Type } from '../../compiler/typing' -import { builtinPackages } from '../../compiler/typing/packages' - -import { Token, TokenLocation } from './base' -import { TopLevelDeclarationToken } from './declaration' -import { StringLiteralToken } from './literals' - -export class SourceFileToken extends Token { - constructor( - sourceLocation: TokenLocation, - public pkg: string, - public imports: ImportToken[] | null, - public declarations: TopLevelDeclarationToken[] | null, - ) { - super('source_file', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - // Setup. - const global_block = new BlockInstruction('GLOBAL') - this.pushInstruction(compiler, global_block) - compiler.context.push_env() - compiler.type_environment = compiler.type_environment.extend() - - // Compile imports. - for (const imp of this.imports ?? []) imp.compile(compiler) - - // Declare builtin constants for `true` and `false`. - this.predeclareConstants(compiler) - - // Compile top level declarations. - for (const declaration of this.declarations || []) { - declaration.compile(compiler) - } - - // Call main function. - const [frame_idx, var_idx] = compiler.context.env.find_var('main') - this.pushInstruction( - compiler, - new LoadVariableInstruction(frame_idx, var_idx, 'main'), - ) - this.pushInstruction(compiler, new CallInstruction(0)) - const vars = compiler.context.env.get_frame() - global_block.set_frame( - vars.map((name) => compiler.type_environment.get(name)), - ) - global_block.set_identifiers(vars) - return new NoType() - } - - private predeclareConstants(compiler: Compiler): void { - const constants = [ - { - name: 'true', - loadInstruction: new LoadConstantInstruction(true, new BoolType()), - type: new BoolType(), - }, - { - name: 'false', - loadInstruction: new LoadConstantInstruction(false, new BoolType()), - type: new BoolType(), - }, - ] - for (const constant of constants) { - const { name, loadInstruction, type } = constant - const [frame_idx, var_idx] = compiler.context.env.declare_var(name) - this.pushInstruction( - compiler, - loadInstruction, - new LoadVariableInstruction(frame_idx, var_idx, name), - new StoreInstruction(), - ) - compiler.type_environment.addType(name, type) - } - } -} - -export class ImportToken extends Token { - constructor( - sourceLocation: TokenLocation, - public importPath: StringLiteralToken, - public pkgName: string | null, - ) { - super('import', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const pkg = this.importPath.getValue() - if (pkg in builtinPackages) { - builtinPackages[pkg as keyof typeof builtinPackages](compiler) - } - return new NoType() - } -} diff --git a/src/virtual-machine/parser/tokens/statement.ts b/src/virtual-machine/parser/tokens/statement.ts deleted file mode 100644 index c19b0d7..0000000 --- a/src/virtual-machine/parser/tokens/statement.ts +++ /dev/null @@ -1,615 +0,0 @@ -import { Compiler } from '../../compiler' -import { - BinaryInstruction, - BlockInstruction, - CallInstruction, - DeferredCallInstruction, - DoneInstruction, - ExitBlockInstruction, - ForkInstruction, - LoadChannelReqInstruction, - LoadConstantInstruction, - PopInstruction, - ReturnInstruction, - SelectInstruction, - StoreInstruction, - TryChannelReqInstruction, -} from '../../compiler/instructions' -import { - ExitLoopInstruction, - JumpIfFalseInstruction, - JumpInstruction, -} from '../../compiler/instructions/control' -import { - BoolType, - ChannelType, - Int64Type, - NoType, - ReturnType, - Type, -} from '../../compiler/typing' - -import { Token, TokenLocation } from './base' -import { BlockToken } from './block' -import { DeclarationToken, ShortVariableDeclarationToken } from './declaration' -import { - CallToken, - EmptyExpressionToken, - ExpressionToken, - PrimaryExpressionToken, -} from './expressions' -import { IdentifierToken } from './identifier' -import { UnaryOperator } from './operator' - -export type StatementToken = - | DeclarationToken - | SimpleStatementToken - | GoStatementToken - | ReturnStatementToken - | BreakStatementToken - | ContinueStatementToken - | FallthroughStatementToken - | BlockToken - | IfStatementToken - | SwitchStatementToken - | SelectStatementToken - | ForStatementToken - | DeferStatementToken - -export type SimpleStatementToken = - | ExpressionStatementToken - | SendStatementToken - | IncDecStatementToken - | AssignmentStatementToken - | ShortVariableDeclarationToken - -export class AssignmentStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public left: ExpressionToken[], - public operation: '=' | '+=' | '*=', - public right: ExpressionToken[], - ) { - super('assignment', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - // TODO: Custom Instructions to avoid recalculation? - for (let i = 0; i < this.left.length; i++) { - const left = this.left[i] - const right = this.right[i] - let assignType: Type - if (this.operation === '+=') { - const leftType = left.compile(compiler) - const rightType = right.compile(compiler) - if (!leftType.equals(rightType)) { - throw Error( - `Invalid operation (mismatched types ${leftType} and ${rightType})`, - ) - } - assignType = leftType - this.pushInstruction(compiler, new BinaryInstruction('sum')) - } else if (this.operation === '*=') { - const leftType = left.compile(compiler) - const rightType = right.compile(compiler) - if (!leftType.equals(rightType)) { - throw Error( - `Invalid operation (mismatched types ${leftType} and ${rightType})`, - ) - } - assignType = leftType - this.pushInstruction(compiler, new BinaryInstruction('product')) - } else if (this.operation === '=') { - assignType = right.compile(compiler) - } else { - throw Error('Unimplemented') - } - const varType = left.compile(compiler) - if (!varType.assignableBy(assignType)) { - throw Error(`Cannot use ${assignType} as ${varType} in assignment`) - } - this.pushInstruction(compiler, new StoreInstruction()) - } - return new NoType() - } -} - -export class IncDecStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public expression: ExpressionToken, - public operation: '++' | '--', - ) { - super('inc_dec', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - // TODO: Custom Instructions to avoid recalculation? - this.expression.compile(compiler) - this.pushInstruction( - compiler, - new LoadConstantInstruction(1, new Int64Type()), - ) - if (this.operation === '++') { - this.pushInstruction(compiler, new BinaryInstruction('sum')) - } else if (this.operation === '--') { - this.pushInstruction(compiler, new BinaryInstruction('difference')) - } - this.expression.compile(compiler) - this.pushInstruction(compiler, new StoreInstruction()) - return new NoType() - } -} - -export class ReturnStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public returns?: ExpressionToken[], - ) { - super('return', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const returnType = new ReturnType( - (this.returns ?? []).map((expr) => expr.compile(compiler)), - ) - - if ( - returnType.types.length > - compiler.type_environment.expectedReturn.types.length - ) { - throw new Error( - `Too many return values\nhave ${returnType}\nwant ${compiler.type_environment.expectedReturn}`, - ) - } - - if (!returnType.equals(compiler.type_environment.expectedReturn)) { - throw new Error( - `Cannot use ${returnType} as ${compiler.type_environment.expectedReturn} value in return statement.`, - ) - } - - this.pushInstruction(compiler, new ReturnInstruction()) - return returnType - } -} - -export class BreakStatementToken extends Token { - constructor(sourceLocation: TokenLocation) { - super('break', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const jumpInstr = new ExitLoopInstruction() - compiler.context.add_break(jumpInstr) - this.pushInstruction(compiler, jumpInstr) - return new NoType() - } -} - -export class ContinueStatementToken extends Token { - constructor(sourceLocation: TokenLocation) { - super('continue', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const jumpInstr = new ExitLoopInstruction() - compiler.context.add_continue(jumpInstr) - this.pushInstruction(compiler, jumpInstr) - return new NoType() - } -} - -export class FallthroughStatementToken extends Token { - constructor(sourceLocation: TokenLocation) { - super('fallthrough', sourceLocation) - } - - override compileUnchecked(_compiler: Compiler): Type { - // TODO: Implement - return new NoType() - } -} - -export class IfStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - /** Executed before the predicate (e.g. if x := 0; x < 1 {} ) */ - public initialization: SimpleStatementToken | null, - public predicate: ExpressionToken, - public consequent: BlockToken, - public alternative: IfStatementToken | BlockToken | null, - ) { - super('if', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - compiler.context.push_env() - const block_instr = new BlockInstruction('IF BLOCK') - this.pushInstruction(compiler, block_instr) - compiler.type_environment = compiler.type_environment.extend() - // Initialisation - if (this.initialization) this.initialization.compile(compiler) - - // Eval Predicate - const predicateType = this.predicate.compile(compiler) - if (!(predicateType instanceof BoolType)) { - throw new Error(`Non-boolean condition in if statement.`) - } - // If False jump to alternative / end - const jumpToAlternative = new JumpIfFalseInstruction() - - // Consequent Block - this.pushInstruction(compiler, jumpToAlternative) - this.consequent.name = 'IF BODY' - const consequentType = this.consequent.compile(compiler) - const jumpToEnd = new JumpInstruction() - this.pushInstruction(compiler, jumpToEnd) - - // Alternative Block - jumpToAlternative.set_addr(compiler.instructions.length) - // AlternativeType defaults to the expected return type, so that if there is no alternative, - // we simply treat the consequent type as the type of the whole if statement. - let alternativeType: Type = compiler.type_environment.expectedReturn - if (this.alternative) { - if (this.alternative instanceof BlockInstruction) - this.alternative.name = 'IF BODY' - alternativeType = this.alternative.compile(compiler) - } - jumpToEnd.set_addr(compiler.instructions.length) - - this.pushInstruction(compiler, new ExitBlockInstruction()) - const vars = compiler.context.env.get_frame() - block_instr.set_frame( - vars.map((name) => compiler.type_environment.get(name)), - ) - block_instr.set_identifiers(vars) - compiler.type_environment = compiler.type_environment.pop() - compiler.context.pop_env() - - if ( - consequentType instanceof ReturnType && - alternativeType instanceof ReturnType - ) { - return consequentType - } - return new NoType() - } -} - -export class SwitchStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public init: SimpleStatementToken | null, - public expressions: ExpressionToken | null, - public cases: SwitchCaseToken[], - ) { - super('switch', sourceLocation) - } - - override compileUnchecked(_compiler: Compiler): Type { - //! TODO: Implement. - return new NoType() - } -} - -export class SwitchCaseToken extends Token { - constructor( - sourceLocation: TokenLocation, - public expressions: ExpressionToken[] | null, - public statements: StatementToken[], - ) { - super('case', sourceLocation) - } - - override compileUnchecked(_compiler: Compiler): Type { - //! TODO: Implement. - return new NoType() - } -} - -export class ForStatementToken extends Token { - // There are 4 types of for loops: - // 1. For statement that iterates the body repeatedly. - // 2. For statements with a single condition. - // 3. For statements with a for clause (init, condition, post). - // 4. For statements with a range clause. - //! Note that range clauses are not supported for now. They will likely be a seperate class. - constructor( - sourceLocation: TokenLocation, - public initialization: SimpleStatementToken | null, - public condition: ExpressionToken | null, - public post: ExpressionToken | null, - public body: BlockToken, - ) { - super('for', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - compiler.context.push_env() - compiler.type_environment = compiler.type_environment.extend() - const block_instr = new BlockInstruction('FOR INIT', true) - this.pushInstruction(compiler, block_instr) - compiler.context.push_loop() - - // Initialisation - if (this.initialization) this.initialization.compile(compiler) - const start_addr = compiler.instructions.length - - // Predicate - const predicate_false = new JumpIfFalseInstruction() - if (this.condition) { - const predicateType = this.condition.compile(compiler) - if (!(predicateType instanceof BoolType)) { - throw new Error(`Non-boolean condition in for statement condition.`) - } - this.pushInstruction(compiler, predicate_false) - } - this.body.name = 'FOR BODY' - const bodyType = this.body.compile(compiler) - - const pre_post_addr = compiler.instructions.length - if (this.post) this.post.compile(compiler) - this.pushInstruction(compiler, new JumpInstruction(start_addr)) - const post_post_addr = compiler.instructions.length - predicate_false.set_addr(post_post_addr) - - compiler.context.pop_loop(pre_post_addr, post_post_addr) - this.pushInstruction(compiler, new ExitBlockInstruction()) - const vars = compiler.context.env.get_frame() - block_instr.set_frame( - vars.map((name) => compiler.type_environment.get(name)), - ) - block_instr.set_identifiers(vars) - compiler.type_environment = compiler.type_environment.pop() - compiler.context.pop_env() - return bodyType - } -} - -export class DeferStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public expression: ExpressionToken, - ) { - super('defer', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - if (!this.isFunctionCall()) { - throw new Error('Expression in defer must be function call.') - } - - this.expression.compile(compiler) - const call = compiler.instructions[compiler.instructions.length - 1] - compiler.instructions[compiler.instructions.length - 1] = - DeferredCallInstruction.fromCallInstruction(call as CallInstruction) - - return new NoType() - } - - private isFunctionCall(): boolean { - if (!(this.expression instanceof PrimaryExpressionToken)) return false - const modifiers = this.expression.rest ?? [] - if (modifiers.length === 0) return false - if (!(modifiers[modifiers.length - 1] instanceof CallToken)) return false - return true - } -} - -export class GoStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public call: PrimaryExpressionToken, - ) { - super('go', sourceLocation) - } - - /** Used in the parser to only parse function calls */ - static isValidGoroutine(expression: PrimaryExpressionToken) { - return ( - expression.rest && - expression.rest.length > 0 && - expression.rest[expression.rest.length - 1] instanceof CallToken - ) - } - - override compileUnchecked(compiler: Compiler): Type { - const fork_instr = new ForkInstruction() - this.pushInstruction(compiler, fork_instr) - this.call.compile(compiler) - this.pushInstruction(compiler, new DoneInstruction()) - fork_instr.set_addr(compiler.instructions.length) - return new NoType() - } -} - -/** Sends a `value` into `channel`. */ -export class SendStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public channel: IdentifierToken, - public value: ExpressionToken, - ) { - super('send', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - const chanType = this.channel.compile(compiler) - if (!(chanType instanceof ChannelType)) - throw Error('Not instance of channel type') - const argType = chanType.element - const exprType = this.value.compile(compiler) - if (!argType.assignableBy(exprType)) { - throw Error(`Cannot use ${exprType} as ${argType} in assignment`) - } - if (!argType.equals(exprType)) throw Error('') - this.pushInstruction( - compiler, - new LoadChannelReqInstruction(false, compiler.instructions.length + 2), - ) - this.pushInstruction(compiler, new TryChannelReqInstruction()) - return new NoType() - } -} - -/** Receive and assign the results to one or two variables. Note that RecvStmt is NOT a SimpleStmt. */ -export class ReceiveStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - /** Whether this is a shorthand variable declaration (else it is an assignment). */ - public declaration: boolean, - public identifiers: IdentifierToken[] | null, - /** expression is guarenteed to be a receive operator. */ - public expression: UnaryOperator, - ) { - super('receive', sourceLocation) - } - - /** Used in the parser to only parse valid receive statements. */ - static isReceiveStatement(identifiers: IdentifierToken[] | null) { - return ( - identifiers === null || - (identifiers.length > 0 && identifiers.length <= 2) - ) - } - - override compileUnchecked(compiler: Compiler): Type { - const chanType = this.expression.compile(compiler) - return chanType - } -} - -export class SelectStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public clauses: CommunicationClauseToken[], - ) { - super('select', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - let default_case = false - const end_jumps = [] - for (const clause of this.clauses) { - if (clause.predicate === 'default') { - if (default_case) throw Error('Multiple Default cases!') - default_case = true - continue - } - clause.compile(compiler) - const jump_instr = new JumpInstruction() - this.pushInstruction(compiler, jump_instr) - end_jumps.push(jump_instr) - } - if (default_case) { - for (const clause of this.clauses) { - if (clause.predicate === 'default') { - clause.compile(compiler) - const jump_instr = new JumpInstruction() - this.pushInstruction(compiler, jump_instr) - end_jumps.push(jump_instr) - break - } - } - } - this.pushInstruction( - compiler, - new SelectInstruction( - this.clauses.length - (default_case ? 1 : 0), - default_case, - ), - ) - for (const jump_instr of end_jumps) - jump_instr.set_addr(compiler.instructions.length) - - return new NoType() - } -} -export class CommunicationClauseToken extends Token { - constructor( - sourceLocation: TokenLocation, - public predicate: 'default' | SendStatementToken | ReceiveStatementToken, - public body: StatementToken[], - ) { - super('communication_clause', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - if (!(this.predicate instanceof ReceiveStatementToken)) { - if (this.predicate === 'default') { - const load_instr = new LoadConstantInstruction( - compiler.instructions.length + 2, - new Int64Type(), - ) - this.pushInstruction(compiler, load_instr) - } else { - // Is send statement - this.predicate.compile(compiler) - compiler.instructions.pop() // Removing blocking op - compiler.symbols.pop() - } - const jump_instr = new JumpInstruction() - this.pushInstruction(compiler, jump_instr) - new BlockToken(this.sourceLocation, this.body, 'CASE BLOCK').compile( - compiler, - ) - jump_instr.set_addr(compiler.instructions.length + 1) - } else { - // This is recv statement - const chanType = this.predicate.expression.compile(compiler) - compiler.instructions.pop() - compiler.symbols.pop() - const jump_instr = new JumpInstruction() - this.pushInstruction(compiler, jump_instr) - if (this.predicate.identifiers) { - if (this.predicate.declaration) { - this.body.unshift( - new ShortVariableDeclarationToken( - this.sourceLocation, - this.predicate.identifiers, - [new EmptyExpressionToken(this.sourceLocation, chanType)], - ), - ) - } else { - // !TODO: Hacky see if better way to implement this - this.body.unshift( - new AssignmentStatementToken( - this.sourceLocation, - [ - new PrimaryExpressionToken( - this.sourceLocation, - this.predicate.identifiers[0], - null, - ), - ], - '=', - [new EmptyExpressionToken(this.sourceLocation, chanType)], - ), - ) - } - } else this.pushInstruction(compiler, new PopInstruction()) - new BlockToken(this.sourceLocation, this.body, 'CASE BLOCK').compile( - compiler, - ) - jump_instr.set_addr(compiler.instructions.length + 1) - } - return new NoType() - } -} - -/** An ExpressionStatement differs from an Expression: it should not leave a value on the OS. */ -export class ExpressionStatementToken extends Token { - constructor( - sourceLocation: TokenLocation, - public expression: ExpressionToken, - ) { - super('expression_statement', sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - this.expression.compile(compiler) - this.pushInstruction(compiler, new PopInstruction()) - return new NoType() - } -} diff --git a/src/virtual-machine/parser/tokens/type.ts b/src/virtual-machine/parser/tokens/type.ts deleted file mode 100644 index 63cf4a2..0000000 --- a/src/virtual-machine/parser/tokens/type.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Compiler } from '../../compiler' -import { - ArrayType, - BoolType, - ChannelType, - Float64Type, - FunctionType, - Int64Type, - NoType, - ParameterType, - ReturnType, - SliceType, - StringType, - Type, -} from '../../compiler/typing' - -import { Token, TokenLocation } from './base' -import { IntegerLiteralToken } from './literals' - -export abstract class TypeToken extends Token { - constructor(sourceLocation: TokenLocation) { - super('type', sourceLocation) - } -} - -/** - * Note that PrimitiveTypeToken is not a native Golang construct. - * It is used to encompass Boolean, Numeric, and String types. - */ -export class PrimitiveTypeToken extends TypeToken { - static primitiveTypes = ['bool', 'int64', 'float64', 'int', 'string'] as const - - static isPrimitive = ( - name: unknown, - ): name is (typeof PrimitiveTypeToken.primitiveTypes)[number] => { - return PrimitiveTypeToken.primitiveTypes.includes( - // This type cast is necessary as .includes only accepts types equal to an array element. - name as (typeof PrimitiveTypeToken.primitiveTypes)[number], - ) - } - - static isPrimitiveToken = (token: unknown): token is PrimitiveTypeToken => { - return ( - token instanceof PrimitiveTypeToken && - PrimitiveTypeToken.isPrimitive(token.name) - ) - } - - name: (typeof PrimitiveTypeToken.primitiveTypes)[number] - - constructor(sourceLocation: TokenLocation, name: string) { - super(sourceLocation) - if (!PrimitiveTypeToken.isPrimitive(name)) { - throw Error(`Invalid primitive type: ${name}`) - } - this.name = name - } - - override compileUnchecked(_compiler: Compiler): Type { - if (this.name === 'bool') return new BoolType() - else if (this.name === 'float64') return new Float64Type() - else if (this.name === 'int') return new Int64Type() - else if (this.name === 'int64') return new Int64Type() - else if (this.name === 'string') return new StringType() - else return new NoType() - } -} - -export class ArrayTypeToken extends TypeToken { - constructor( - sourceLocation: TokenLocation, - public element: TypeToken, - public length: IntegerLiteralToken, - ) { - super(sourceLocation) - } - - override compileUnchecked(compiler: Compiler): ArrayType { - return new ArrayType(this.element.compile(compiler), this.length.getValue()) - } -} - -export class SliceTypeToken extends TypeToken { - constructor(sourceLocation: TokenLocation, public element: TypeToken) { - super(sourceLocation) - } - - override compileUnchecked(compiler: Compiler): SliceType { - return new SliceType(this.element.compile(compiler)) - } -} - -type ParameterDecl = { - identifier: string | null - type: TypeToken -} -export class FunctionTypeToken extends TypeToken { - public parameters: ParameterDecl[] - public results: ParameterDecl[] - - constructor( - sourceLocation: TokenLocation, - parameters: ParameterDecl[], - results: ParameterDecl[] | null, - ) { - super(sourceLocation) - this.parameters = parameters - this.results = results ?? [] - } - - override compileUnchecked(compiler: Compiler): FunctionType { - const parameterTypes = this.parameters.map( - (p) => new ParameterType(p.identifier, p.type.compile(compiler)), - ) - const resultTypes = new ReturnType( - this.results.map((r) => r.type.compile(compiler)), - ) - return new FunctionType(parameterTypes, resultTypes) - } -} - -export class MapTypeToken extends TypeToken { - constructor( - sourceLocation: TokenLocation, - public key: TypeToken, - public element: TypeToken, - ) { - super(sourceLocation) - } - - override compileUnchecked(_compiler: Compiler): Type { - //! TODO: Implement. - return new NoType() - } -} - -export class ChannelTypeToken extends TypeToken { - constructor( - sourceLocation: TokenLocation, - public element: TypeToken, - public readable: boolean, - public writable: boolean, - ) { - super(sourceLocation) - } - - override compileUnchecked(compiler: Compiler): Type { - return new ChannelType( - this.element.compile(compiler), - this.readable, - this.writable, - ) - } -} diff --git a/tests/array.test.ts b/tests/array.test.ts deleted file mode 100644 index 86bf0e2..0000000 --- a/tests/array.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Array Type Checking', () => { - test('Array literal with more elements than in the type should fail.', () => { - expect( - mainRunner('var a [3]int = [3]int{1, 2, 3, 4}').error?.message, - ).toEqual( - 'Array literal has 4 elements but only expected 3, in type [3]int64.', - ) - }) - - test('Array literal must have the same type as the declared type.', () => { - expect( - mainRunner('var a [3]int = [3]int{1, "wrong type", 3}').error?.message, - ).toEqual('Cannot use string as int64 value in array literal.') - }) - - test('Array indexing with non integer type should fail.', () => { - expect( - mainRunner('var a [3]int = [3]int{1, 2, 3}; fmt.Println(a[1.2])').error - ?.message, - ).toEqual('Invalid argument: Index has type float64 but must be an integer') - }) -}) - -describe('Array Execution', () => { - test('Array indexing with valid index works.', () => { - expect( - mainRunner( - 'var a [3]string = [3]string{"a", "b", "c"}\n fmt.Println(a[2])', - ).output, - ).toEqual('c\n') - }) - - test('Array indexing with negative index fails.', () => { - expect( - mainRunner( - 'var a [3]string = [3]string{"a", "b", "c"}\n fmt.Println(a[-1])', - ).error?.message, - ).toEqual('Execution Error: Index out of range [-1] with length 3') - }) - - test('Array indexing with out of range index fails.', () => { - expect( - mainRunner( - 'var a [3]string = [3]string{"a", "b", "c"}\n fmt.Println(a[3])', - ).error?.message, - ).toEqual('Execution Error: Index out of range [3] with length 3') - }) - - test('Nested arrays work.', () => { - expect( - mainRunner( - 'a := [3][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; fmt.Println(a[1][2])', - ).output, - ).toEqual('6\n') - }) -}) diff --git a/tests/builtin.test.ts b/tests/builtin.test.ts deleted file mode 100644 index 8bd8e2e..0000000 --- a/tests/builtin.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Builtins Type Checking', () => { - test('Assignment of make with incompatible type should fail', () => { - expect( - mainRunner('var a chan int = make(chan string)').error?.message, - ).toEqual('Cannot use chan string as chan int64 in variable declaration') - }) -}) diff --git a/tests/channel.test.ts b/tests/channel.test.ts deleted file mode 100644 index 1e65508..0000000 --- a/tests/channel.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Channel Tests', () => { - test('Assign int to channel should fail', () => { - expect(mainRunner('var a <-chan int = 1').error?.message).toEqual( - 'Cannot use int64 as <-chan int64 in variable declaration', - ) - }) - - test('Channels Basic Test', () => { - expect( - mainRunner(` - c1 := make(chan int) - go func(){ - for i:=0; i < 100; i++ { - - } - fmt.Println("hi") - c1 <- 1 - }() - a := <- c1 - fmt.Println(a)`).output, - ).toEqual('hi\n1\n') - }) - - test('Channels Operator Test', () => { - expect( - mainRunner(` - c1 := make(chan int) - go func() { - c1<- 5 - }() - fmt.Println(4 + <- c1)`).output, - ).toEqual('9\n') - }) - - test('Channels Select Case Test', () => { - const strs = mainRunner(` - c1 := make(chan int) - c2 := make(chan string) - c3 := make(chan string) - go func() { - for { - select { - case <-c2: - c3 <- "stop" - break - default: - c1 <- 1 - } - } - }() - - go func() { - for { - select { - case <- c1: - fmt.Println("recv!") - case <-c3: - fmt.Println("stopped") - break - } - } - }() - for i:=0; i < 5; i++{} - fmt.Println("done") - c2 <- "stop" - for i:=0; i < 100; i++{}`) - .output?.trim() - .split('\n') - let done = false - let stopped = false - if (!strs) throw Error('No output') - for (const str of strs) { - if (str === 'done') { - if (done) throw Error('Multiple Dones!') - done = true - } - if (str === 'stopped') { - if (!done) throw Error('Stopped before main thread done!') - if (stopped) throw Error('Multiple Stopped!') - stopped = true - } - } - if (strs[strs.length - 1] !== 'stopped') - throw Error('Final string not stopped') - }) - - test('Channels Select Case Test 2', () => { - const strs = mainRunner(` - c1 := make(chan int) - n := 25 - - go func() { - for i := 0; i < n; i++ { - select { - case c1 <- 1: - fmt.Println("Write 1 1") - case <-c1: - fmt.Println("Read 1 2") - } - } - }() - - go func() { - for i := 0; i < n; i++ { - select { - case c1 <- 2: - fmt.Println("Write 2 2") - case <-c1: - fmt.Println("Read 2 1") - } - } - }() - for i:=0; i < 100; i++{}`) - .output?.trim() - .split('\n') - const arr1: number[] = [], - arr2: number[] = [] - expect(strs?.length).toEqual(50) - for (const str of strs || []) { - if (str.startsWith('Write 1') || str.startsWith('Read 1')) { - arr1.push(parseInt(str[str.length - 1])) - } else if (str.startsWith('Write 2') || str.startsWith('Read 2')) { - arr2.push(parseInt(str[str.length - 1])) - } else throw Error('Invalid String') - } - expect(arr1.length).toEqual(25) - expect(arr2.length).toEqual(25) - for (let i = 0; i < 25; i++) { - expect(arr1[i]).toEqual(arr2[i]) - } - }) - test('Channels Buffer Test', () => { - expect( - mainRunner(` - c1 := make(chan int, 3) - for i:= 0; i < 3; i++ { - c1 <- 1 - } - go func(){ - c1<- 1 - fmt.Println("done2") - }() - for i:=0;i < 100;i++ { - } - fmt.Println("done1") - <-c1 - for i:=0; i < 100; i++{}`).output, - ).toEqual('done1\ndone2\n') - }) -}) diff --git a/tests/comment.test.ts b/tests/comment.test.ts deleted file mode 100644 index 4f4fdd4..0000000 --- a/tests/comment.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { runCode } from '../src/virtual-machine' - -describe('Comments Tests', () => { - test('Comments are correctly ignored', () => { - const code = ` - package main - // comments in global - import "fmt" - /* - multiline - comments in global - */ - - func main() { - a := 1 + 2 // comment at end of line - /* multiline - comment */ - /**/ - // - fmt.Println(/* comment in middle of code */a) - } - ` - expect(runCode(code, 2048).output).toEqual('3\n') - }) -}) diff --git a/tests/concurrency.test.ts b/tests/concurrency.test.ts deleted file mode 100644 index be5fef6..0000000 --- a/tests/concurrency.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Concurrency Check', () => { - test('Basic Check', () => { - expect( - mainRunner(` - a := 0 - for i := 0; i < 5; i++ { - go func(){ - a+=1 - }() - } - a+=1 - for j := 0; j < 100 ; j++ { - } - fmt.Println(a)`).output, - ).toEqual('6\n') - }) - test('Race Cond', () => { - expect( - parseInt( - mainRunner(` - a := 0 - for i := 0; i < 5; i++ { - go func(){ - for j := 0; j < 100 ; j++ { - a += 1 - } - }() - } - for j := 0; j < 1000 ; j++ { - } - fmt.Println(a)`).output || '100', - ), - ).toBeLessThan(500) - }) -}) diff --git a/tests/control.test.ts b/tests/control.test.ts deleted file mode 100644 index be3c3e0..0000000 --- a/tests/control.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Variable Declaration Tests', () => { - test('If statement', () => { - expect( - mainRunner( - 'i:= 4\ - a:= 1337\ - if i:= 3; i == 4 {\ - a += i\ - } else {\ - a += -i\ - }\ - fmt.Println(a)', - ).output, - ).toEqual('1334\n') - }) - test('For loop', () => { - expect( - mainRunner( - 'a := 0;\ - for i:= 0; i < 5; i++ {\ - a += i\ - i := 3\ - a += i\ - }\ - fmt.Println(a)', - ).output, - ).toEqual('25\n') - }) - test('For loop continue', () => { - expect( - mainRunner( - 'a := 0;\ - for i:= 0; i < 5; i++ {\ - if (i == 3) {\ - continue\ - }\ - a += i\ - i := 3\ - a += i\ - }\ - fmt.Println(a)', - ).output, - ).toEqual('19\n') - }) - test('For loop break', () => { - expect( - mainRunner( - 'a := 0;\ - for i:= 0; i < 5; i++ {\ - if (i == 3) {\ - break\ - }\ - a += i\ - i := 3\ - a += i\ - }\ - fmt.Println(a)', - ).output, - ).toEqual('12\n') - }) -}) diff --git a/tests/declaration.test.ts b/tests/declaration.test.ts deleted file mode 100644 index b1e1d89..0000000 --- a/tests/declaration.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Variable Declaration Tests', () => { - test('Const Variables', () => { - expect( - mainRunner( - 'var a int = 3;\ - const b int = 5;\ - const c int = b;\ - fmt.Println(a+b+c)', - ).output, - ).toEqual('13\n') - }) - - test('String Variables', () => { - expect( - mainRunner( - 'a := "hi";\ - b := "hi2";\ - fmt.Println(a + b)', - ).output, - ).toEqual('hihi2\n') - }) - - test('Boolean constants true and false are predeclared', () => { - const code = ` - if false { - fmt.Println("false") - } - if true { - fmt.Println("true") - } - ` - expect(mainRunner(code).output).toEqual('true\n') - }) - - test('Boolean constants true and false can be shadowed by local declaration', () => { - const code = ` - true := false - false := true - if false { - fmt.Println("false") - } - if true { - fmt.Println("true") - } - ` - expect(mainRunner(code).output).toEqual('') - }) -}) diff --git a/tests/defer.test.ts b/tests/defer.test.ts deleted file mode 100644 index 046a655..0000000 --- a/tests/defer.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { runCode } from '../src/virtual-machine' - -import { mainRunner } from './utility' - -describe('Defer Type Checking', () => { - test('Defer on non call should fail.', () => { - const code = ` - defer "hello" - ` - expect(mainRunner(code).error?.message).toEqual( - 'Expression in defer must be function call.', - ) - }) -}) - -describe('Defer Execution', () => { - test('Defer runs in order', () => { - const code = ` - defer func(){ fmt.Println("!!!") }() - defer func(){ fmt.Println("world") }() - fmt.Println("hello") - ` - expect(mainRunner(code).output).toEqual('hello\nworld\n!!!\n') - }) - - test('Defer with wait groups work', () => { - const code = ` - package main - import "fmt" - import "sync" - func main() { - count := 0 - var wg sync.WaitGroup - for i := 0; i < 1000; i++ { - wg.Add(1) - go func() { - defer wg.Done() - count++ - }() - } - wg.Wait() - fmt.Println(count) - } - ` - expect(runCode(code, 2048).output).toEqual('1000\n') - }) -}) diff --git a/tests/environment.test.ts b/tests/environment.test.ts deleted file mode 100644 index ff6cbbd..0000000 --- a/tests/environment.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Basic Environment Tests', () => { - test('Number Variables', () => { - expect( - mainRunner( - 'var a int = 3;\ - b:= a + 3;\ - c := a + b;\ - c *= a;\ - fmt.Println(a + b + c)', - ).output, - ).toEqual('36\n') - }) - test('Number Variables Scoping', () => { - expect( - mainRunner( - ' var a int = 3;\ - {\ - var a int = 1;\ - a = 2;\ - };\ - fmt.Println(a)', - ).output, - ).toEqual('3\n') - }) -}) diff --git a/tests/expression.test.ts b/tests/expression.test.ts deleted file mode 100644 index 679cb98..0000000 --- a/tests/expression.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Basic Expression Tests', () => { - test('Basic Arithmetic 1', () => { - expect(mainRunner('fmt.Println(5 * -1 + 3 * 4 / 2 + 3)').output).toEqual( - '4\n', - ) - }) - test('Basic Arithmetic 2', () => { - expect(mainRunner('fmt.Println((4+3)*5%(5+3)+2)').output).toEqual('5\n') - }) - test('Boolean Expression', () => { - expect( - mainRunner('fmt.Println((2+1 < 3) || (7 == 9%5 + 15/5))').output, - ).toEqual('true\n') - }) -}) diff --git a/tests/fmt.test.ts b/tests/fmt.test.ts deleted file mode 100644 index 912dd72..0000000 --- a/tests/fmt.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('fmt Type Checking', () => { - test('Selector on fmt should fail unless it is fmt.Println.', () => { - const code = ` - fmt.nonexistent("hi") - ` - expect(mainRunner(code).error?.message).toEqual( - 'undefined: fmt.nonexistent', - ) - }) -}) - -describe('fmt Execution', () => { - test('fmt.Println works', () => { - const code = ` - fmt.Println("Hello", "world", true, false) - fmt.Println(1, 2, 3, 4) - ` - expect(mainRunner(code).output).toEqual('Hello world true false\n1 2 3 4\n') - }) -}) diff --git a/tests/function.test.ts b/tests/function.test.ts deleted file mode 100644 index 4e90cc4..0000000 --- a/tests/function.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { runCode } from '../src/virtual-machine' - -import { mainRunner } from './utility' - -describe('Function Type Checking', () => { - test('Function assignment', () => { - expect( - mainRunner('var a func(int, int) = func(int, int, int) {}').error - ?.message, - ).toEqual( - 'Cannot use func(int64, int64, int64) () as func(int64, int64) () in variable declaration', - ) - }) - - test('Function call - too many arguments', () => { - expect( - mainRunner('f := func(int, int) {}; f(1, 2, 3)').error?.message, - ).toEqual( - 'Too many arguments in function call\n' + - 'have (int64, int64, int64)\n' + - 'want (int64, int64)', - ) - }) - - test('Function call - too few arguments', () => { - expect(mainRunner('f := func(int, int) {}; f(1)').error?.message).toEqual( - 'Not enough arguments in function call\n' + - 'have (int64)\n' + - 'want (int64, int64)', - ) - }) - - test('Function call - incorrect argument type', () => { - expect( - mainRunner('f := func(int, int) {}; f(1, "a")').error?.message, - ).toEqual('Cannot use string as int64 in argument to function call') - }) - - test('Function missing return', () => { - expect(mainRunner('f := func(x int) int { x += 1}').error?.message).toEqual( - 'Missing return.', - ) - }) - - test('Function with if statement missing return in one branch', () => { - expect( - mainRunner( - 'f := func(x int) int { if x == 1 { x += 1 } else { return 1 } }', - ).error?.message, - ).toEqual('Missing return.') - }) - - test('Function with wrong return type', () => { - expect( - mainRunner( - 'f := func(x int) int { if x == 1 { return "hi" } else { return 1 } }', - ).error?.message, - ).toEqual('Cannot use (string) as (int64) value in return statement.') - }) - - test('Function with too many return values', () => { - expect( - mainRunner( - 'f := func(x int) { if x == 1 { return 1 } else { return 1 } }', - ).error?.message, - ).toEqual('Too many return values\nhave (int64)\nwant ()') - }) -}) - -describe('Function Execution tests', () => { - test('Function Literals', () => { - expect( - mainRunner( - 'f := func(x int, y int) int{\ - return x + y\ - }\ - fmt.Println(1 + f(1, 2))', - ).output, - ).toEqual('4\n') - }) - - test('Function Declaration', () => { - expect( - runCode( - `package main - import "fmt" - - var a int = 1 - - func f(x, y int) int { - return x + y + a - } - - func main() { - f := func(x, y int) int { - return x + y + 100 - } - fmt.Println(f(1, 2)) - }`, - 2048, - ).output, - ).toEqual('103\n') - }) - - test('Function assignment in loop', () => { - expect( - runCode( - `package main - import "fmt" - func main() { - f := func(x, y int) int { - return x + y - } - for i := 0; i < 5; i++ { - f = func(x, y int) int { - return x + y + i - } - } - fmt.Println(f(1, 2)) - }`, - 2048, - ).output, - ).toEqual('8\n') - }) - - test('Function assignment in loop and if', () => { - expect( - runCode( - `package main - import "fmt" - func main() { - f := func(x, y int) int { - return x + y - } - for i := 0; i < 100; i++ { - if i < 50 { - f = func(x, y int) int { - return x + y + i - } - } - } - fmt.Println(f(1, 2)) - }`, - 2048, - ).output, - ).toEqual('103\n') - }) - - test('Recursive function', () => { - expect( - runCode( - `package main - - import "fmt" - - func f(x int) int { - if x == 0 { - return 0 - } - return f(x - 1) + 1 - } - - func main() { - fmt.Println(f(10)) - }`, - 2048, - ).output, - ).toEqual('10\n') - }) - - test('Calling a function twice.', () => { - expect( - mainRunner('f := func(){ fmt.Println(1) }; f(); f()').output, - ).toEqual('1\n1\n') - }) - - test('Closures', () => { - expect( - runCode( - `package main - import "fmt" - - func getAreaFunc() func(int, int) int { - a := 0 - k := func(x, y int) int { - a += 1 - return x*y + a - } - a += 1 - return k - } - - func main() { - f := getAreaFunc() - f2 := getAreaFunc() - fmt.Println(f(3, 2)) - fmt.Println(f(1, 1)) - fmt.Println(f(1, 1)) - fmt.Println(f2(1, 1)) - fmt.Println(f2(2, 3)) - fmt.Println(f2(1, 1)) - } - `, - 2048, - ).output, - ).toEqual('8\n4\n5\n3\n9\n5\n') - }) -}) diff --git a/tests/heap.test.ts b/tests/heap.test.ts deleted file mode 100644 index 1b8ef03..0000000 --- a/tests/heap.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { Heap } from '../src/virtual-machine/heap' -import { ContextNode } from '../src/virtual-machine/heap/types/context' -import { - EnvironmentNode, - FrameNode, -} from '../src/virtual-machine/heap/types/environment' - -describe('Heap Tests', () => { - test('Get Set Bits', () => { - const heap = new Heap(320) - heap.memory.set_bits(8796013022207, 121, 45, 2) - // heap.memory.print() - expect(heap.memory.get_bits(121, 45, 2)).toEqual(8796013022207) - }) - test('Get Set Bits 2', () => { - const heap = new Heap(320) - heap.memory.set_bits(-1, 121, 45, 2) - expect(heap.memory.get_bits(121, 45, 2)).toEqual(35184372088831) // 2**45 - 1 - }) - test('Get Set Bits 3', () => { - const heap = new Heap(100) - heap.memory.set_bits(1, 1, 29, 6) - heap.memory.set_bits(2, 2, 29, 3) - expect(heap.memory.get_bits(1, 29, 6)).toEqual(1) - }) - test('Get Set Bits 4', () => { - const heap = new Heap(56) - heap.memory.set_bits(2, 3, 5, 1) - heap.memory.set_bits(3, 3, 29, 6) - expect(heap.memory.get_bits(3, 5, 1)).toEqual(2) - }) - test('Mark And Sweep', () => { - const heap = new Heap(56) - expect(() => FrameNode.create(0, heap)).toThrow(Error) - }) - test('Mark And Sweep 2', () => { - const heap = new Heap(62) - const context = new ContextNode(heap, heap.contexts.peek()) - const base_frame = FrameNode.create(0, heap) - const base_env = EnvironmentNode.create(base_frame.addr, [], false, heap) - context.set_E(base_env.addr) - heap.allocate(2) - heap.allocate(2) - heap.allocate(2) - expect(() => heap.allocate(4)).toThrow(Error) - }) -}) diff --git a/tests/slice.test.ts b/tests/slice.test.ts deleted file mode 100644 index 3742f36..0000000 --- a/tests/slice.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Slice Type Checking', () => { - test('Slice literal must have the same type as the declared type.', () => { - expect( - mainRunner('var a []int = []int{1, "wrong type", 3}').error?.message, - ).toEqual('Cannot use string as int64 value in slice literal.') - }) - - test('Slice indexing with non integer type should fail.', () => { - expect( - mainRunner('var a []int = []int{1, 2, 3}; fmt.Println(a[1.2])').error - ?.message, - ).toEqual('Invalid argument: Index has type float64 but must be an integer') - }) - - test('Slice len with too little arguments fails', () => { - expect( - mainRunner('a := []int{1, 2, 3, 4}; fmt.Println(len())').error?.message, - ).toEqual( - 'Invalid operation: not enough arguments for len (expected 1, found 0)', - ) - }) - - test('Slice len with too many arguments fails', () => { - expect( - mainRunner('a := []int{1, 2, 3, 4}; fmt.Println(len(a, a))').error - ?.message, - ).toEqual( - 'Invalid operation: too many arguments for len (expected 1, found 2)', - ) - }) - - test('Slice len with wrong type', () => { - expect( - mainRunner('a := []int{1, 2, 3, 4}; fmt.Println(len(1))').error?.message, - ).toEqual('Invalid argument: (int64) for len') - }) - - test('Slicing invalid types should fail.', () => { - expect(mainRunner('a := 1; b := a[:]').error?.message).toEqual( - 'Invalid operation: Cannot slice int64', - ) - }) -}) - -describe('Slice Execution', () => { - test('Slice indexing with valid index works.', () => { - expect( - mainRunner('var a []string = []string{"a", "b", "c"}\n fmt.Println(a[2])') - .output, - ).toEqual('c\n') - }) - - test('Slice indexing with negative index fails.', () => { - expect( - mainRunner( - 'var a []string = []string{"a", "b", "c"}\n fmt.Println(a[-1])', - ).error?.message, - ).toEqual('Execution Error: Index out of range [-1] with length 3') - }) - - test('Slice indexing with out of range index fails.', () => { - expect( - mainRunner('var a []string = []string{"a", "b", "c"}\n fmt.Println(a[3])') - .error?.message, - ).toEqual('Execution Error: Index out of range [3] with length 3') - }) - - test('Nested slices work.', () => { - expect( - mainRunner( - 'a := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; fmt.Println(a[1][2])', - ).output, - ).toEqual('6\n') - }) - - test('Slice len works.', () => { - expect( - mainRunner('a := [][]int{{1}, {2}, {3}}; fmt.Println(len(a))').output, - ).toEqual('3\n') - }) - - test('Slice capacity works.', () => { - expect( - mainRunner('a := [][]int{{1}, {2}, {3}}; fmt.Println(cap(a))').output, - ).toEqual('3\n') - }) - - test('Slicing works.', () => { - expect( - mainRunner(`a := [4]int{0, 1, 2, 3} - b := a[:] - fmt.Println(b) - b = b[2:] - fmt.Println(b) - c := b[1:] - fmt.Println(c) - c = c[1:] - fmt.Println(c)`).output, - ).toEqual('[0 1 2 3]\n[2 3]\n[3]\n[]\n') - }) - - test('Slicing with out of bounds range should error.', () => { - expect( - mainRunner(`a := [4]int{0, 1, 2, 3} - b := a[4:5]`).error?.message, - ).toEqual('Execution Error: Slice bounds out of range') - }) -}) diff --git a/tests/type.test.ts b/tests/type.test.ts deleted file mode 100644 index 0d988b0..0000000 --- a/tests/type.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { mainRunner } from './utility' - -describe('Assignment Type Checking', () => { - test('Declaration', () => { - expect(mainRunner('var a int = 1.0').error?.message).toEqual( - 'Cannot use float64 as int64 in variable declaration', - ) - }) - - test('Short variable declaration', () => { - expect(mainRunner('a := 1; var b string = a').error?.message).toEqual( - 'Cannot use int64 as string in variable declaration', - ) - }) - - test('Assigment', () => { - expect(mainRunner('a := "hi"; a = 2').error?.message).toEqual( - 'Cannot use int64 as string in assignment', - ) - }) -}) - -describe('Binary Operator Type Checking', () => { - test('Add assign', () => { - expect(mainRunner('a := "hi"; a = 2 * "xyz"').error?.message).toEqual( - 'Invalid operation (mismatched types int64 and string)', - ) - }) - - test('Binary multiplication', () => { - expect(mainRunner('a := 1 * 1.0').error?.message).toEqual( - 'Invalid operation (mismatched types int64 and float64)', - ) - }) -}) - -describe('Miscellaneous Type Checking', () => { - test('Variable shadowing', () => { - expect(mainRunner('a := 1; { a := 2.0; a = 1 }').error?.message).toEqual( - 'Cannot use int64 as float64 in assignment', - ) - }) -}) diff --git a/tests/utility.ts b/tests/utility.ts deleted file mode 100644 index c08677e..0000000 --- a/tests/utility.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { runCode } from '../src/virtual-machine' - -/** Runs the code in a main function */ -export const mainRunner = (code: string) => { - const packagedCode = ` - package main - import "fmt" - func main() { - ${code} - } - ` - return runCode(packagedCode, 2048) -} diff --git a/tests/waitGroup.test.ts b/tests/waitGroup.test.ts deleted file mode 100644 index 555b07d..0000000 --- a/tests/waitGroup.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { runCode } from '../src/virtual-machine' - -describe('Wait Group Type Checking', () => { - test('Wait groups should not work without importing sync.', () => { - const code = ` - package main - - func main() { - var a sync.WaitGroup - } - ` - expect(runCode(code, 2048).error?.message).toEqual( - 'Variable sync not found', - ) - }) - - test('Assinging a variable of another type to WaitGroup should fail.', () => { - const code = ` - package main - import "sync" - func main() { - var a sync.WaitGroup = "hello" - } - ` - expect(runCode(code, 2048).error?.message).toEqual( - 'Cannot use string as sync.WaitGroup in variable declaration', - ) - }) - - test('Calling .Add() with too many arguments should fail.', () => { - const code = ` - package main - import "sync" - func main() { - var a sync.WaitGroup - a.Add(1, 2) - } - ` - expect(runCode(code, 2048).error?.message).toEqual( - 'Too many arguments in function call\nhave (int64, int64)\nwant (int64)', - ) - }) -}) - -describe('Wait Group Execution', () => { - test('Making the WaitGroup counter negative by adding should panic.', () => { - const code = ` - package main - import "sync" - func main() { - var a sync.WaitGroup - a.Add(-5) - } - ` - expect(runCode(code, 2048).error?.message).toEqual( - 'Execution Error: sync: negative WaitGroup counter.', - ) - }) - - test('Making the WaitGroup counter negative by Done should panic.', () => { - const code = ` - package main - import "sync" - func main() { - var a sync.WaitGroup - a.Add(1) - a.Done() - a.Done() - } - ` - expect(runCode(code, 2048).error?.message).toEqual( - 'Execution Error: sync: negative WaitGroup counter.', - ) - }) - - test('Waiting works.', () => { - const code = ` - package main - import "fmt" - import "sync" - func main() { - count := 0 - var wg sync.WaitGroup - for i := 0; i < 1000; i++ { - wg.Add(1) - go func() { - count++ - wg.Done() - }() - } - wg.Wait() - fmt.Println(count) - } - ` - expect(runCode(code, 2048).output).toEqual('1000\n') - }) -})