Replace StringRange w/ an actual StrRVal + more command implementations
This commit is contained in:
parent
82eda43dad
commit
feba84051a
@ -1,4 +1,4 @@
|
|||||||
import {Command, ParseContext} from "./command.js";
|
import {Command, ParseContext, wrapString} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
import {StrVM} from "../vm.js";
|
import {StrVM} from "../vm.js";
|
||||||
import {Awaitable} from "../../util/types.js";
|
import {Awaitable} from "../../util/types.js";
|
||||||
@ -17,8 +17,6 @@ export class Clear extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx => ctx.replaceSubjectAsString(''))
|
||||||
ctx.replaceSubject(sub =>
|
|
||||||
sub.replaceWith('')))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {LexInput, LexToken} from '../lexer.js'
|
import {createHash} from 'node:crypto';
|
||||||
|
import {LexInput} from '../lexer.js'
|
||||||
import {
|
import {
|
||||||
Executable,
|
Executable,
|
||||||
ExpectedEndOfInputError,
|
ExpectedEndOfInputError,
|
||||||
@ -11,9 +12,31 @@ import {StrVM} from "../vm.js";
|
|||||||
|
|
||||||
export type StrLVal = { term: 'variable', name: string }
|
export type StrLVal = { term: 'variable', name: string }
|
||||||
|
|
||||||
|
export type StrDestructured = { term: 'destructured', value: { prefix?: string, value: string }[] }
|
||||||
|
|
||||||
|
export const joinDestructured = (val: StrDestructured['value']): string =>
|
||||||
|
val
|
||||||
|
.map(part => `${part.prefix || ''}${part.value}`)
|
||||||
|
.join('')
|
||||||
|
|
||||||
export type StrRVal =
|
export type StrRVal =
|
||||||
{ term: 'string', value: string, literal?: true }
|
{ term: 'string', value: string, literal?: true }
|
||||||
| { term: 'int', value: number }
|
| { term: 'int', value: number }
|
||||||
|
| StrDestructured
|
||||||
|
|
||||||
|
const toHex = (v: string) => createHash('sha256').update(v).digest('hex')
|
||||||
|
|
||||||
|
export const hashStrRVal = (val: StrRVal): string => {
|
||||||
|
if ( val.term === 'string' ) {
|
||||||
|
return toHex(`s:str:${val.value}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( val.term === 'int' ) {
|
||||||
|
return toHex(`s:int:${val.value}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return toHex(`s:dstr:${joinDestructured(val.value)}`)
|
||||||
|
}
|
||||||
|
|
||||||
export type StrTerm = StrRVal | StrLVal
|
export type StrTerm = StrRVal | StrLVal
|
||||||
|
|
||||||
@ -25,6 +48,10 @@ export const unwrapString = (term: StrRVal): string => {
|
|||||||
return String(term.value)
|
return String(term.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( term.term === 'destructured' ) {
|
||||||
|
throw new Error('ope!') // fixme
|
||||||
|
}
|
||||||
|
|
||||||
return term.value
|
return term.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +63,19 @@ export const unwrapInt = (term: StrRVal): number => {
|
|||||||
return term.value
|
return term.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const wrapDestructured = (val: StrDestructured['value']): StrDestructured => ({
|
||||||
|
term: 'destructured',
|
||||||
|
value: val,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const unwrapDestructured = (term: StrRVal): StrDestructured['value'] => {
|
||||||
|
if ( term.term !== 'destructured' ) {
|
||||||
|
throw new Error('Unexpected error: cannot unwrap term: is not a destructured')
|
||||||
|
}
|
||||||
|
|
||||||
|
return term.value
|
||||||
|
}
|
||||||
|
|
||||||
export const wrapString = (str: string): StrRVal => ({
|
export const wrapString = (str: string): StrRVal => ({
|
||||||
term: 'string',
|
term: 'string',
|
||||||
value: str,
|
value: str,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { LexInput } from "../lexer.js";
|
import { LexInput } from "../lexer.js";
|
||||||
import {Command, ParseContext, StrTerm, unwrapString} from "./command.js";
|
import {Command, ParseContext, StrTerm, unwrapString, wrapString} from "./command.js";
|
||||||
import {StrVM} from "../vm.js";
|
import {StrVM} from "../vm.js";
|
||||||
import {Awaitable} from "../../util/types.js";
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
@ -19,9 +19,10 @@ export class Contains extends Command<{ find: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub =>
|
||||||
sub.emptyUnlessCondition(s =>
|
sub.includes(ctx.resolveString(data.find))
|
||||||
s.includes(ctx.resolveString(data.find)))))
|
? sub
|
||||||
|
: ''))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
src/vm/commands/each.ts
Normal file
44
src/vm/commands/each.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import {Command, CommandData, ParseContext, unwrapDestructured, unwrapString, wrapDestructured} from "./command.js";
|
||||||
|
import {Executable} from "../parse.js";
|
||||||
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
|
export type EachData = {
|
||||||
|
exec: Executable<CommandData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Each extends Command<EachData> {
|
||||||
|
async attemptParse(context: ParseContext): Promise<EachData> {
|
||||||
|
return {
|
||||||
|
exec: await context.popExecutable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayName(): string {
|
||||||
|
return 'each'
|
||||||
|
}
|
||||||
|
|
||||||
|
isParseCandidate(token: LexInput): boolean {
|
||||||
|
return this.isKeyword(token, 'each')
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: EachData): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(async sub => {
|
||||||
|
const promises = unwrapDestructured(sub)
|
||||||
|
.map(async part => ({
|
||||||
|
prefix: part.prefix,
|
||||||
|
value: await vm.runInChild(async (child, ctx) => {
|
||||||
|
return child.runInPlace(async ctx => {
|
||||||
|
await ctx.replaceSubjectAsString(part.value)
|
||||||
|
await data.exec.command.execute(child, data.exec.data)
|
||||||
|
return unwrapString(ctx.getSubject())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
return wrapDestructured(await Promise.all(promises))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
import {Command, ParseContext, StrTerm, unwrapString, wrapString} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
import {StrVM} from "../vm.js";
|
import {StrVM} from "../vm.js";
|
||||||
import {Awaitable} from "../../util/types.js";
|
import {Awaitable} from "../../util/types.js";
|
||||||
@ -25,14 +25,13 @@ export class Enclose extends Command<EncloseData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx => {
|
return vm.tapInPlace(ctx => {
|
||||||
const [left, right] = this.determineSurroundingStrings(
|
const [left, right] = this.determineSurroundingStrings(
|
||||||
data.left ? ctx.resolveString(data.left) : undefined,
|
data.left ? ctx.resolveString(data.left) : undefined,
|
||||||
data.right ? ctx.resolveString(data.right) : undefined,
|
data.right ? ctx.resolveString(data.right) : undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
return ctx.replaceSubject(sub =>
|
return ctx.replaceSubjectAsString(sub => `${left}${sub}${right}`)
|
||||||
sub.modify(s => `${left}${s}${right}`))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import {Command, ParseContext, StrLVal} from "./command.js";
|
import {Command, ParseContext, StrLVal} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
export class From extends Command<{ var: StrLVal }> {
|
export class From extends Command<{ var: StrLVal }> {
|
||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
@ -13,4 +15,10 @@ export class From extends Command<{ var: StrLVal }> {
|
|||||||
getDisplayName(): string {
|
getDisplayName(): string {
|
||||||
return 'from'
|
return 'from'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: { var: StrLVal }): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(() =>
|
||||||
|
ctx.resolveRequired(data.var)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,12 +39,15 @@ import {Over} from "./over.js";
|
|||||||
import {Line} from "./line.js";
|
import {Line} from "./line.js";
|
||||||
import {Word} from "./word.js";
|
import {Word} from "./word.js";
|
||||||
import {On} from "./on.js";
|
import {On} from "./on.js";
|
||||||
|
import {Each} from "./each.js";
|
||||||
|
import {Words} from "./words.js";
|
||||||
|
|
||||||
export type Commands = Command<CommandData>[]
|
export type Commands = Command<CommandData>[]
|
||||||
export const commands: Commands = [
|
export const commands: Commands = [
|
||||||
new Clear,
|
new Clear,
|
||||||
new Contains,
|
new Contains,
|
||||||
new Copy,
|
new Copy,
|
||||||
|
new Each,
|
||||||
new Edit,
|
new Edit,
|
||||||
new Enclose,
|
new Enclose,
|
||||||
new Exit,
|
new Exit,
|
||||||
@ -82,4 +85,5 @@ export const commands: Commands = [
|
|||||||
new Unquote,
|
new Unquote,
|
||||||
new Upper,
|
new Upper,
|
||||||
new Word,
|
new Word,
|
||||||
|
new Words,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
import {Command, joinDestructured, ParseContext, StrTerm, unwrapDestructured, wrapString} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
export class Join extends Command<{ with: StrTerm }> {
|
export class Join extends Command<{ with?: StrTerm }> {
|
||||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||||
return {
|
return {
|
||||||
with: context.popTerm(),
|
with: context.popOptionalTerm(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,4 +17,18 @@ export class Join extends Command<{ with: StrTerm }> {
|
|||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
return this.isKeyword(token, 'join')
|
return this.isKeyword(token, 'join')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(sub => {
|
||||||
|
if ( data.with ) {
|
||||||
|
return wrapString(
|
||||||
|
unwrapDestructured(sub)
|
||||||
|
.map(part => part.value)
|
||||||
|
.join(ctx.resolveString(data.with)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapString(joinDestructured(unwrapDestructured(sub)))
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import {Command, CommandData, ParseContext, StrLVal} from "./command.js";
|
import {Command, CommandData, ParseContext} from "./command.js";
|
||||||
import {Executable} from "../parse.js";
|
import {Executable} from "../parse.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
import {Lines} from "./lines.js";
|
||||||
|
import {Each} from "./each.js";
|
||||||
|
import {Join} from "./join.js";
|
||||||
|
|
||||||
export type LineData = {
|
export type LineData = {
|
||||||
exec: Executable<CommandData>,
|
exec: Executable<CommandData>,
|
||||||
@ -20,4 +25,16 @@ export class Line extends Command<LineData> {
|
|||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
return this.isKeyword(token, 'line')
|
return this.isKeyword(token, 'line')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: LineData): Awaitable<StrVM> {
|
||||||
|
// `line <cmd>` is equivalent to `lines` -> `each <cmd>` -> `join`, so just do that
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(async sub =>
|
||||||
|
vm.runInChild(async (child, ctx) => {
|
||||||
|
await (new Lines).execute(child, {})
|
||||||
|
await (new Each).execute(child, { exec: data.exec })
|
||||||
|
await (new Join).execute(child, {})
|
||||||
|
return ctx.getSubject()
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
import {Command, ParseContext, unwrapString} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
export type LinesData = {
|
export type LinesData = {}
|
||||||
on?: StrTerm,
|
|
||||||
with?: StrTerm,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Lines extends Command<LinesData> {
|
export class Lines extends Command<LinesData> {
|
||||||
attemptParse(context: ParseContext): LinesData {
|
attemptParse(context: ParseContext): LinesData {
|
||||||
return {
|
return {}
|
||||||
on: context.popOptionalTerm(),
|
|
||||||
with: context.popOptionalTerm(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDisplayName(): string {
|
getDisplayName(): string {
|
||||||
@ -21,4 +17,14 @@ export class Lines extends Command<LinesData> {
|
|||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
return this.isKeyword(token, 'lines')
|
return this.isKeyword(token, 'lines')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: LinesData): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(sub => ({
|
||||||
|
term: 'destructured',
|
||||||
|
value: unwrapString(sub)
|
||||||
|
.split('\n')
|
||||||
|
.map((line, idx) => ({ prefix: idx ? '\n' : undefined, value: line })),
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,7 @@ export class Lower extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => sub.toLowerCase()))
|
||||||
sub.modify(s => s.toLowerCase())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,12 +25,11 @@ export class LSub extends Command<LSubData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub =>{
|
||||||
sub.modify(s => {
|
const offset = ctx.resolveInt(data.offset)
|
||||||
const offset = ctx.resolveInt(data.offset)
|
const length = data.length ? ctx.resolveInt(data.length) : sub.length
|
||||||
const length = data.length ? ctx.resolveInt(data.length) : s.length
|
return sub.slice(offset, offset + length)
|
||||||
return s.slice(offset, offset + length)
|
}))
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,9 +19,10 @@ export class Missing extends Command<{ find: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub =>
|
||||||
sub.emptyWhenCondition(s =>
|
sub.includes(ctx.resolveString(data.find))
|
||||||
s.includes(ctx.resolveString(data.find)))))
|
? ''
|
||||||
|
: sub))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,7 @@ export class Prefix extends Command<{ with: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => `${ctx.resolveString(data.with)}${sub}`))
|
||||||
sub.modify(s => `${ctx.resolveString(data.with)}${s}`)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,16 +38,15 @@ export class Quote extends Command<{ with?: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => {
|
||||||
sub.modify(s => {
|
let quote = '\''
|
||||||
let quote = '\''
|
if ( data.with ) {
|
||||||
if ( data.with ) {
|
quote = ctx.resolveString(data.with)
|
||||||
quote = ctx.resolveString(data.with)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
s = stripQuotemarkLayer(s)
|
sub = stripQuotemarkLayer(sub)
|
||||||
return `${quote}${s}${quote}`
|
return `${quote}${sub}${quote}`
|
||||||
})))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
import {Split} from "./split.js";
|
||||||
|
import {Join} from "./join.js";
|
||||||
|
|
||||||
export type ReplaceData = {
|
export type ReplaceData = {
|
||||||
find: StrTerm,
|
find: StrTerm,
|
||||||
@ -21,4 +25,15 @@ export class Replace extends Command<ReplaceData> {
|
|||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
return this.isKeyword(token, 'replace')
|
return this.isKeyword(token, 'replace')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: ReplaceData): Awaitable<StrVM> {
|
||||||
|
// `replace <a> <b>` is equivalent to: `split <a>` -> `join <b>`, so do that:
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(async sub =>
|
||||||
|
vm.runInChild(async (child, ctx) => {
|
||||||
|
await (new Split).execute(child, { on: data.find })
|
||||||
|
await (new Join).execute(child, { with: data.with })
|
||||||
|
return ctx.getSubject()
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,16 +26,15 @@ export class RSub extends Command<RSubData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => {
|
||||||
sub.modify(s => {
|
const offset = ctx.resolveInt(data.offset)
|
||||||
const offset = ctx.resolveInt(data.offset)
|
const length = data.length ? ctx.resolveInt(data.length) : sub.length
|
||||||
const length = data.length ? ctx.resolveInt(data.length) : s.length
|
return sub.split('') // fixme: do the math so we don't have to do this bs
|
||||||
return s.split('') // fixme: do the math so we don't have to do this bs
|
.reverse()
|
||||||
.reverse()
|
.slice(offset, offset + length)
|
||||||
.slice(offset, offset + length)
|
.reverse()
|
||||||
.reverse()
|
.join('')
|
||||||
.join('')
|
}))
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
import {Command, ParseContext, StrTerm, unwrapString} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
export type SplitData = {
|
export type SplitData = {
|
||||||
on: StrTerm,
|
on: StrTerm,
|
||||||
@ -10,7 +12,6 @@ export class Split extends Command<SplitData> {
|
|||||||
attemptParse(context: ParseContext): SplitData {
|
attemptParse(context: ParseContext): SplitData {
|
||||||
return {
|
return {
|
||||||
on: context.popTerm(),
|
on: context.popTerm(),
|
||||||
with: context.popOptionalTerm(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,4 +22,17 @@ export class Split extends Command<SplitData> {
|
|||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
return this.isKeyword(token, 'split')
|
return this.isKeyword(token, 'split')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: SplitData): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(sub => {
|
||||||
|
const prefix = ctx.resolveString(data.on)
|
||||||
|
const resolved = unwrapString(sub)
|
||||||
|
return {
|
||||||
|
term: 'destructured',
|
||||||
|
value: resolved.split(prefix)
|
||||||
|
.map((segment, idx) => ({ prefix: idx ? prefix : undefined, value: segment })),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,7 @@ export class Suffix extends Command<{ with: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => `${sub}${ctx.resolveString(data.with)}`))
|
||||||
sub.modify(s => `${s}${ctx.resolveString(data.with)}`)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import {Command, ParseContext, StrLVal} from "./command.js";
|
import {Command, ParseContext, StrLVal} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
export class To extends Command<{ var: StrLVal }> {
|
export class To extends Command<{ var: StrLVal }> {
|
||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
@ -13,4 +15,10 @@ export class To extends Command<{ var: StrLVal }> {
|
|||||||
getDisplayName(): string {
|
getDisplayName(): string {
|
||||||
return 'to'
|
return 'to'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: { var: StrLVal }): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.inScope(s =>
|
||||||
|
s.setOrShadowValue(data.var, ctx.getSubject())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,30 +26,29 @@ export class Trim extends Command<TrimData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => {
|
||||||
sub.modify(s => {
|
const char = data.char
|
||||||
const char = data.char
|
? rexEscape(ctx.resolveString(data.char))
|
||||||
? rexEscape(ctx.resolveString(data.char))
|
: '\\s'
|
||||||
: '\\s'
|
|
||||||
|
|
||||||
if ( !data.type || ['start', 'left', 'both'].includes(data.type) ) {
|
if ( !data.type || ['start', 'left', 'both'].includes(data.type) ) {
|
||||||
const leftRex = new RegExp(`^${char || '\\s'}*`, 's')
|
const leftRex = new RegExp(`^${char || '\\s'}*`, 's')
|
||||||
s = s.replace(leftRex, '')
|
sub = sub.replace(leftRex, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !data.type || ['end', 'right', 'both'].includes(data.type) ) {
|
if ( !data.type || ['end', 'right', 'both'].includes(data.type) ) {
|
||||||
const rightRex = new RegExp(`${char || '\\s'}*$`, 's')
|
const rightRex = new RegExp(`${char || '\\s'}*$`, 's')
|
||||||
s = s.replace(rightRex, '')
|
sub = sub.replace(rightRex, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( data.type === 'lines' ) {
|
if ( data.type === 'lines' ) {
|
||||||
s = s.split('\n')
|
sub = sub.split('\n')
|
||||||
.filter(l => l.trim())
|
.filter(l => l.trim())
|
||||||
.join('\n')
|
.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return sub
|
||||||
})))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import {Command, ParseContext} from "./command.js";
|
import {Command, hashStrRVal, ParseContext, unwrapDestructured, wrapDestructured, wrapString} from "./command.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
export class Unique extends Command<{}> {
|
export class Unique extends Command<{}> {
|
||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
@ -13,4 +15,22 @@ export class Unique extends Command<{}> {
|
|||||||
getDisplayName(): string {
|
getDisplayName(): string {
|
||||||
return 'unique'
|
return 'unique'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(sub => {
|
||||||
|
const seen: Record<string, boolean> = {}
|
||||||
|
return wrapDestructured(
|
||||||
|
unwrapDestructured(sub)
|
||||||
|
.filter(part => {
|
||||||
|
const hash = hashStrRVal(wrapString(part.value))
|
||||||
|
if ( seen[hash] ) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
seen[hash] = true
|
||||||
|
return true
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,15 +20,14 @@ export class Unquote extends Command<{ with?: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => {
|
||||||
sub.modify(s => {
|
let marks: string[]|undefined = undefined
|
||||||
let marks: string[]|undefined = undefined
|
if ( data.with ) {
|
||||||
if ( data.with ) {
|
marks = [ctx.resolveString(data.with)]
|
||||||
marks = [ctx.resolveString(data.with)]
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return stripQuotemarkLayer(s, marks)
|
return stripQuotemarkLayer(sub, marks)
|
||||||
})))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,7 @@ export class Upper extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.inPlace(ctx =>
|
return vm.tapInPlace(ctx =>
|
||||||
ctx.replaceSubject(sub =>
|
ctx.replaceSubjectAsString(sub => sub.toUpperCase()))
|
||||||
sub.modify(s => s.toUpperCase())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import {Command, CommandData, ParseContext, StrLVal} from "./command.js";
|
import {Command, CommandData, ParseContext} from "./command.js";
|
||||||
import {Executable} from "../parse.js";
|
import {Executable} from "../parse.js";
|
||||||
import {LexInput} from "../lexer.js";
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
import {Words} from "./words.js";
|
||||||
|
import {Each} from "./each.js";
|
||||||
|
import {Join} from "./join.js";
|
||||||
|
|
||||||
export type WordData = {
|
export type WordData = {
|
||||||
exec: Executable<CommandData>,
|
exec: Executable<CommandData>,
|
||||||
@ -20,4 +25,16 @@ export class Word extends Command<WordData> {
|
|||||||
isParseCandidate(token: LexInput): boolean {
|
isParseCandidate(token: LexInput): boolean {
|
||||||
return this.isKeyword(token, 'word')
|
return this.isKeyword(token, 'word')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM, data: WordData): Awaitable<StrVM> {
|
||||||
|
// `word <cmd>` is equivalent to `words` -> `each <cmd>` -> `join`, so just do that
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(async sub =>
|
||||||
|
vm.runInChild(async (child, ctx) => {
|
||||||
|
await (new Words).execute(child)
|
||||||
|
await (new Each).execute(child, { exec: data.exec })
|
||||||
|
await (new Join).execute(child, {})
|
||||||
|
return ctx.getSubject()
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/vm/commands/words.ts
Normal file
33
src/vm/commands/words.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {Command, ParseContext, unwrapString, wrapDestructured} from "./command.js";
|
||||||
|
import {LexInput} from "../lexer.js";
|
||||||
|
import {StrVM} from "../vm.js";
|
||||||
|
import {Awaitable} from "../../util/types.js";
|
||||||
|
|
||||||
|
export class Words extends Command<{}> {
|
||||||
|
attemptParse(context: ParseContext): {} {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayName(): string {
|
||||||
|
return 'words'
|
||||||
|
}
|
||||||
|
|
||||||
|
isParseCandidate(token: LexInput): boolean {
|
||||||
|
return this.isKeyword(token, 'words')
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
|
return vm.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(sub => {
|
||||||
|
const val = unwrapString(sub)
|
||||||
|
const separators = [...val.matchAll(/\s+/sg)]
|
||||||
|
const parts = val.split(/\s+/sg)
|
||||||
|
|
||||||
|
return wrapDestructured(
|
||||||
|
parts.map((part, idx) => ({
|
||||||
|
prefix: idx ? separators[idx - 1][0] : undefined,
|
||||||
|
value: part,
|
||||||
|
})))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/vm/output.ts
Normal file
13
src/vm/output.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {StrRVal} from "./commands/command.js";
|
||||||
|
|
||||||
|
export const getSubjectDisplay = (sub: StrRVal): string => {
|
||||||
|
if ( sub.term === 'string' ) {
|
||||||
|
return sub.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( sub.term === 'int' ) {
|
||||||
|
return String(sub.term)
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(sub.value, null, '\t') // fixme
|
||||||
|
}
|
||||||
235
src/vm/vm.ts
235
src/vm/vm.ts
@ -1,125 +1,190 @@
|
|||||||
import {Awaitable} from "../util/types.js";
|
import {Awaitable} from "../util/types.js";
|
||||||
import {CommandData, isStrRVal, StrLVal, StrRVal, StrTerm, unwrapInt, unwrapString} from "./commands/command.js";
|
import {
|
||||||
|
CommandData,
|
||||||
|
isStrRVal,
|
||||||
|
StrLVal,
|
||||||
|
StrRVal,
|
||||||
|
StrTerm,
|
||||||
|
unwrapInt,
|
||||||
|
unwrapString,
|
||||||
|
wrapString
|
||||||
|
} from "./commands/command.js";
|
||||||
import {BehaviorSubject} from "../util/subject.js";
|
import {BehaviorSubject} from "../util/subject.js";
|
||||||
import {StreamLogger} from "../util/log.js";
|
import {StreamLogger} from "../util/log.js";
|
||||||
import {Commands} from "./commands/index.js";
|
|
||||||
import {Parser} from "./parser.js";
|
import {Parser} from "./parser.js";
|
||||||
import {log} from "../log.js";
|
import {log} from "../log.js";
|
||||||
import {Executable} from "./parse.js";
|
import {Executable} from "./parse.js";
|
||||||
|
import {getSubjectDisplay} from "./output.js";
|
||||||
|
|
||||||
export class StringRange {
|
export class StringRange {
|
||||||
constructor(
|
constructor(
|
||||||
private subject: string,
|
private subject: string,
|
||||||
private parentRef?: { range: StringRange, start: number, end: number },
|
private parentRef?: { range: StringRange, start: number, end: number },
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
replaceWith(subject: string): StringRange {
|
replaceWith(subject: string): StringRange {
|
||||||
return new StringRange(subject, this.parentRef ? {...this.parentRef} : undefined)
|
return new StringRange(subject, this.parentRef ? {...this.parentRef} : undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
modify(operation: (sub: string) => string): StringRange {
|
modify(operation: (sub: string) => string): StringRange {
|
||||||
return new StringRange(operation(this.subject), this.parentRef ? {...this.parentRef} : undefined)
|
return new StringRange(operation(this.subject), this.parentRef ? {...this.parentRef} : undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyUnlessCondition(condition: (sub: string) => boolean): StringRange {
|
emptyUnlessCondition(condition: (sub: string) => boolean): StringRange {
|
||||||
return condition(this.subject)
|
return condition(this.subject)
|
||||||
? this
|
? this
|
||||||
: this.replaceWith('')
|
: this.replaceWith('')
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyWhenCondition(condition: (sub: string) => boolean): StringRange {
|
emptyWhenCondition(condition: (sub: string) => boolean): StringRange {
|
||||||
return this.emptyUnlessCondition(s => !condition(s))
|
return this.emptyUnlessCondition(s => !condition(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubject(): string {
|
getSubject(): string {
|
||||||
return this.subject
|
return this.subject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Scope {
|
export class Scope {
|
||||||
private entries: Record<string, StrRVal> = {}
|
private entries: Record<string, StrRVal> = {}
|
||||||
|
|
||||||
constructor() {}
|
constructor(
|
||||||
|
private parent?: Scope,
|
||||||
|
) {}
|
||||||
|
|
||||||
resolve(lval: StrLVal): StrRVal|undefined {
|
resolve(lval: StrLVal): StrRVal|undefined {
|
||||||
return this.entries[lval.name]
|
return this.entries[lval.name] || this.parent?.resolve(lval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOrShadowValue(lval: StrLVal, val: StrRVal): void {
|
||||||
|
if ( !this.setValueIfDefined(lval, val) ) {
|
||||||
|
this.shadowValue(lval, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setValueIfDefined(lval: StrLVal, val: StrRVal): boolean {
|
||||||
|
if ( this.entries[lval.name] ) {
|
||||||
|
this.entries[lval.name] = val
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.parent ) {
|
||||||
|
return this.parent.setValueIfDefined(lval, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowValue(lval: StrLVal, val: StrRVal) {
|
||||||
|
this.entries[lval.name] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
makeChild(): Scope {
|
||||||
|
return new Scope(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionContext {
|
export class ExecutionContext {
|
||||||
constructor(
|
constructor(
|
||||||
private subject: StringRange,
|
private subject: StrRVal,
|
||||||
private scope: Scope,
|
private scope: Scope,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async replaceSubject(operator: (sub: StringRange) => Awaitable<StringRange>) {
|
async replaceSubject(operator: (sub: StrRVal) => Awaitable<StrRVal>) {
|
||||||
this.subject = await operator(this.subject)
|
this.subject = await operator(this.subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(term: StrTerm): StrRVal|undefined {
|
async replaceSubjectAsString(operator: string|((sub: string) => Awaitable<string>)) {
|
||||||
if ( isStrRVal(term) ) {
|
if ( typeof operator === 'string' ) {
|
||||||
return term
|
return this.replaceSubject(() => wrapString(operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.scope.resolve(term)
|
return this.replaceSubject(async sub => wrapString(await operator(unwrapString(sub))))
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveRequired(term: StrTerm): StrRVal {
|
resolve(term: StrTerm): StrRVal|undefined {
|
||||||
const rval = this.resolve(term)
|
if ( isStrRVal(term) ) {
|
||||||
if ( !rval ) {
|
return term
|
||||||
throw new Error('FIXME: undefined term')
|
}
|
||||||
}
|
|
||||||
return rval
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveString(term: StrTerm): string {
|
return this.scope.resolve(term)
|
||||||
return unwrapString(this.resolveRequired(term))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
resolveInt(term: StrTerm): number {
|
inScope<TReturn>(operator: (s: Scope) => TReturn): TReturn {
|
||||||
return unwrapInt(this.resolveRequired(term))
|
return operator(this.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapSubject(): string {
|
resolveRequired(term: StrTerm): StrRVal {
|
||||||
return this.subject.getSubject()
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubject(): StrRVal {
|
||||||
|
return {...this.subject}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeChild(): ExecutionContext {
|
||||||
|
return new ExecutionContext(this.subject, this.scope.makeChild())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StrVM {
|
export class StrVM {
|
||||||
public static make(): StrVM {
|
public static make(): StrVM {
|
||||||
return new StrVM(
|
return new StrVM(
|
||||||
new ExecutionContext(
|
new ExecutionContext(wrapString(''), new Scope()))
|
||||||
new StringRange(''),
|
}
|
||||||
new Scope()))
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private context: ExecutionContext,
|
private context: ExecutionContext,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async inPlace(operator: (ctx: ExecutionContext) => Awaitable<unknown>): Promise<this> {
|
public async runInPlace<TReturn>(operator: (ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> {
|
||||||
await operator(this.context)
|
return operator(this.context)
|
||||||
return this
|
}
|
||||||
}
|
|
||||||
|
|
||||||
output() {
|
public async tapInPlace(operator: (ctx: ExecutionContext) => Awaitable<unknown>): Promise<this> {
|
||||||
console.log('---------------')
|
await this.runInPlace(operator)
|
||||||
console.log(this.context.unwrapSubject())
|
return this
|
||||||
console.log('---------------')
|
}
|
||||||
}
|
|
||||||
|
public async runInChild<TReturn>(operator: (child: StrVM, ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> {
|
||||||
|
const vm = this.makeChild()
|
||||||
|
return vm.runInPlace(ctx => operator(vm, ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
output() {
|
||||||
|
console.log('---------------')
|
||||||
|
console.log(getSubjectDisplay(this.context.getSubject()))
|
||||||
|
console.log('---------------')
|
||||||
|
}
|
||||||
|
|
||||||
|
makeChild(): StrVM {
|
||||||
|
return new StrVM(this.context.makeChild())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Executor extends BehaviorSubject<StrVM> {
|
export class Executor extends BehaviorSubject<StrVM> {
|
||||||
private logger: StreamLogger
|
private logger: StreamLogger
|
||||||
|
|
||||||
constructor(parser?: Parser) {
|
constructor(parser?: Parser) {
|
||||||
super()
|
super()
|
||||||
this.logger = log.getStreamLogger('executor')
|
this.logger = log.getStreamLogger('executor')
|
||||||
parser?.subscribe(exec => this.handleExecutable(exec))
|
parser?.subscribe(exec => this.handleExecutable(exec))
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleExecutable(exec: Executable<CommandData>) {
|
async handleExecutable(exec: Executable<CommandData>) {
|
||||||
const vm = this.currentValue || StrVM.make()
|
const vm = this.currentValue || StrVM.make()
|
||||||
await this.next(await exec.command.execute(vm, exec.data))
|
await this.next(await exec.command.execute(vm, exec.data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2016",
|
"target": "es2018",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user