91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
import {LexInput} from '../lexer.js'
|
|
import {
|
|
ExpectedEndOfInputError,
|
|
InvalidVariableNameError,
|
|
IsNotKeywordError,
|
|
UnexpectedEndOfInputError
|
|
} from "../parse.js";
|
|
|
|
export type StrLVal = { term: 'variable', name: string }
|
|
|
|
export type StrTerm =
|
|
{ term: 'string', value: string, literal?: true }
|
|
| 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, literal: input.literal }
|
|
}
|
|
|
|
popKeywordInSet<T extends string[]>(options: T) {
|
|
if ( !this.inputs.length ) {
|
|
throw new UnexpectedEndOfInputError('Unexpected end of input. Expected one of: ' + options.join(', '))
|
|
}
|
|
|
|
const input = this.inputs.shift()!
|
|
|
|
if ( input.literal || !options.includes(input.value) ) {
|
|
throw new IsNotKeywordError('Unexpected term: ' + input.value + ' (expected one of: ' + options.join(', ') + ')')
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|