Implement sub-command parsing + add on/word/line/over commands
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
23
src/vm/commands/line.ts
Normal 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
27
src/vm/commands/on.ts
Normal 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
25
src/vm/commands/over.ts
Normal 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
23
src/vm/commands/word.ts
Normal 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')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user