[WIP] Start implementing execution in the new TS version
This commit is contained in:
parent
aaff8a5011
commit
82eda43dad
@ -4,6 +4,7 @@ import {Input} from './vm/input.js'
|
||||
import {Lexer} from "./vm/lexer.js";
|
||||
import {Parser} from "./vm/parser.js";
|
||||
import {commands} from "./vm/commands/index.js";
|
||||
import {Executor} from "./vm/vm.js";
|
||||
|
||||
const lifecycle = new Lifecycle()
|
||||
const input = new Input()
|
||||
@ -16,6 +17,9 @@ lexer.subscribe(token => log.verbose('token', token))
|
||||
const parser = new Parser(commands, lexer)
|
||||
parser.subscribe(exec => log.verbose('exec', exec))
|
||||
|
||||
const exec = new Executor(parser)
|
||||
exec.subscribe(state => state.output())
|
||||
|
||||
input.setupPrompt()
|
||||
|
||||
process.on('SIGINT', () => lifecycle.close())
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Clear extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@ -13,4 +15,10 @@ export class Clear extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'clear'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.replaceWith('')))
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,12 +7,40 @@ import {
|
||||
UnexpectedEndOfInputError
|
||||
} from "../parse.js";
|
||||
import {Awaitable, ElementType} from "../../util/types.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
|
||||
export type StrLVal = { term: 'variable', name: string }
|
||||
|
||||
export type StrTerm =
|
||||
export type StrRVal =
|
||||
{ term: 'string', value: string, literal?: true }
|
||||
| StrLVal
|
||||
| { term: 'int', value: number }
|
||||
|
||||
export type StrTerm = StrRVal | StrLVal
|
||||
|
||||
export const isStrRVal = (term: StrTerm): term is StrRVal =>
|
||||
term.term === 'string' || term.term === 'int'
|
||||
|
||||
export const unwrapString = (term: StrRVal): string => {
|
||||
if ( term.term === 'int' ) {
|
||||
return String(term.value)
|
||||
}
|
||||
|
||||
return term.value
|
||||
}
|
||||
|
||||
export const unwrapInt = (term: StrRVal): number => {
|
||||
if ( term.term !== 'int' ) {
|
||||
throw new Error('Unexpected error: cannot unwrap term: is not an int')
|
||||
}
|
||||
|
||||
return term.value
|
||||
}
|
||||
|
||||
export const wrapString = (str: string): StrRVal => ({
|
||||
term: 'string',
|
||||
value: str,
|
||||
literal: true,
|
||||
})
|
||||
|
||||
export class ParseContext {
|
||||
constructor(
|
||||
@ -53,6 +81,11 @@ export class ParseContext {
|
||||
return { term: 'variable', name: input.value }
|
||||
}
|
||||
|
||||
// Check if the token is a valid integer:
|
||||
if ( /^-?[1-9][0-9]*$/.test(input.value) ) {
|
||||
return { term: 'int', value: parseInt(input.value, 10) }
|
||||
}
|
||||
|
||||
// Otherwise, parse it as a string literal:
|
||||
return { term: 'string', value: input.value, literal: input.literal }
|
||||
}
|
||||
@ -100,6 +133,10 @@ export abstract class Command<TData extends CommandData> {
|
||||
|
||||
abstract getDisplayName(): string
|
||||
|
||||
execute(vm: StrVM, data: TData): Awaitable<StrVM> {
|
||||
return vm // fixme: once implemented by all commands, make abstract
|
||||
}
|
||||
|
||||
protected isKeyword(token: LexInput, keyword: string): boolean {
|
||||
return !token.literal && token.value === keyword
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { LexInput } from "../lexer.js";
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {Command, ParseContext, StrTerm, unwrapString} from "./command.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Contains extends Command<{ find: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { find: StrTerm } {
|
||||
@ -15,4 +17,11 @@ export class Contains extends Command<{ find: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'contains')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.emptyUnlessCondition(s =>
|
||||
s.includes(ctx.resolveString(data.find)))))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export type EncloseData = {
|
||||
left?: StrTerm,
|
||||
@ -21,4 +23,33 @@ export class Enclose extends Command<EncloseData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'enclose')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx => {
|
||||
const [left, right] = this.determineSurroundingStrings(
|
||||
data.left ? ctx.resolveString(data.left) : undefined,
|
||||
data.right ? ctx.resolveString(data.right) : undefined,
|
||||
)
|
||||
|
||||
return ctx.replaceSubject(sub =>
|
||||
sub.modify(s => `${left}${s}${right}`))
|
||||
})
|
||||
}
|
||||
|
||||
private determineSurroundingStrings(left?: string, right?: string): [string, string] {
|
||||
if ( !left ) {
|
||||
left = '('
|
||||
}
|
||||
|
||||
if ( !right ) {
|
||||
right = ({
|
||||
'(': ')',
|
||||
'[': ']',
|
||||
'{': '}',
|
||||
'<': '>',
|
||||
})[left] ?? left
|
||||
}
|
||||
|
||||
return [left, right]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
|
||||
export class Lower extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@ -13,4 +15,10 @@ export class Lower extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'lower'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => s.toLowerCase())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export type LSubData = {
|
||||
offset: StrTerm,
|
||||
@ -21,4 +23,14 @@ export class LSub extends Command<LSubData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'lsub')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
const offset = ctx.resolveInt(data.offset)
|
||||
const length = data.length ? ctx.resolveInt(data.length) : s.length
|
||||
return s.slice(offset, offset + length)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { LexInput } from "../lexer.js";
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Missing extends Command<{ find: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { find: StrTerm } {
|
||||
@ -15,4 +17,11 @@ export class Missing extends Command<{ find: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'missing')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.emptyWhenCondition(s =>
|
||||
s.includes(ctx.resolveString(data.find)))))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Prefix extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
@ -15,4 +17,10 @@ export class Prefix extends Command<{ with: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'prefix')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => `${ctx.resolveString(data.with)}${s}`)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,26 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export const QUOTEMARKS = ['"', '\'', '`']
|
||||
|
||||
export const stripQuotemarkLayer = (s: string, marks?: string[]): string => {
|
||||
if ( !marks ) {
|
||||
marks = QUOTEMARKS
|
||||
}
|
||||
|
||||
for ( const mark of marks ) {
|
||||
if ( !s.startsWith(mark) || !s.endsWith(mark) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
s = s.substring(mark.length, s.length - mark.length)
|
||||
break
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
export class Quote extends Command<{ with?: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||
@ -15,4 +36,18 @@ export class Quote extends Command<{ with?: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'quote')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
let quote = '\''
|
||||
if ( data.with ) {
|
||||
quote = ctx.resolveString(data.with)
|
||||
}
|
||||
|
||||
s = stripQuotemarkLayer(s)
|
||||
return `${quote}${s}${quote}`
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
import {LSubData} from "./lsub.js";
|
||||
|
||||
export type RSubData = {
|
||||
offset: StrTerm,
|
||||
@ -21,4 +24,18 @@ export class RSub extends Command<RSubData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'rsub')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
const offset = ctx.resolveInt(data.offset)
|
||||
const length = data.length ? ctx.resolveInt(data.length) : s.length
|
||||
return s.split('') // fixme: do the math so we don't have to do this bs
|
||||
.reverse()
|
||||
.slice(offset, offset + length)
|
||||
.reverse()
|
||||
.join('')
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Show extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@ -13,4 +15,8 @@ export class Show extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'show'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Suffix extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
@ -15,4 +17,10 @@ export class Suffix extends Command<{ with: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'suffix')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => `${s}${ctx.resolveString(data.with)}`)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
import {rexEscape} from "../string.js";
|
||||
|
||||
export type TrimData = {
|
||||
type?: 'start'|'end'|'both'|'left'|'right'|'lines',
|
||||
@ -21,4 +24,32 @@ export class Trim extends Command<TrimData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'trim')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
const char = data.char
|
||||
? rexEscape(ctx.resolveString(data.char))
|
||||
: '\\s'
|
||||
|
||||
if ( !data.type || ['start', 'left', 'both'].includes(data.type) ) {
|
||||
const leftRex = new RegExp(`^${char || '\\s'}*`, 's')
|
||||
s = s.replace(leftRex, '')
|
||||
}
|
||||
|
||||
if ( !data.type || ['end', 'right', 'both'].includes(data.type) ) {
|
||||
const rightRex = new RegExp(`${char || '\\s'}*$`, 's')
|
||||
s = s.replace(rightRex, '')
|
||||
}
|
||||
|
||||
if ( data.type === 'lines' ) {
|
||||
s = s.split('\n')
|
||||
.filter(l => l.trim())
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
return s
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {stripQuotemarkLayer} from "./quote.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Unquote extends Command<{ with?: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||
@ -15,4 +18,17 @@ export class Unquote extends Command<{ with?: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'unquote')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
let marks: string[]|undefined = undefined
|
||||
if ( data.with ) {
|
||||
marks = [ctx.resolveString(data.with)]
|
||||
}
|
||||
|
||||
return stripQuotemarkLayer(s, marks)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Upper extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@ -13,4 +15,10 @@ export class Upper extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'upper'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => s.toUpperCase())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import {Input} from './input.js'
|
||||
|
||||
export class StrVM {
|
||||
constructor(
|
||||
private input: Input,
|
||||
) {}
|
||||
}
|
||||
@ -3,6 +3,9 @@ export type Whitespace = { type: 'space', value: string }
|
||||
|
||||
export type Component = Word | Whitespace
|
||||
|
||||
export const rexEscape = (s: string) =>
|
||||
s.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||
|
||||
export const isWord = (cmp: Component): cmp is Word =>
|
||||
cmp.type === 'word'
|
||||
|
||||
|
||||
125
src/vm/vm.ts
Normal file
125
src/vm/vm.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import {Awaitable} from "../util/types.js";
|
||||
import {CommandData, isStrRVal, StrLVal, StrRVal, StrTerm, unwrapInt, unwrapString} from "./commands/command.js";
|
||||
import {BehaviorSubject} from "../util/subject.js";
|
||||
import {StreamLogger} from "../util/log.js";
|
||||
import {Commands} from "./commands/index.js";
|
||||
import {Parser} from "./parser.js";
|
||||
import {log} from "../log.js";
|
||||
import {Executable} from "./parse.js";
|
||||
|
||||
export class StringRange {
|
||||
constructor(
|
||||
private subject: string,
|
||||
private parentRef?: { range: StringRange, start: number, end: number },
|
||||
) {}
|
||||
|
||||
replaceWith(subject: string): StringRange {
|
||||
return new StringRange(subject, this.parentRef ? {...this.parentRef} : undefined)
|
||||
}
|
||||
|
||||
modify(operation: (sub: string) => string): StringRange {
|
||||
return new StringRange(operation(this.subject), this.parentRef ? {...this.parentRef} : undefined)
|
||||
}
|
||||
|
||||
emptyUnlessCondition(condition: (sub: string) => boolean): StringRange {
|
||||
return condition(this.subject)
|
||||
? this
|
||||
: this.replaceWith('')
|
||||
}
|
||||
|
||||
emptyWhenCondition(condition: (sub: string) => boolean): StringRange {
|
||||
return this.emptyUnlessCondition(s => !condition(s))
|
||||
}
|
||||
|
||||
getSubject(): string {
|
||||
return this.subject
|
||||
}
|
||||
}
|
||||
|
||||
export class Scope {
|
||||
private entries: Record<string, StrRVal> = {}
|
||||
|
||||
constructor() {}
|
||||
|
||||
resolve(lval: StrLVal): StrRVal|undefined {
|
||||
return this.entries[lval.name]
|
||||
}
|
||||
}
|
||||
|
||||
export class ExecutionContext {
|
||||
constructor(
|
||||
private subject: StringRange,
|
||||
private scope: Scope,
|
||||
) {}
|
||||
|
||||
async replaceSubject(operator: (sub: StringRange) => Awaitable<StringRange>) {
|
||||
this.subject = await operator(this.subject)
|
||||
}
|
||||
|
||||
resolve(term: StrTerm): StrRVal|undefined {
|
||||
if ( isStrRVal(term) ) {
|
||||
return term
|
||||
}
|
||||
|
||||
return this.scope.resolve(term)
|
||||
}
|
||||
|
||||
resolveRequired(term: StrTerm): StrRVal {
|
||||
const rval = this.resolve(term)
|
||||
if ( !rval ) {
|
||||
throw new Error('FIXME: undefined term')
|
||||
}
|
||||
return rval
|
||||
}
|
||||
|
||||
resolveString(term: StrTerm): string {
|
||||
return unwrapString(this.resolveRequired(term))
|
||||
}
|
||||
|
||||
resolveInt(term: StrTerm): number {
|
||||
return unwrapInt(this.resolveRequired(term))
|
||||
}
|
||||
|
||||
unwrapSubject(): string {
|
||||
return this.subject.getSubject()
|
||||
}
|
||||
}
|
||||
|
||||
export class StrVM {
|
||||
public static make(): StrVM {
|
||||
return new StrVM(
|
||||
new ExecutionContext(
|
||||
new StringRange(''),
|
||||
new Scope()))
|
||||
}
|
||||
|
||||
constructor(
|
||||
private context: ExecutionContext,
|
||||
) {}
|
||||
|
||||
public async inPlace(operator: (ctx: ExecutionContext) => Awaitable<unknown>): Promise<this> {
|
||||
await operator(this.context)
|
||||
return this
|
||||
}
|
||||
|
||||
output() {
|
||||
console.log('---------------')
|
||||
console.log(this.context.unwrapSubject())
|
||||
console.log('---------------')
|
||||
}
|
||||
}
|
||||
|
||||
export class Executor extends BehaviorSubject<StrVM> {
|
||||
private logger: StreamLogger
|
||||
|
||||
constructor(parser?: Parser) {
|
||||
super()
|
||||
this.logger = log.getStreamLogger('executor')
|
||||
parser?.subscribe(exec => this.handleExecutable(exec))
|
||||
}
|
||||
|
||||
async handleExecutable(exec: Executable<CommandData>) {
|
||||
const vm = this.currentValue || StrVM.make()
|
||||
await this.next(await exec.command.execute(vm, exec.data))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user