74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
|
|
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
|
||
|
|
}
|
||
|
|
}
|