Add & register more basic commands
This commit is contained in:
parent
c437958406
commit
bfc9459b69
@ -3,6 +3,8 @@ export type Awaitable<T> = T | Promise<T>
|
||||
export type JSONScalar = string | boolean | number | undefined
|
||||
export type JSONData = JSONScalar | Array<JSONScalar | JSONData> | { [key: string]: JSONScalar | JSONData }
|
||||
|
||||
export type ElementType<T extends readonly any[]> = T extends (infer U)[] ? U : never;
|
||||
|
||||
/** A typescript-compatible version of Object.hasOwnProperty. */
|
||||
export function hasOwnProperty<X extends {}, Y extends PropertyKey>(obj: X, prop: Y): obj is X & Record<Y, unknown> { // eslint-disable-line @typescript-eslint/ban-types
|
||||
return Object.hasOwnProperty.call(obj, prop)
|
||||
|
||||
16
src/vm/commands/clear.ts
Normal file
16
src/vm/commands/clear.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Clear extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'clear')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'clear'
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import {
|
||||
IsNotKeywordError,
|
||||
UnexpectedEndOfInputError
|
||||
} from "../parse.js";
|
||||
import {ElementType} from "../../util/types.js";
|
||||
|
||||
export type StrLVal = { term: 'variable', name: string }
|
||||
|
||||
@ -48,7 +49,12 @@ export class ParseContext {
|
||||
return { term: 'string', value: input.value, literal: input.literal }
|
||||
}
|
||||
|
||||
popKeywordInSet<T extends string[]>(options: T) {
|
||||
popOptionalKeywordInSet<const T extends readonly string[]>(options: T): (StrTerm & { value: ElementType<T> }) | undefined {
|
||||
if ( this.inputs.length ) return this.popKeywordInSet(options)
|
||||
return undefined
|
||||
}
|
||||
|
||||
popKeywordInSet<const T extends readonly string[]>(options: T): StrTerm & { value: ElementType<T> } {
|
||||
if ( !this.inputs.length ) {
|
||||
throw new UnexpectedEndOfInputError('Unexpected end of input. Expected one of: ' + options.join(', '))
|
||||
}
|
||||
@ -58,6 +64,8 @@ export class ParseContext {
|
||||
if ( input.literal || !options.includes(input.value) ) {
|
||||
throw new IsNotKeywordError('Unexpected term: ' + input.value + ' (expected one of: ' + options.join(', ') + ')')
|
||||
}
|
||||
|
||||
return { term: 'string', value: input.value as ElementType<T> }
|
||||
}
|
||||
|
||||
popLVal(): StrLVal {
|
||||
|
||||
18
src/vm/commands/contains.ts
Normal file
18
src/vm/commands/contains.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { LexInput } from "../lexer.js";
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
|
||||
export class Contains extends Command<{ find: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { find: StrTerm } {
|
||||
return {
|
||||
find: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'contains'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'contains')
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/enclose.ts
Normal file
24
src/vm/commands/enclose.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type EncloseData = {
|
||||
left?: StrTerm,
|
||||
right?: StrTerm,
|
||||
}
|
||||
|
||||
export class Enclose extends Command<EncloseData> {
|
||||
attemptParse(context: ParseContext): EncloseData {
|
||||
return {
|
||||
left: context.popOptionalTerm(),
|
||||
right: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'enclose'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'enclose')
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/help.ts
Normal file
16
src/vm/commands/help.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Help extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'help')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'help'
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/indent.ts
Normal file
24
src/vm/commands/indent.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type IndentData = {
|
||||
type: 'space'|'tab',
|
||||
level?: StrTerm,
|
||||
}
|
||||
|
||||
export class Indent extends Command<IndentData> {
|
||||
attemptParse(context: ParseContext): IndentData {
|
||||
return {
|
||||
type: context.popKeywordInSet(['space', 'tab']).value,
|
||||
level: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'indent')
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'indent'
|
||||
}
|
||||
}
|
||||
@ -11,19 +11,67 @@ import {Paste} from "./paste.js";
|
||||
import {RunFile} from "./runfile.js";
|
||||
import {Save} from "./save.js";
|
||||
import {To} from "./to.js";
|
||||
import {Lipsum} from "./lipsum.js";
|
||||
import {Indent} from "./indent.js";
|
||||
import {Clear} from "./clear.js";
|
||||
import {Contains} from "./contains.js";
|
||||
import {Enclose} from "./enclose.js";
|
||||
import {Help} from "./help.js";
|
||||
import {Join} from "./join.js";
|
||||
import {Lines} from "./lines.js";
|
||||
import {Lower} from "./lower.js";
|
||||
import {LSub} from "./lsub.js";
|
||||
import {Missing} from "./missing.js";
|
||||
import {Prefix} from "./prefix.js";
|
||||
import {Quote} from "./quote.js";
|
||||
import {Redo} from "./redo.js";
|
||||
import {Replace} from "./replace.js";
|
||||
import {RSub} from "./rsub.js";
|
||||
import {Show} from "./show.js";
|
||||
import {Split} from "./split.js";
|
||||
import {Suffix} from "./suffix.js";
|
||||
import {Trim} from "./trim.js";
|
||||
import {Undo} from "./undo.js";
|
||||
import {Unique} from "./unique.js";
|
||||
import {Unquote} from "./unquote.js";
|
||||
import {Upper} from "./upper.js";
|
||||
|
||||
export type Commands = Command<CommandData>[]
|
||||
export const commands: Commands = [
|
||||
new Clear,
|
||||
new Contains,
|
||||
new Copy,
|
||||
new Edit,
|
||||
new Enclose,
|
||||
new Exit,
|
||||
new From,
|
||||
new Help,
|
||||
new History,
|
||||
new Indent,
|
||||
new InFile,
|
||||
new Join,
|
||||
new Lines,
|
||||
new Lipsum,
|
||||
new Load,
|
||||
new Lower,
|
||||
new LSub,
|
||||
new Missing,
|
||||
new OutFile,
|
||||
new Paste,
|
||||
new Prefix,
|
||||
new Quote,
|
||||
new Redo,
|
||||
new Replace,
|
||||
new RSub,
|
||||
new RunFile,
|
||||
new Save,
|
||||
new Show,
|
||||
new Split,
|
||||
new Suffix,
|
||||
new To,
|
||||
new Trim,
|
||||
new Undo,
|
||||
new Unique,
|
||||
new Unquote,
|
||||
new Upper,
|
||||
]
|
||||
|
||||
18
src/vm/commands/join.ts
Normal file
18
src/vm/commands/join.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Join extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
return {
|
||||
with: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'join'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'join')
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/lines.ts
Normal file
24
src/vm/commands/lines.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type LinesData = {
|
||||
on?: StrTerm,
|
||||
with?: StrTerm,
|
||||
}
|
||||
|
||||
export class Lines extends Command<LinesData> {
|
||||
attemptParse(context: ParseContext): LinesData {
|
||||
return {
|
||||
on: context.popOptionalTerm(),
|
||||
with: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'lines'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'lines')
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,13 @@
|
||||
import {Command, ParseContext, StrTerm} from './command.js'
|
||||
import {LexInput} from '../lexer.js'
|
||||
|
||||
export class Lipsum extends Command<{ length: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { length: StrTerm } {
|
||||
export type LipsumData = { length: StrTerm, type: 'word'|'line'|'para' }
|
||||
|
||||
export class Lipsum extends Command<LipsumData> {
|
||||
attemptParse(context: ParseContext): LipsumData {
|
||||
return {
|
||||
length: context.popTerm(),
|
||||
type: context.popKeywordInSet(['word', 'line', 'para']).value,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/vm/commands/lower.ts
Normal file
16
src/vm/commands/lower.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Lower extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'lower')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'lower'
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/lsub.ts
Normal file
24
src/vm/commands/lsub.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type LSubData = {
|
||||
offset: StrTerm,
|
||||
length?: StrTerm,
|
||||
}
|
||||
|
||||
export class LSub extends Command<LSubData> {
|
||||
attemptParse(context: ParseContext): LSubData {
|
||||
return {
|
||||
offset: context.popTerm(),
|
||||
length: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'lsub'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'lsub')
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/missing.ts
Normal file
18
src/vm/commands/missing.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { LexInput } from "../lexer.js";
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
|
||||
export class Missing extends Command<{ find: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { find: StrTerm } {
|
||||
return {
|
||||
find: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'missing'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'missing')
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/prefix.ts
Normal file
18
src/vm/commands/prefix.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Prefix extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
return {
|
||||
with: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'prefix'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'prefix')
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/quote.ts
Normal file
18
src/vm/commands/quote.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Quote extends Command<{ with?: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||
return {
|
||||
with: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'quote'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'quote')
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/redo.ts
Normal file
18
src/vm/commands/redo.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Redo extends Command<{ steps: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'redo')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { steps: StrTerm } {
|
||||
return {
|
||||
steps: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'redo'
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/replace.ts
Normal file
24
src/vm/commands/replace.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type ReplaceData = {
|
||||
find: StrTerm,
|
||||
with: StrTerm,
|
||||
}
|
||||
|
||||
export class Replace extends Command<ReplaceData> {
|
||||
attemptParse(context: ParseContext): ReplaceData {
|
||||
return {
|
||||
find: context.popTerm(),
|
||||
with: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'replace'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'replace')
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/rsub.ts
Normal file
24
src/vm/commands/rsub.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type RSubData = {
|
||||
offset: StrTerm,
|
||||
length?: StrTerm,
|
||||
}
|
||||
|
||||
export class RSub extends Command<RSubData> {
|
||||
attemptParse(context: ParseContext): RSubData {
|
||||
return {
|
||||
offset: context.popTerm(),
|
||||
length: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'rsub'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'rsub')
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/show.ts
Normal file
16
src/vm/commands/show.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Show extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'show')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'show'
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/split.ts
Normal file
24
src/vm/commands/split.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type SplitData = {
|
||||
on: StrTerm,
|
||||
with?: StrTerm,
|
||||
}
|
||||
|
||||
export class Split extends Command<SplitData> {
|
||||
attemptParse(context: ParseContext): SplitData {
|
||||
return {
|
||||
on: context.popTerm(),
|
||||
with: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'split'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'split')
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/suffix.ts
Normal file
18
src/vm/commands/suffix.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Suffix extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
return {
|
||||
with: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'suffix'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'suffix')
|
||||
}
|
||||
}
|
||||
24
src/vm/commands/trim.ts
Normal file
24
src/vm/commands/trim.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export type TrimData = {
|
||||
type?: 'start'|'end'|'both'|'left'|'right'|'lines',
|
||||
char?: StrTerm,
|
||||
}
|
||||
|
||||
export class Trim extends Command<TrimData> {
|
||||
attemptParse(context: ParseContext): TrimData {
|
||||
return {
|
||||
type: context.popOptionalKeywordInSet(['start', 'end', 'both', 'left', 'right', 'lines'])?.value,
|
||||
char: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'trim'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'trim')
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/undo.ts
Normal file
18
src/vm/commands/undo.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Undo extends Command<{ steps: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'undo')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { steps: StrTerm } {
|
||||
return {
|
||||
steps: context.popTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'undo'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/unique.ts
Normal file
16
src/vm/commands/unique.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Unique extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'unique')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'unique'
|
||||
}
|
||||
}
|
||||
18
src/vm/commands/unquote.ts
Normal file
18
src/vm/commands/unquote.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Unquote extends Command<{ with?: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||
return {
|
||||
with: context.popOptionalTerm(),
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'unquote'
|
||||
}
|
||||
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'unquote')
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/upper.ts
Normal file
16
src/vm/commands/upper.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Upper extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'upper')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'upper'
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user