Introduce TermOperator helper and refactor execute()s to use replaceSubjectMatchingTerm
This commit is contained in:
@@ -17,6 +17,6 @@ export class Clear extends Command<{}> {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export const wrapInt = (val: number): StrRVal => ({
|
||||
term: 'int',
|
||||
value: val,
|
||||
})
|
||||
|
||||
export const unwrapInt = (term: StrRVal): number => {
|
||||
if ( term.term !== '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> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub =>
|
||||
sub.includes(ctx.resolveString(data.find))
|
||||
? sub
|
||||
: ''))
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => sub.includes(ctx.resolveString(data.find)) ? sub : '',
|
||||
destructured: parts => parts.filter(part =>
|
||||
part.value.includes(ctx.resolveString(data.find))),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ export class Each extends Command<EachData> {
|
||||
.map(async part => ({
|
||||
prefix: part.prefix,
|
||||
value: await vm.runInChild(async (child, ctx) => {
|
||||
await child.replaceContextMatchingTerm({ override: part.value })
|
||||
return child.runInPlace(async ctx => {
|
||||
await ctx.replaceSubjectAsString(part.value)
|
||||
await data.exec.command.execute(child, data.exec.data)
|
||||
return unwrapString(ctx.getSubject())
|
||||
})
|
||||
|
||||
@@ -25,14 +25,16 @@ export class Enclose extends Command<EncloseData> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx => {
|
||||
const [left, right] = this.determineSurroundingStrings(
|
||||
data.left ? ctx.resolveString(data.left) : undefined,
|
||||
data.right ? ctx.resolveString(data.right) : undefined,
|
||||
)
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => {
|
||||
const [left, right] = this.determineSurroundingStrings(
|
||||
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] {
|
||||
|
||||
@@ -17,8 +17,8 @@ export class From extends Command<{ var: StrLVal }> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { var: StrLVal }): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubject(() =>
|
||||
ctx.resolveRequired(data.var)))
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
override: ctx.resolveRequired(data.var),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,16 @@ export class Join extends Command<{ with?: StrTerm }> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubject(sub => {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
restructure: parts => {
|
||||
if ( data.with ) {
|
||||
return wrapString(
|
||||
unwrapDestructured(sub)
|
||||
.map(part => part.value)
|
||||
.join(ctx.resolveString(data.with)))
|
||||
return parts
|
||||
.map(part => part.value)
|
||||
.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> {
|
||||
// `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()
|
||||
})))
|
||||
return vm.replaceContextFromChild(async child => {
|
||||
await (new Lines).execute(child)
|
||||
await (new Each).execute(child, { exec: data.exec })
|
||||
await (new Join).execute(child, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export type LinesData = {}
|
||||
|
||||
export class Lines extends Command<LinesData> {
|
||||
attemptParse(context: ParseContext): LinesData {
|
||||
export class Lines extends Command<{}> {
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -18,13 +16,15 @@ export class Lines extends Command<LinesData> {
|
||||
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 })),
|
||||
})))
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.replaceContextMatchingTerm({
|
||||
destructure: sub => {
|
||||
return sub.split('\n')
|
||||
.map((line, idx) => ({
|
||||
prefix: idx ? '\n' : undefined,
|
||||
value: line,
|
||||
}))
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ export class Lower extends Command<{}> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => sub.toLowerCase()))
|
||||
return vm.replaceContextMatchingTerm({
|
||||
stringOrDestructuredPart: sub => sub.toLowerCase(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,17 @@ export class LSub extends Command<LSubData> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub =>{
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => {
|
||||
const offset = ctx.resolveInt(data.offset)
|
||||
const length = data.length ? ctx.resolveInt(data.length) : sub.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> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub =>
|
||||
sub.includes(ctx.resolveString(data.find))
|
||||
? ''
|
||||
: sub))
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => sub.includes(ctx.resolveString(data.find)) ? '' : sub,
|
||||
destructured: parts => parts.filter(part =>
|
||||
!part.value.includes(ctx.resolveString(data.find))),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ export class Prefix extends Command<{ with: StrTerm }> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => `${ctx.resolveString(data.with)}${sub}`))
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
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> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => {
|
||||
let quote = '\''
|
||||
if ( data.with ) {
|
||||
quote = ctx.resolveString(data.with)
|
||||
@@ -47,6 +47,7 @@ export class Quote extends Command<{ with?: StrTerm }> {
|
||||
|
||||
sub = stripQuotemarkLayer(sub)
|
||||
return `${quote}${sub}${quote}`
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,9 @@ export class Replace extends Command<ReplaceData> {
|
||||
|
||||
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()
|
||||
})))
|
||||
return vm.replaceContextFromChild(async child => {
|
||||
await (new Split).execute(child, { on: data.find })
|
||||
await (new Join).execute(child, { with: data.with })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ export class RSub extends Command<RSubData> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => {
|
||||
const offset = ctx.resolveInt(data.offset)
|
||||
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
|
||||
@@ -35,6 +35,14 @@ export class RSub extends Command<RSubData> {
|
||||
.slice(offset, offset + length)
|
||||
.reverse()
|
||||
.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 {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
@@ -24,15 +24,15 @@ export class Split extends Command<SplitData> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: SplitData): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubject(sub => {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
destructure: 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 })),
|
||||
}
|
||||
}))
|
||||
return sub.split(prefix)
|
||||
.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> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => `${sub}${ctx.resolveString(data.with)}`))
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => `${sub}${ctx.resolveString(data.with)}`,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ export class Trim extends Command<TrimData> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => {
|
||||
const char = data.char
|
||||
? rexEscape(ctx.resolveString(data.char))
|
||||
: '\\s'
|
||||
@@ -49,6 +49,7 @@ export class Trim extends Command<TrimData> {
|
||||
}
|
||||
|
||||
return sub
|
||||
}))
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,19 @@ export class Unique extends Command<{}> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubject(sub => {
|
||||
return vm.replaceContextMatchingTerm({
|
||||
destructured: sub => {
|
||||
const seen: Record<string, boolean> = {}
|
||||
return wrapDestructured(
|
||||
unwrapDestructured(sub)
|
||||
.filter(part => {
|
||||
const hash = hashStrRVal(wrapString(part.value))
|
||||
if ( seen[hash] ) {
|
||||
return false
|
||||
}
|
||||
return sub.filter(part => {
|
||||
const hash = hashStrRVal(wrapString(part.value))
|
||||
if ( seen[hash] ) {
|
||||
return false
|
||||
}
|
||||
|
||||
seen[hash] = true
|
||||
return true
|
||||
}))
|
||||
}))
|
||||
seen[hash] = true
|
||||
return true
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,15 @@ export class Unquote extends Command<{ with?: StrTerm }> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => {
|
||||
return vm.replaceContextMatchingTerm(ctx => ({
|
||||
string: sub => {
|
||||
let marks: string[]|undefined = undefined
|
||||
if ( data.with ) {
|
||||
marks = [ctx.resolveString(data.with)]
|
||||
}
|
||||
|
||||
return stripQuotemarkLayer(sub, marks)
|
||||
}))
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ export class Upper extends Command<{}> {
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.tapInPlace(ctx =>
|
||||
ctx.replaceSubjectAsString(sub => sub.toUpperCase()))
|
||||
return vm.replaceContextMatchingTerm({
|
||||
stringOrDestructuredPart: sub => sub.toUpperCase(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,10 @@ export class Word extends Command<WordData> {
|
||||
|
||||
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()
|
||||
})))
|
||||
return vm.replaceContextFromChild(async child => {
|
||||
await (new Words).execute(child)
|
||||
await (new Each).execute(child, { exec: data.exec })
|
||||
await (new Join).execute(child, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Command, ParseContext, unwrapString, wrapDestructured} from "./command.js";
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
@@ -17,17 +17,16 @@ export class Words extends Command<{}> {
|
||||
}
|
||||
|
||||
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 vm.replaceContextMatchingTerm({
|
||||
destructure: sub => {
|
||||
const separators = [...sub.matchAll(/\s+/sg)]
|
||||
const parts = sub.split(/\s+/sg)
|
||||
|
||||
return wrapDestructured(
|
||||
parts.map((part, idx) => ({
|
||||
prefix: idx ? separators[idx - 1][0] : undefined,
|
||||
value: part,
|
||||
})))
|
||||
}))
|
||||
return parts.map((part, idx) => ({
|
||||
prefix: idx ? separators[idx - 1][0] : undefined,
|
||||
value: part,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user