Files
str/src/vm/commands/on.ts

103 lines
3.4 KiB
TypeScript

import {Command, CommandData, ParseContext, StrTerm, unwrapString, wrapString} from "./command.js";
import {Executable} from "../parse.js";
import {LexInput} from "../lexer.js";
import {StrVM} from "../vm.js";
import {Lines} from "./lines.js";
import {Words} from "./words.js";
import {Join} from "./join.js";
export type OnData = {
type: 'line'|'word'|'index',
specific: StrTerm,
exec: Executable<CommandData>,
}
/**
* This command has a few forms:
*
* on line 3 <exec>
* Assume the subject is a string and perform the given exec on line 3
*
* on word 3 <exec>
* Assume the subject is a string and perform the given exec on word 3
*
* on index 3 <exec>
* on 3 <exec>
* Assume the subject is a destructured and perform the given exec on the item at index 3.
*/
export class On extends Command<OnData> {
async attemptParse(context: ParseContext): Promise<OnData> {
// Check if the next term we received is an int or a variable.
// If so, we got the "on 3 <exec>" form of the command.
const next = context.peekTerm()
if ( next?.term === 'int' || next?.term === 'variable' ) {
return {
type: 'index',
specific: context.popTerm(),
exec: await context.popExecutable(),
}
}
// Otherwise, assume we got the "on <type> <index> <exec>" form:
return {
type: context.popKeywordInSet(['line', 'word', 'index']).value,
specific: context.popTerm(),
exec: await context.popExecutable(),
}
}
getDisplayName(): string {
return 'on'
}
isParseCandidate(token: LexInput): boolean {
return this.isKeyword(token, 'on')
}
async execute(vm: StrVM, data: OnData): Promise<StrVM> {
// If the type is line|word, first destructure the subject accordingly:
let rejoin = false
if ( data.type === 'line' ) {
vm = await (new Lines).execute(vm)
rejoin = true
} else if ( data.type === 'word' ) {
vm = await (new Words).execute(vm)
rejoin = true
}
// Then, apply the given command to the specified index of the subject:
vm = await vm.replaceContextMatchingTerm(ctx => ({
destructured: async sub => {
// Retrieve the specific item in the destructured we're operating over:
const idx = ctx.resolveInt(data.specific)
const operand = sub[idx]
if ( !operand ) {
throw new Error(`Invalid ${data.type} ${idx}`)
}
// Apply the command to the value of the given index:
const result = await vm.runInChild(async (child, childCtx) => {
await childCtx.replaceSubject(() => wrapString(operand.value))
await data.exec.command.execute(child, data.exec.data)
return unwrapString(childCtx.getSubject())
})
// Replace the specific index back into the destructured:
sub[idx] = {
...operand,
value: result,
}
return sub
},
}))
// If we previously split the value (i.e. for type = line|word), rejoin it:
if ( rejoin ) {
vm = await (new Join).execute(vm, {})
}
return vm
}
}