112 lines
3.8 KiB
TypeScript
112 lines
3.8 KiB
TypeScript
import { readFileSync } from 'fs'
|
|
import { VM, fromValue, toValue, isValue, type Bytecode } from 'reefvm'
|
|
import { type Tree } from '@lezer/common'
|
|
import { Compiler } from '#compiler/compiler'
|
|
import { parser } from '#parser/shrimp'
|
|
import { globals as parserGlobals, setGlobals as setParserGlobals } from '#parser/tokenizer'
|
|
import { globals as prelude } from '#prelude'
|
|
|
|
export { Compiler } from '#compiler/compiler'
|
|
export { parser } from '#parser/shrimp'
|
|
export { globals as prelude } from '#prelude'
|
|
export type { Tree } from '@lezer/common'
|
|
export { type Value, type Bytecode } from 'reefvm'
|
|
export { toValue, fromValue, isValue, Scope, VM, bytecodeToString } from 'reefvm'
|
|
|
|
export class Shrimp {
|
|
vm: VM
|
|
private globals?: Record<string, any>
|
|
|
|
constructor(globals?: Record<string, any>) {
|
|
const emptyBytecode = { instructions: [], constants: [], labels: new Map() }
|
|
this.vm = new VM(emptyBytecode, Object.assign({}, prelude, globals ?? {}))
|
|
this.globals = globals
|
|
}
|
|
|
|
get(name: string): any {
|
|
const value = this.vm.scope.get(name)
|
|
return value ? fromValue(value, this.vm) : null
|
|
}
|
|
|
|
set(name: string, value: any) {
|
|
this.vm.scope.set(name, toValue(value, this.vm))
|
|
}
|
|
|
|
has(name: string): boolean {
|
|
return this.vm.scope.has(name)
|
|
}
|
|
|
|
async call(name: string, ...args: any[]): Promise<any> {
|
|
const result = await this.vm.call(name, ...args)
|
|
return isValue(result) ? fromValue(result, this.vm) : result
|
|
}
|
|
|
|
parse(code: string): Tree {
|
|
return parseCode(code, this.globals)
|
|
}
|
|
|
|
compile(code: string): Bytecode {
|
|
return compileCode(code, this.globals)
|
|
}
|
|
|
|
async run(code: string | Bytecode, locals?: Record<string, any>): Promise<any> {
|
|
let bytecode
|
|
|
|
if (typeof code === 'string') {
|
|
const compiler = new Compiler(code, Object.keys(Object.assign({}, prelude, this.globals ?? {}, locals ?? {})))
|
|
bytecode = compiler.bytecode
|
|
} else {
|
|
bytecode = code
|
|
}
|
|
|
|
if (locals) this.vm.pushScope(locals)
|
|
this.vm.appendBytecode(bytecode)
|
|
await this.vm.continue()
|
|
if (locals) this.vm.popScope()
|
|
|
|
return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!, this.vm) : null
|
|
}
|
|
|
|
}
|
|
|
|
export async function runFile(path: string, globals?: Record<string, any>): Promise<any> {
|
|
const code = readFileSync(path, 'utf-8')
|
|
return await runCode(code, globals)
|
|
}
|
|
|
|
export async function runCode(code: string, globals?: Record<string, any>): Promise<any> {
|
|
return await runBytecode(compileCode(code, globals), globals)
|
|
}
|
|
|
|
export async function runBytecode(bytecode: Bytecode, globals?: Record<string, any>): Promise<any> {
|
|
const vm = new VM(bytecode, Object.assign({}, prelude, globals))
|
|
await vm.run()
|
|
return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]!, vm) : null
|
|
}
|
|
|
|
export function compileFile(path: string, globals?: Record<string, any>): Bytecode {
|
|
const code = readFileSync(path, 'utf-8')
|
|
return compileCode(code, globals)
|
|
}
|
|
|
|
export function compileCode(code: string, globals?: Record<string, any>): Bytecode {
|
|
const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])]
|
|
const compiler = new Compiler(code, globalNames)
|
|
return compiler.bytecode
|
|
}
|
|
|
|
export function parseFile(path: string, globals?: Record<string, any>): Tree {
|
|
const code = readFileSync(path, 'utf-8')
|
|
return parseCode(code, globals)
|
|
}
|
|
|
|
export function parseCode(code: string, globals?: Record<string, any>): Tree {
|
|
const oldGlobals = [...parserGlobals]
|
|
const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])]
|
|
|
|
setParserGlobals(globalNames)
|
|
const result = parser.parse(code)
|
|
setParserGlobals(oldGlobals)
|
|
|
|
return result
|
|
} |