Start reimplementation in typescript
This commit is contained in:
73
src/vm/commands/command.ts
Normal file
73
src/vm/commands/command.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {LexInput} from '../lexer.js'
|
||||
import {ExpectedEndOfInputError, InvalidVariableNameError, UnexpectedEndOfInputError} from "../parse.js";
|
||||
|
||||
export type StrLVal = { term: 'variable', name: string }
|
||||
|
||||
export type StrTerm =
|
||||
{ term: 'string', value: string }
|
||||
| StrLVal
|
||||
|
||||
export class ParseContext {
|
||||
constructor(
|
||||
private inputs: LexInput[],
|
||||
) {}
|
||||
|
||||
assertEmpty() {
|
||||
if ( this.inputs.length ) {
|
||||
throw new ExpectedEndOfInputError(`Expected end of input. Found: ${this.inputs[0].value}`)
|
||||
}
|
||||
}
|
||||
|
||||
popOptionalTerm(): StrTerm|undefined {
|
||||
if ( this.inputs.length ) return this.popTerm()
|
||||
return undefined
|
||||
}
|
||||
|
||||
popTerm(): StrTerm {
|
||||
if ( !this.inputs.length ) {
|
||||
throw new UnexpectedEndOfInputError('Unexpected end of input. Expected term.');
|
||||
}
|
||||
|
||||
const input = this.inputs.shift()!
|
||||
|
||||
// Check if the token is a literal variable name:
|
||||
if ( !input.literal && input.value.startsWith('$') ) {
|
||||
if ( !input.value.match(/^\$[a-zA-Z0-9_]+$/) ) {
|
||||
throw new InvalidVariableNameError(`Invalid variable name: ${input.value}`)
|
||||
}
|
||||
|
||||
return { term: 'variable', name: input.value }
|
||||
}
|
||||
|
||||
// Otherwise, parse it as a string literal:
|
||||
return { term: 'string', value: input.value }
|
||||
}
|
||||
|
||||
popLVal(): StrLVal {
|
||||
if ( !this.inputs.length ) {
|
||||
throw new UnexpectedEndOfInputError('Unexpected end of input. Expected lval.');
|
||||
}
|
||||
|
||||
const input = this.inputs.shift()!
|
||||
|
||||
if ( input.literal || !input.value.match(/^\$[a-zA-Z0-9_]+$/) ) {
|
||||
throw new InvalidVariableNameError(`Expected variable name. Found: ${input.value}`)
|
||||
}
|
||||
|
||||
return { term: 'variable', name: input.value }
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandData = Record<string, unknown>
|
||||
|
||||
export abstract class Command<TData extends CommandData> {
|
||||
abstract isParseCandidate(token: LexInput): boolean
|
||||
|
||||
abstract attemptParse(context: ParseContext): TData
|
||||
|
||||
abstract getDisplayName(): string
|
||||
|
||||
protected isKeyword(token: LexInput, keyword: string): boolean {
|
||||
return !token.literal && token.value === keyword
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/copy.ts
Normal file
16
src/vm/commands/copy.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Copy extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'copy')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'copy'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/edit.ts
Normal file
16
src/vm/commands/edit.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Edit extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'edit')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'edit'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/exit.ts
Normal file
16
src/vm/commands/exit.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Exit extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'exit')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'exit'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/from.ts
Normal file
16
src/vm/commands/from.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrLVal} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class From extends Command<{ var: StrLVal }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'from')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { var: StrLVal } {
|
||||
return { var: context.popLVal() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'from'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/history.ts
Normal file
16
src/vm/commands/history.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class History extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'history')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'history'
|
||||
}
|
||||
}
|
||||
29
src/vm/commands/index.ts
Normal file
29
src/vm/commands/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {Command, CommandData} from './command.js'
|
||||
import {Exit} from "./exit.js";
|
||||
import {InFile} from "./infile.js";
|
||||
import {Copy} from "./copy.js";
|
||||
import {Edit} from "./edit.js";
|
||||
import {From} from "./from.js";
|
||||
import {History} from "./history.js";
|
||||
import {Load} from "./load.js";
|
||||
import {OutFile} from "./outfile.js";
|
||||
import {Paste} from "./paste.js";
|
||||
import {RunFile} from "./runfile.js";
|
||||
import {Save} from "./save.js";
|
||||
import {To} from "./to.js";
|
||||
|
||||
export type Commands = Command<CommandData>[]
|
||||
export const commands: Commands = [
|
||||
new Copy,
|
||||
new Edit,
|
||||
new Exit,
|
||||
new From,
|
||||
new History,
|
||||
new InFile,
|
||||
new Load,
|
||||
new OutFile,
|
||||
new Paste,
|
||||
new RunFile,
|
||||
new Save,
|
||||
new To,
|
||||
]
|
||||
16
src/vm/commands/infile.ts
Normal file
16
src/vm/commands/infile.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class InFile extends Command<{ path: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'infile')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { path: StrTerm } {
|
||||
return { path: context.popTerm() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'infile'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/load.ts
Normal file
16
src/vm/commands/load.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Load extends Command<{ path?: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'load')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { path?: StrTerm } {
|
||||
return { path: context.popOptionalTerm() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'load'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/outfile.ts
Normal file
16
src/vm/commands/outfile.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class OutFile extends Command<{ path: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'outfile')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { path: StrTerm } {
|
||||
return { path: context.popTerm() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'outfile'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/paste.ts
Normal file
16
src/vm/commands/paste.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Paste extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'paste')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): {} {
|
||||
return {}
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'paste'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/runfile.ts
Normal file
16
src/vm/commands/runfile.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class RunFile extends Command<{ path: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'runfile')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { path: StrTerm } {
|
||||
return { path: context.popTerm() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'runfile'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/save.ts
Normal file
16
src/vm/commands/save.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class Save extends Command<{ path?: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'save')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { path?: StrTerm } {
|
||||
return { path: context.popOptionalTerm() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'save'
|
||||
}
|
||||
}
|
||||
16
src/vm/commands/to.ts
Normal file
16
src/vm/commands/to.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Command, ParseContext, StrLVal} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
|
||||
export class To extends Command<{ var: StrLVal }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'to')
|
||||
}
|
||||
|
||||
attemptParse(context: ParseContext): { var: StrLVal } {
|
||||
return { var: context.popLVal() }
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
return 'to'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user