import {Command, ParseContext, StrTerm} from "./command.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 DropData = { type: 'line'|'word'|'index', specific: StrTerm, } /** * Like `on`, this command has a few forms: * * drop word 3 | drop line 3 * Assume the subject is a string. Split into words|lines respectively, remove the 3rd index, and rejoin. * * drop 3 | drop index 3 * Assume the subject is a destructured. Drop the 3rd element. */ export class Drop extends Command { async attemptParse(context: ParseContext): Promise { const next = context.peekTerm() if ( next?.term === 'int' || next?.term === 'variable' ) { return { type: 'index', specific: context.popTerm(), } } return { type: context.popKeywordInSet(['line', 'word', 'index']).value, specific: context.popTerm(), } } getDisplayName(): string { return 'drop' } isParseCandidate(token: LexInput): boolean { return this.isKeyword(token, 'drop') } async execute(vm: StrVM, data: DropData): 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, drop the item at the given index in the destructured subject: vm = await vm.replaceContextMatchingTerm(ctx => ({ destructured: async sub => { const idx = ctx.resolveInt(data.specific) sub = [...sub] // In word mode, all whitespace is preserved. So, we need to take the prefix // of the element that's about to be deleted and add it to the prefix of the // word right after it (example: "a\nb c" `drop word 1` should become "a\n c", not "a c" if ( data.type === 'word' && sub[idx]?.prefix && sub[idx + 1] ) { sub[idx + 1].prefix = `${sub[idx].prefix}${sub[idx + 1].prefix || ''}` } // Remove the nth element. sub.splice(idx, 1) // In line mode, if we delete a line, we *don't* want the newline // prefixes to be preserved, as that would clear the contents of the line, // but leave an empty line in-place. The only edge case we need to account // for is if we removed the first line, we should remove the newline prefix // from the *new* first line: if ( data.type === 'line' && idx === 0 && sub.length > 0 ) { sub[0].prefix = undefined } 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 } }