103 lines
3.4 KiB
TypeScript
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
|
|
}
|
|
}
|