Introduce TermOperator helper and refactor execute()s to use replaceSubjectMatchingTerm
This commit is contained in:
parent
feba84051a
commit
f36621c646
@ -17,6 +17,6 @@ export class Clear extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx => ctx.replaceSubjectAsString(''))
|
return vm.replaceContextMatchingTerm({ override: '' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,11 @@ export const unwrapString = (term: StrRVal): string => {
|
|||||||
return term.value
|
return term.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const wrapInt = (val: number): StrRVal => ({
|
||||||
|
term: 'int',
|
||||||
|
value: val,
|
||||||
|
})
|
||||||
|
|
||||||
export const unwrapInt = (term: StrRVal): number => {
|
export const unwrapInt = (term: StrRVal): number => {
|
||||||
if ( term.term !== 'int' ) {
|
if ( term.term !== 'int' ) {
|
||||||
throw new Error('Unexpected error: cannot unwrap term: is not an int')
|
throw new Error('Unexpected error: cannot unwrap term: is not an int')
|
||||||
|
|||||||
@ -19,10 +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.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub =>
|
string: sub => sub.includes(ctx.resolveString(data.find)) ? sub : '',
|
||||||
sub.includes(ctx.resolveString(data.find))
|
destructured: parts => parts.filter(part =>
|
||||||
? sub
|
part.value.includes(ctx.resolveString(data.find))),
|
||||||
: ''))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,8 +30,8 @@ export class Each extends Command<EachData> {
|
|||||||
.map(async part => ({
|
.map(async part => ({
|
||||||
prefix: part.prefix,
|
prefix: part.prefix,
|
||||||
value: await vm.runInChild(async (child, ctx) => {
|
value: await vm.runInChild(async (child, ctx) => {
|
||||||
|
await child.replaceContextMatchingTerm({ override: part.value })
|
||||||
return child.runInPlace(async ctx => {
|
return child.runInPlace(async ctx => {
|
||||||
await ctx.replaceSubjectAsString(part.value)
|
|
||||||
await data.exec.command.execute(child, data.exec.data)
|
await data.exec.command.execute(child, data.exec.data)
|
||||||
return unwrapString(ctx.getSubject())
|
return unwrapString(ctx.getSubject())
|
||||||
})
|
})
|
||||||
|
|||||||
@ -25,14 +25,16 @@ export class Enclose extends Command<EncloseData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx => {
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
const [left, right] = this.determineSurroundingStrings(
|
string: sub => {
|
||||||
data.left ? ctx.resolveString(data.left) : undefined,
|
const [left, right] = this.determineSurroundingStrings(
|
||||||
data.right ? ctx.resolveString(data.right) : undefined,
|
data.left ? ctx.resolveString(data.left) : undefined,
|
||||||
)
|
data.right ? ctx.resolveString(data.right) : undefined,
|
||||||
|
)
|
||||||
|
|
||||||
return ctx.replaceSubjectAsString(sub => `${left}${sub}${right}`)
|
return `${left}${sub}${right}`
|
||||||
})
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
private determineSurroundingStrings(left?: string, right?: string): [string, string] {
|
private determineSurroundingStrings(left?: string, right?: string): [string, string] {
|
||||||
|
|||||||
@ -17,8 +17,8 @@ export class From extends Command<{ var: StrLVal }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { var: StrLVal }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { var: StrLVal }): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubject(() =>
|
override: ctx.resolveRequired(data.var),
|
||||||
ctx.resolveRequired(data.var)))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,16 +19,16 @@ export class Join extends Command<{ with?: StrTerm }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubject(sub => {
|
restructure: parts => {
|
||||||
if ( data.with ) {
|
if ( data.with ) {
|
||||||
return wrapString(
|
return parts
|
||||||
unwrapDestructured(sub)
|
.map(part => part.value)
|
||||||
.map(part => part.value)
|
.join(ctx.resolveString(data.with))
|
||||||
.join(ctx.resolveString(data.with)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapString(joinDestructured(unwrapDestructured(sub)))
|
return joinDestructured(parts)
|
||||||
}))
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,13 +28,10 @@ export class Line extends Command<LineData> {
|
|||||||
|
|
||||||
execute(vm: StrVM, data: LineData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: LineData): Awaitable<StrVM> {
|
||||||
// `line <cmd>` is equivalent to `lines` -> `each <cmd>` -> `join`, so just do that
|
// `line <cmd>` is equivalent to `lines` -> `each <cmd>` -> `join`, so just do that
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextFromChild(async child => {
|
||||||
ctx.replaceSubject(async sub =>
|
await (new Lines).execute(child)
|
||||||
vm.runInChild(async (child, ctx) => {
|
await (new Each).execute(child, { exec: data.exec })
|
||||||
await (new Lines).execute(child, {})
|
await (new Join).execute(child, {})
|
||||||
await (new Each).execute(child, { exec: data.exec })
|
})
|
||||||
await (new Join).execute(child, {})
|
|
||||||
return ctx.getSubject()
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,8 @@ 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";
|
||||||
|
|
||||||
export type LinesData = {}
|
export class Lines extends Command<{}> {
|
||||||
|
attemptParse(context: ParseContext): {} {
|
||||||
export class Lines extends Command<LinesData> {
|
|
||||||
attemptParse(context: ParseContext): LinesData {
|
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,13 +16,15 @@ export class Lines extends Command<LinesData> {
|
|||||||
return this.isKeyword(token, 'lines')
|
return this.isKeyword(token, 'lines')
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: LinesData): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm({
|
||||||
ctx.replaceSubject(sub => ({
|
destructure: sub => {
|
||||||
term: 'destructured',
|
return sub.split('\n')
|
||||||
value: unwrapString(sub)
|
.map((line, idx) => ({
|
||||||
.split('\n')
|
prefix: idx ? '\n' : undefined,
|
||||||
.map((line, idx) => ({ prefix: idx ? '\n' : undefined, value: line })),
|
value: line,
|
||||||
})))
|
}))
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,8 @@ export class Lower extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm({
|
||||||
ctx.replaceSubjectAsString(sub => sub.toLowerCase()))
|
stringOrDestructuredPart: sub => sub.toLowerCase(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,11 +25,17 @@ export class LSub extends Command<LSubData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub =>{
|
string: sub => {
|
||||||
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) : sub.length
|
||||||
return sub.slice(offset, offset + length)
|
return sub.slice(offset, offset + length)
|
||||||
}))
|
},
|
||||||
|
destructured: parts => {
|
||||||
|
const offset = ctx.resolveInt(data.offset)
|
||||||
|
const length = data.length ? ctx.resolveInt(data.length) : parts.length
|
||||||
|
return parts.slice(offset, offset + length)
|
||||||
|
},
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,10 +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.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub =>
|
string: sub => sub.includes(ctx.resolveString(data.find)) ? '' : sub,
|
||||||
sub.includes(ctx.resolveString(data.find))
|
destructured: parts => parts.filter(part =>
|
||||||
? ''
|
!part.value.includes(ctx.resolveString(data.find))),
|
||||||
: sub))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,8 @@ 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.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub => `${ctx.resolveString(data.with)}${sub}`))
|
string: sub => `${ctx.resolveString(data.with)}${sub}`,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,8 +38,8 @@ 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.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub => {
|
string: sub => {
|
||||||
let quote = '\''
|
let quote = '\''
|
||||||
if ( data.with ) {
|
if ( data.with ) {
|
||||||
quote = ctx.resolveString(data.with)
|
quote = ctx.resolveString(data.with)
|
||||||
@ -47,6 +47,7 @@ export class Quote extends Command<{ with?: StrTerm }> {
|
|||||||
|
|
||||||
sub = stripQuotemarkLayer(sub)
|
sub = stripQuotemarkLayer(sub)
|
||||||
return `${quote}${sub}${quote}`
|
return `${quote}${sub}${quote}`
|
||||||
}))
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,12 +28,9 @@ export class Replace extends Command<ReplaceData> {
|
|||||||
|
|
||||||
execute(vm: StrVM, data: ReplaceData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: ReplaceData): Awaitable<StrVM> {
|
||||||
// `replace <a> <b>` is equivalent to: `split <a>` -> `join <b>`, so do that:
|
// `replace <a> <b>` is equivalent to: `split <a>` -> `join <b>`, so do that:
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextFromChild(async child => {
|
||||||
ctx.replaceSubject(async sub =>
|
await (new Split).execute(child, { on: data.find })
|
||||||
vm.runInChild(async (child, ctx) => {
|
await (new Join).execute(child, { with: data.with })
|
||||||
await (new Split).execute(child, { on: data.find })
|
})
|
||||||
await (new Join).execute(child, { with: data.with })
|
|
||||||
return ctx.getSubject()
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,8 +26,8 @@ export class RSub extends Command<RSubData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub => {
|
string: sub => {
|
||||||
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) : sub.length
|
||||||
return sub.split('') // fixme: do the math so we don't have to do this bs
|
return sub.split('') // fixme: do the math so we don't have to do this bs
|
||||||
@ -35,6 +35,14 @@ export class RSub extends Command<RSubData> {
|
|||||||
.slice(offset, offset + length)
|
.slice(offset, offset + length)
|
||||||
.reverse()
|
.reverse()
|
||||||
.join('')
|
.join('')
|
||||||
}))
|
},
|
||||||
|
destructured: parts => {
|
||||||
|
const offset = ctx.resolveInt(data.offset)
|
||||||
|
const length = data.length ? ctx.resolveInt(data.length) : parts.length
|
||||||
|
return parts.reverse()
|
||||||
|
.slice(offset, offset + length)
|
||||||
|
.reverse()
|
||||||
|
},
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Command, ParseContext, StrTerm, unwrapString} from "./command.js";
|
import {Command, ParseContext, StrTerm, unwrapString, wrapDestructured} 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";
|
||||||
@ -24,15 +24,15 @@ export class Split extends Command<SplitData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: SplitData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: SplitData): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubject(sub => {
|
destructure: sub => {
|
||||||
const prefix = ctx.resolveString(data.on)
|
const prefix = ctx.resolveString(data.on)
|
||||||
const resolved = unwrapString(sub)
|
return sub.split(prefix)
|
||||||
return {
|
.map((segment, idx) => ({
|
||||||
term: 'destructured',
|
prefix: idx ? prefix : undefined,
|
||||||
value: resolved.split(prefix)
|
value: segment,
|
||||||
.map((segment, idx) => ({ prefix: idx ? prefix : undefined, value: segment })),
|
}))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,8 @@ 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.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub => `${sub}${ctx.resolveString(data.with)}`))
|
string: sub => `${sub}${ctx.resolveString(data.with)}`,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,8 +26,8 @@ export class Trim extends Command<TrimData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub => {
|
string: sub => {
|
||||||
const char = data.char
|
const char = data.char
|
||||||
? rexEscape(ctx.resolveString(data.char))
|
? rexEscape(ctx.resolveString(data.char))
|
||||||
: '\\s'
|
: '\\s'
|
||||||
@ -49,6 +49,7 @@ export class Trim extends Command<TrimData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return sub
|
return sub
|
||||||
}))
|
},
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,20 +17,19 @@ export class Unique extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm({
|
||||||
ctx.replaceSubject(sub => {
|
destructured: sub => {
|
||||||
const seen: Record<string, boolean> = {}
|
const seen: Record<string, boolean> = {}
|
||||||
return wrapDestructured(
|
return sub.filter(part => {
|
||||||
unwrapDestructured(sub)
|
const hash = hashStrRVal(wrapString(part.value))
|
||||||
.filter(part => {
|
if ( seen[hash] ) {
|
||||||
const hash = hashStrRVal(wrapString(part.value))
|
return false
|
||||||
if ( seen[hash] ) {
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
seen[hash] = true
|
seen[hash] = true
|
||||||
return true
|
return true
|
||||||
}))
|
})
|
||||||
}))
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,14 +20,15 @@ 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.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm(ctx => ({
|
||||||
ctx.replaceSubjectAsString(sub => {
|
string: sub => {
|
||||||
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(sub, marks)
|
return stripQuotemarkLayer(sub, marks)
|
||||||
}))
|
},
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,8 @@ export class Upper extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm({
|
||||||
ctx.replaceSubjectAsString(sub => sub.toUpperCase()))
|
stringOrDestructuredPart: sub => sub.toUpperCase(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,13 +28,10 @@ export class Word extends Command<WordData> {
|
|||||||
|
|
||||||
execute(vm: StrVM, data: WordData): Awaitable<StrVM> {
|
execute(vm: StrVM, data: WordData): Awaitable<StrVM> {
|
||||||
// `word <cmd>` is equivalent to `words` -> `each <cmd>` -> `join`, so just do that
|
// `word <cmd>` is equivalent to `words` -> `each <cmd>` -> `join`, so just do that
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextFromChild(async child => {
|
||||||
ctx.replaceSubject(async sub =>
|
await (new Words).execute(child)
|
||||||
vm.runInChild(async (child, ctx) => {
|
await (new Each).execute(child, { exec: data.exec })
|
||||||
await (new Words).execute(child)
|
await (new Join).execute(child, {})
|
||||||
await (new Each).execute(child, { exec: data.exec })
|
})
|
||||||
await (new Join).execute(child, {})
|
|
||||||
return ctx.getSubject()
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Command, ParseContext, unwrapString, wrapDestructured} from "./command.js";
|
import {Command, ParseContext} 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,17 +17,16 @@ export class Words extends Command<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute(vm: StrVM): Awaitable<StrVM> {
|
execute(vm: StrVM): Awaitable<StrVM> {
|
||||||
return vm.tapInPlace(ctx =>
|
return vm.replaceContextMatchingTerm({
|
||||||
ctx.replaceSubject(sub => {
|
destructure: sub => {
|
||||||
const val = unwrapString(sub)
|
const separators = [...sub.matchAll(/\s+/sg)]
|
||||||
const separators = [...val.matchAll(/\s+/sg)]
|
const parts = sub.split(/\s+/sg)
|
||||||
const parts = val.split(/\s+/sg)
|
|
||||||
|
|
||||||
return wrapDestructured(
|
return parts.map((part, idx) => ({
|
||||||
parts.map((part, idx) => ({
|
prefix: idx ? separators[idx - 1][0] : undefined,
|
||||||
prefix: idx ? separators[idx - 1][0] : undefined,
|
value: part,
|
||||||
value: part,
|
}))
|
||||||
})))
|
}
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
139
src/vm/vm.ts
139
src/vm/vm.ts
@ -1,12 +1,12 @@
|
|||||||
import {Awaitable} from "../util/types.js";
|
import {Awaitable} from "../util/types.js";
|
||||||
import {
|
import {
|
||||||
CommandData,
|
CommandData,
|
||||||
isStrRVal,
|
isStrRVal, StrDestructured,
|
||||||
StrLVal,
|
StrLVal,
|
||||||
StrRVal,
|
StrRVal,
|
||||||
StrTerm,
|
StrTerm, unwrapDestructured,
|
||||||
unwrapInt,
|
unwrapInt,
|
||||||
unwrapString,
|
unwrapString, wrapDestructured, wrapInt,
|
||||||
wrapString
|
wrapString
|
||||||
} from "./commands/command.js";
|
} from "./commands/command.js";
|
||||||
import {BehaviorSubject} from "../util/subject.js";
|
import {BehaviorSubject} from "../util/subject.js";
|
||||||
@ -16,35 +16,6 @@ import {log} from "../log.js";
|
|||||||
import {Executable} from "./parse.js";
|
import {Executable} from "./parse.js";
|
||||||
import {getSubjectDisplay} from "./output.js";
|
import {getSubjectDisplay} from "./output.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 {
|
export class Scope {
|
||||||
private entries: Record<string, StrRVal> = {}
|
private entries: Record<string, StrRVal> = {}
|
||||||
|
|
||||||
@ -84,6 +55,31 @@ export class Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents some operation that may be applied to a StrRVal.
|
||||||
|
* Depending on what type StrRVal is, different keys in the operator will be invoked
|
||||||
|
* and automatically wrapped/unwrapped. Keys are listed in order of precedence.
|
||||||
|
*/
|
||||||
|
export type TermOperator = {
|
||||||
|
/** Map `string` or `int` to `destructured`. */
|
||||||
|
destructure?: (sub: string) => Awaitable<StrDestructured['value']>,
|
||||||
|
/** Map `int` to `int`. */
|
||||||
|
int?: (sub: number) => Awaitable<number>,
|
||||||
|
/** Map `string` or `int` to `string`. */
|
||||||
|
string?: string | ((sub: string) => Awaitable<string>),
|
||||||
|
/** Map `destructured` to `string`. */
|
||||||
|
restructure?: (sub: StrDestructured['value']) => Awaitable<string>,
|
||||||
|
/** Map `destructured` to `destructured`. */
|
||||||
|
destructured?: (sub: StrDestructured['value']) => Awaitable<StrDestructured['value']>,
|
||||||
|
/**
|
||||||
|
* If `string`, map to `string`.
|
||||||
|
* If `destructured`, map each part individual element, but keep it as a `destructured`.
|
||||||
|
*/
|
||||||
|
stringOrDestructuredPart?: (sub: string) => Awaitable<string>,
|
||||||
|
/** If no other matches were made, just output the given rval. */
|
||||||
|
override?: string | StrRVal | ((sub: StrRVal) => Awaitable<StrRVal>),
|
||||||
|
}
|
||||||
|
|
||||||
export class ExecutionContext {
|
export class ExecutionContext {
|
||||||
constructor(
|
constructor(
|
||||||
private subject: StrRVal,
|
private subject: StrRVal,
|
||||||
@ -94,12 +90,64 @@ export class ExecutionContext {
|
|||||||
this.subject = await operator(this.subject)
|
this.subject = await operator(this.subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
async replaceSubjectAsString(operator: string|((sub: string) => Awaitable<string>)) {
|
async replaceSubjectMatchingTerm(operator: TermOperator) {
|
||||||
if ( typeof operator === 'string' ) {
|
const sub = this.subject
|
||||||
return this.replaceSubject(() => wrapString(operator))
|
if ( (sub.term === 'int' || sub.term === 'string') && operator.destructure ) {
|
||||||
|
this.subject = wrapDestructured(await operator.destructure(unwrapString(sub)))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.replaceSubject(async sub => wrapString(await operator(unwrapString(sub))))
|
if ( sub.term === 'int' && operator.int ) {
|
||||||
|
this.subject = wrapInt(await operator.int(unwrapInt(sub)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (sub.term === 'int' || sub.term === 'string') && (operator.string || operator.string === '') ) {
|
||||||
|
if ( typeof operator.string === 'string' ) {
|
||||||
|
this.subject = wrapString(operator.string)
|
||||||
|
} else {
|
||||||
|
this.subject = wrapString(await operator.string(unwrapString(sub)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (sub.term === 'int' || sub.term === 'string') && operator.stringOrDestructuredPart ) {
|
||||||
|
this.subject = wrapString(await operator.stringOrDestructuredPart(unwrapString(sub)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( sub.term === 'destructured' && operator.restructure ) {
|
||||||
|
this.subject = wrapString(await operator.restructure(unwrapDestructured(sub)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( sub.term === 'destructured' && operator.destructured ) {
|
||||||
|
this.subject = wrapDestructured(await operator.destructured(unwrapDestructured(sub)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( sub.term === 'destructured' && operator.stringOrDestructuredPart ) {
|
||||||
|
this.subject = wrapDestructured(await Promise.all(
|
||||||
|
unwrapDestructured(sub)
|
||||||
|
.map(async part => ({
|
||||||
|
...part,
|
||||||
|
value: await operator.stringOrDestructuredPart!(part.value),
|
||||||
|
}))))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( operator.override || operator.override === '' ) {
|
||||||
|
if ( typeof operator.override === 'string' ) {
|
||||||
|
this.subject = wrapString(operator.override)
|
||||||
|
} else if ( typeof operator.override === 'function' ) {
|
||||||
|
this.subject = await operator.override(sub)
|
||||||
|
} else {
|
||||||
|
this.subject = operator.override
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('(todo: better error) Cannot replace subject: could not find an appropriate operation for the term type of the current subject')
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(term: StrTerm): StrRVal|undefined {
|
resolve(term: StrTerm): StrRVal|undefined {
|
||||||
@ -158,11 +206,30 @@ export class StrVM {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async replaceContextMatchingTerm(operator: TermOperator|((ctx: ExecutionContext) => TermOperator)): Promise<this> {
|
||||||
|
return this.tapInPlace(ctx => {
|
||||||
|
if ( typeof operator === 'function' ) {
|
||||||
|
operator = operator(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.replaceSubjectMatchingTerm(operator)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public async runInChild<TReturn>(operator: (child: StrVM, ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> {
|
public async runInChild<TReturn>(operator: (child: StrVM, ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> {
|
||||||
const vm = this.makeChild()
|
const vm = this.makeChild()
|
||||||
return vm.runInPlace(ctx => operator(vm, ctx))
|
return vm.runInPlace(ctx => operator(vm, ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async replaceContextFromChild(operator: (child: StrVM, ctx: ExecutionContext) => Awaitable<unknown>): Promise<this> {
|
||||||
|
return this.tapInPlace(ctx =>
|
||||||
|
ctx.replaceSubject(async () =>
|
||||||
|
this.runInChild(async (childVm, childCtx) => {
|
||||||
|
await operator(childVm, childCtx)
|
||||||
|
return childCtx.getSubject()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
output() {
|
output() {
|
||||||
console.log('---------------')
|
console.log('---------------')
|
||||||
console.log(getSubjectDisplay(this.context.getSubject()))
|
console.log(getSubjectDisplay(this.context.getSubject()))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user