Implement sub-command parsing + add on/word/line/over commands

This commit is contained in:
2025-11-11 22:09:26 -06:00
parent bfc9459b69
commit aaff8a5011
8 changed files with 153 additions and 9 deletions

View File

@@ -1,11 +1,12 @@
import {LexInput} from '../lexer.js'
import {LexInput, LexToken} from '../lexer.js'
import {
Executable,
ExpectedEndOfInputError,
InvalidVariableNameError,
IsNotKeywordError,
UnexpectedEndOfInputError
} from "../parse.js";
import {ElementType} from "../../util/types.js";
import {Awaitable, ElementType} from "../../util/types.js";
export type StrLVal = { term: 'variable', name: string }
@@ -16,6 +17,7 @@ export type StrTerm =
export class ParseContext {
constructor(
private inputs: LexInput[],
private childParser: (tokens: LexInput[]) => Awaitable<[Executable<CommandData>, LexInput[]]>,
) {}
assertEmpty() {
@@ -24,6 +26,12 @@ export class ParseContext {
}
}
async popExecutable(): Promise<Executable<CommandData>> {
const [exec, remainingInputs] = await this.childParser(this.inputs)
this.inputs = remainingInputs
return exec
}
popOptionalTerm(): StrTerm|undefined {
if ( this.inputs.length ) return this.popTerm()
return undefined
@@ -88,7 +96,7 @@ export type CommandData = Record<string, unknown>
export abstract class Command<TData extends CommandData> {
abstract isParseCandidate(token: LexInput): boolean
abstract attemptParse(context: ParseContext): TData
abstract attemptParse(context: ParseContext): Awaitable<TData>
abstract getDisplayName(): string

View File

@@ -35,6 +35,10 @@ import {Undo} from "./undo.js";
import {Unique} from "./unique.js";
import {Unquote} from "./unquote.js";
import {Upper} from "./upper.js";
import {Over} from "./over.js";
import {Line} from "./line.js";
import {Word} from "./word.js";
import {On} from "./on.js";
export type Commands = Command<CommandData>[]
export const commands: Commands = [
@@ -50,13 +54,16 @@ export const commands: Commands = [
new Indent,
new InFile,
new Join,
new Line,
new Lines,
new Lipsum,
new Load,
new Lower,
new LSub,
new Missing,
new On,
new OutFile,
new Over,
new Paste,
new Prefix,
new Quote,
@@ -74,4 +81,5 @@ export const commands: Commands = [
new Unique,
new Unquote,
new Upper,
new Word,
]

23
src/vm/commands/line.ts Normal file
View File

@@ -0,0 +1,23 @@
import {Command, CommandData, ParseContext, StrLVal} from "./command.js";
import {Executable} from "../parse.js";
import {LexInput} from "../lexer.js";
export type LineData = {
exec: Executable<CommandData>,
}
export class Line extends Command<LineData> {
async attemptParse(context: ParseContext): Promise<LineData> {
return {
exec: await context.popExecutable(),
}
}
getDisplayName(): string {
return 'line'
}
isParseCandidate(token: LexInput): boolean {
return this.isKeyword(token, 'line')
}
}

27
src/vm/commands/on.ts Normal file
View File

@@ -0,0 +1,27 @@
import {Command, CommandData, ParseContext, StrTerm} from "./command.js";
import {Executable} from "../parse.js";
import {LexInput} from "../lexer.js";
export type OnData = {
type: 'line'|'word',
specific: StrTerm,
exec: Executable<CommandData>,
}
export class On extends Command<OnData> {
async attemptParse(context: ParseContext): Promise<OnData> {
return {
type: context.popKeywordInSet(['line', 'word']).value,
specific: context.popTerm(),
exec: await context.popExecutable(),
}
}
getDisplayName(): string {
return 'on'
}
isParseCandidate(token: LexInput): boolean {
return this.isKeyword(token, 'on')
}
}

25
src/vm/commands/over.ts Normal file
View File

@@ -0,0 +1,25 @@
import {Command, CommandData, ParseContext, StrLVal} from "./command.js";
import {Executable} from "../parse.js";
import {LexInput} from "../lexer.js";
export type OverData = {
subject: StrLVal,
exec: Executable<CommandData>,
}
export class Over extends Command<OverData> {
async attemptParse(context: ParseContext): Promise<OverData> {
return {
subject: context.popLVal(),
exec: await context.popExecutable(),
}
}
getDisplayName(): string {
return 'over'
}
isParseCandidate(token: LexInput): boolean {
return this.isKeyword(token, 'over')
}
}

23
src/vm/commands/word.ts Normal file
View File

@@ -0,0 +1,23 @@
import {Command, CommandData, ParseContext, StrLVal} from "./command.js";
import {Executable} from "../parse.js";
import {LexInput} from "../lexer.js";
export type WordData = {
exec: Executable<CommandData>,
}
export class Word extends Command<WordData> {
async attemptParse(context: ParseContext): Promise<WordData> {
return {
exec: await context.popExecutable(),
}
}
getDisplayName(): string {
return 'word'
}
isParseCandidate(token: LexInput): boolean {
return this.isKeyword(token, 'word')
}
}