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, } /** * This command has a few forms: * * on line 3 * Assume the subject is a string and perform the given exec on line 3 * * on word 3 * Assume the subject is a string and perform the given exec on word 3 * * on index 3 * on 3 * Assume the subject is a destructured and perform the given exec on the item at index 3. */ export class On extends Command { async attemptParse(context: ParseContext): Promise { // Check if the next term we received is an int or a variable. // If so, we got the "on 3 " 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 " 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 { // 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 } }