Add Shakespeare esolang

This commit is contained in:
Nilay Majorwar
2022-02-18 16:57:59 +05:30
parent a05731e91d
commit e810855933
26 changed files with 4106 additions and 2 deletions

View File

@ -0,0 +1,74 @@
/** Regex bit to accommodate line breaks in multi-word keywords */
const br = /(?: |\s*\n\s*)/.source;
/** Utility to create regex from list of words */
const makeRegex = (words: string[], flags?: string) => {
const fixedWords = words.map((w) => w.replace(/ /g, br));
const pattern = "(" + fixedWords.join("|") + ")\\b";
return new RegExp(pattern, flags);
};
// prettier-ignore
const CHARACTERS = ["Achilles", "Adonis", "Adriana", "Aegeon", "Aemilia", "Agamemnon", "Agrippa", "Ajax", "Alonso", "Andromache",
"Angelo", "Antiochus", "Antonio", "Arthur", "Autolycus", "Balthazar", "Banquo", "Beatrice", "Benedick", "Benvolio", "Bianca",
"Brabantio", "Brutus", "Capulet", "Cassandra", "Cassius", "Christopher Sly", "Cicero", "Claudio", "Claudius", "Cleopatra",
"Cordelia", "Cornelius", "Cressida", "Cymberline", "Demetrius", "Desdemona", "Dionyza", "Doctor Caius", "Dogberry", "Don John",
"Don Pedro", "Donalbain", "Dorcas", "Duncan", "Egeus", "Emilia", "Escalus", "Falstaff", "Fenton", "Ferdinand", "Ford", "Fortinbras",
"Francisca", "Friar John", "Friar Laurence", "Gertrude", "Goneril", "Hamlet", "Hecate", "Hector", "Helen", "Helena", "Hermia",
"Hermonie", "Hippolyta", "Horatio", "Imogen", "Isabella", "John of Gaunt", "John of Lancaster", "Julia", "Juliet", "Julius Caesar",
"King Henry", "King John", "King Lear", "King Richard", "Lady Capulet", "Lady Macbeth", "Lady Macduff", "Lady Montague", "Lennox",
"Leonato", "Luciana", "Lucio", "Lychorida", "Lysander", "Macbeth", "Macduff", "Malcolm", "Mariana", "Mark Antony", "Mercutio",
"Miranda", "Mistress Ford", "Mistress Overdone", "Mistress Page", "Montague", "Mopsa", "Oberon", "Octavia", "Octavius Caesar",
"Olivia", "Ophelia", "Orlando", "Orsino", "Othello", "Page", "Pantino", "Paris", "Pericles", "Pinch", "Polonius", "Pompeius",
"Portia", "Priam", "Prince Henry", "Prospero", "Proteus", "Publius", "Puck", "Queen Elinor", "Regan", "Robin", "Romeo", "Rosalind",
"Sebastian", "Shallow", "Shylock", "Slender", "Solinus", "Stephano", "Thaisa", "The Abbot of Westminster", "The Apothecary",
"The Archbishop of Canterbury", "The Duke of Milan", "The Duke of Venice", "The Ghost", "Theseus", "Thurio", "Timon", "Titania",
"Titus", "Troilus", "Tybalt", "Ulysses", "Valentine", "Venus", "Vincentio", "Viola"]
/** Regex that matches an identified character name */
export const CharacterRegex = makeRegex(CHARACTERS, "i");
// prettier-ignore
const NEGATIVE_ADJECTIVE = ["bad","cowardly","cursed","damned","dirty","disgusting", "distasteful","dusty","evil","fat-kidneyed",
"fat","fatherless","foul","hairy","half-witted", "horrible","horrid","infected","lying","miserable","misused",
"oozing","rotten","rotten","smelly","snotty","sorry", "stinking","stuffed","stupid","vile","villainous","worried"]
/** Regex that matches a negative adjective */
export const NegativeAdjectiveRegex = makeRegex(NEGATIVE_ADJECTIVE, "i");
// prettier-ignore
const NEGATIVE_NOUNS = ["Hell","Microsoft","bastard","beggar","blister","codpiece","coward","curse","death","devil","draught",
"famine","flirt-gill","goat","hate","hog","hound","leech","lie","pig","plague","starvation","toad","war","wolf"];
/** Regex that matches a negative noun */
export const NegativeNounRegex = makeRegex(NEGATIVE_NOUNS, "i");
// prettier-ignore
const NEUTRAL_ADJECTIVES = ["big","black","blue","bluest","bottomless","furry","green","hard","huge","large","little",
"normal","old","purple","red","rural","small","tiny","white","yellow"];
/** Regex that matches a neutral adjective */
export const NeutralAdjectiveRegex = makeRegex(NEUTRAL_ADJECTIVES, "i");
// prettier-ignore
const NEUTRAL_NOUNS = ["animal","aunt","brother","cat","chihuahua","cousin","cow","daughter","door","face","father",
"fellow","granddaughter","grandfather","grandmother","grandson","hair","hamster","horse","lamp","lantern","mistletoe",
"moon","morning","mother","nephew","niece","nose","purse","road","roman","sister","sky","son","squirrel","stone wall",
"thing","town","tree","uncle","wind"]
/** Regex that matches a neutral noun */
export const NeutralNounRegex = makeRegex(NEUTRAL_NOUNS, "i");
// prettier-ignore
const POSITIVE_ADJECTIVES = ["amazing","beautiful","blossoming","bold","brave","charming","clearest","cunning","cute",
"delicious","embroidered","fair","fine","gentle","golden","good","handsome","happy","healthy","honest","lovely","loving",
"mighty","noble","peaceful","pretty","prompt","proud","reddest","rich","smooth","sunny","sweet","sweetest","trustworthy","warm"]
/** Regex that matches a positive adjective */
export const PositiveAdjectiveRegex = makeRegex(POSITIVE_ADJECTIVES, "i");
// prettier-ignore
const POSITIVE_NOUN = ["Heaven","King","Lord","angel","flower","happiness","joy","plum","summer's day","hero","rose","kingdom","pony"]
/** Regex that matches a positive noun */
export const PositiveNounRegex = makeRegex(POSITIVE_NOUN, "i");

671
languages/shakespeare/parser/cst.d.ts vendored Normal file
View File

@ -0,0 +1,671 @@
import type { CstNode, ICstVisitor, IToken } from "chevrotain";
export interface ProgramCstNode extends CstNode {
name: "program";
children: ProgramCstChildren;
}
export type ProgramCstChildren = {
programTitle: ProgramTitleCstNode[];
WhitespaceOrNewline?: IToken[];
characterIntro?: CharacterIntroCstNode[];
actSection?: ActSectionCstNode[];
};
export interface ProgramTitleCstNode extends CstNode {
name: "programTitle";
children: ProgramTitleCstChildren;
}
export type ProgramTitleCstChildren = {
Word?: IToken[];
Period: IToken[];
};
export interface CharacterIntroCstNode extends CstNode {
name: "characterIntro";
children: CharacterIntroCstChildren;
}
export type CharacterIntroCstChildren = {
Character: IToken[];
Comma: IToken[];
Word?: IToken[];
SentenceMark: IToken[];
};
export interface ActHeadingCstNode extends CstNode {
name: "actHeading";
children: ActHeadingCstChildren;
}
export type ActHeadingCstChildren = {
Act: IToken[];
RomanNumeral: IToken[];
Colon: IToken[];
Word?: IToken[];
SentenceMark: IToken[];
};
export interface SceneHeadingCstNode extends CstNode {
name: "sceneHeading";
children: SceneHeadingCstChildren;
}
export type SceneHeadingCstChildren = {
Scene: IToken[];
RomanNumeral: IToken[];
Colon: IToken[];
Word?: IToken[];
SentenceMark: IToken[];
};
export interface EntranceCstNode extends CstNode {
name: "entrance";
children: EntranceCstChildren;
}
export type EntranceCstChildren = {
SquareBracketOpen: IToken[];
Enter: IToken[];
Character: IToken[];
And?: IToken[];
SquareBracketClose: IToken[];
};
export interface ExitCstNode extends CstNode {
name: "exit";
children: ExitCstChildren;
}
export type ExitCstChildren = {
SquareBracketOpen: IToken[];
Exit: IToken[];
Character: IToken[];
SquareBracketClose: IToken[];
};
export interface MultiExitCstNode extends CstNode {
name: "multiExit";
children: MultiExitCstChildren;
}
export type MultiExitCstChildren = {
SquareBracketOpen: IToken[];
Exeunt: IToken[];
Character?: IToken[];
And?: IToken[];
SquareBracketClose: IToken[];
};
export interface EntryExitClauseCstNode extends CstNode {
name: "entryExitClause";
children: EntryExitClauseCstChildren;
}
export type EntryExitClauseCstChildren = {
entrance?: EntranceCstNode[];
exit?: ExitCstNode[];
multiExit?: MultiExitCstNode[];
};
export interface ActSectionCstNode extends CstNode {
name: "actSection";
children: ActSectionCstChildren;
}
export type ActSectionCstChildren = {
actHeading: ActHeadingCstNode[];
sceneSection: SceneSectionCstNode[];
};
export interface SceneSectionCstNode extends CstNode {
name: "sceneSection";
children: SceneSectionCstChildren;
}
export type SceneSectionCstChildren = {
sceneHeading: SceneHeadingCstNode[];
sceneSectionChunk: SceneSectionChunkCstNode[];
};
export interface SceneSectionChunkCstNode extends CstNode {
name: "sceneSectionChunk";
children: SceneSectionChunkCstChildren;
}
export type SceneSectionChunkCstChildren = {
entryExitClause?: EntryExitClauseCstNode[];
dialogueSet?: DialogueSetCstNode[];
};
export interface SpeakerClauseCstNode extends CstNode {
name: "speakerClause";
children: SpeakerClauseCstChildren;
}
export type SpeakerClauseCstChildren = {
Character: IToken[];
Colon: IToken[];
};
export interface DialogueSetCstNode extends CstNode {
name: "dialogueSet";
children: DialogueSetCstChildren;
}
export type DialogueSetCstChildren = {
speakerClause: SpeakerClauseCstNode[];
dialogueLine: DialogueLineCstNode[];
};
export interface DialogueLineCstNode extends CstNode {
name: "dialogueLine";
children: DialogueLineCstChildren;
}
export type DialogueLineCstChildren = {
conditional?: ConditionalCstNode[];
nonConditionalDialogueLine?: NonConditionalDialogueLineCstNode[];
};
export interface NonConditionalDialogueLineCstNode extends CstNode {
name: "nonConditionalDialogueLine";
children: NonConditionalDialogueLineCstChildren;
}
export type NonConditionalDialogueLineCstChildren = {
assignment?: AssignmentCstNode[];
stdin?: StdinCstNode[];
stdout?: StdoutCstNode[];
goto?: GotoCstNode[];
stackPush?: StackPushCstNode[];
stackPop?: StackPopCstNode[];
question?: QuestionCstNode[];
};
export interface NounCstNode extends CstNode {
name: "noun";
children: NounCstChildren;
}
export type NounCstChildren = {
NegativeNoun?: IToken[];
NeutralNoun?: IToken[];
PositiveNoun?: IToken[];
};
export interface AdjectiveCstNode extends CstNode {
name: "adjective";
children: AdjectiveCstChildren;
}
export type AdjectiveCstChildren = {
NegativeAdjective?: IToken[];
NeutralAdjective?: IToken[];
PositiveAdjective?: IToken[];
};
export interface PossessiveCstNode extends CstNode {
name: "possessive";
children: PossessiveCstChildren;
}
export type PossessiveCstChildren = {
FirstPersonPossessive?: IToken[];
SecondPersonPossessive?: IToken[];
ThirdPersonPossessive?: IToken[];
};
export interface ReflexiveCstNode extends CstNode {
name: "reflexive";
children: ReflexiveCstChildren;
}
export type ReflexiveCstChildren = {
FirstPersonReflexive?: IToken[];
SecondPersonReflexive?: IToken[];
};
export interface ComparativeCstNode extends CstNode {
name: "comparative";
children: ComparativeCstChildren;
}
export type ComparativeCstChildren = {
PositiveComparative?: IToken[];
NegativeComparative?: IToken[];
};
export interface AssignmentCstNode extends CstNode {
name: "assignment";
children: AssignmentCstChildren;
}
export type AssignmentCstChildren = {
exclaimAssignment?: ExclaimAssignmentCstNode[];
arithAssignment?: ArithAssignmentCstNode[];
};
export interface ExclaimAssignmentCstNode extends CstNode {
name: "exclaimAssignment";
children: ExclaimAssignmentCstChildren;
}
export type ExclaimAssignmentCstChildren = {
SecondPerson: IToken[];
unarticulatedVerbalConstant: UnarticulatedVerbalConstantCstNode[];
SentenceMark: IToken[];
};
export interface ArithAssignmentCstNode extends CstNode {
name: "arithAssignment";
children: ArithAssignmentCstChildren;
}
export type ArithAssignmentCstChildren = {
SecondPerson: IToken[];
Be: IToken[];
As?: IToken[];
adjective?: AdjectiveCstNode[];
expression: ExpressionCstNode[];
SentenceMark: IToken[];
};
export interface StdinCstNode extends CstNode {
name: "stdin";
children: StdinCstChildren;
}
export type StdinCstChildren = {
Listen?: IToken[];
To?: IToken[];
SecondPersonPossessive?: IToken[];
Heart?: IToken[];
Open?: IToken[];
Mind?: IToken[];
SentenceMark: IToken[];
};
export interface StdoutCstNode extends CstNode {
name: "stdout";
children: StdoutCstChildren;
}
export type StdoutCstChildren = {
Open?: IToken[];
SecondPersonPossessive?: IToken[];
Heart?: IToken[];
Speak?: IToken[];
Mind?: IToken[];
SentenceMark: IToken[];
};
export interface GotoCstNode extends CstNode {
name: "goto";
children: GotoCstChildren;
}
export type GotoCstChildren = {
Let?: IToken[];
Us?: IToken[];
We?: IToken[];
Shall?: IToken[];
Must?: IToken[];
Return?: IToken[];
Proceed?: IToken[];
To: IToken[];
Act?: IToken[];
Scene?: IToken[];
RomanNumeral: IToken[];
SentenceMark: IToken[];
};
export interface StackPushCstNode extends CstNode {
name: "stackPush";
children: StackPushCstChildren;
}
export type StackPushCstChildren = {
Remember: IToken[];
expression: ExpressionCstNode[];
SentenceMark: IToken[];
};
export interface StackPopCstNode extends CstNode {
name: "stackPop";
children: StackPopCstChildren;
}
export type StackPopCstChildren = {
Recall: IToken[];
Word?: IToken[];
SentenceMark: IToken[];
};
export interface QuestionCstNode extends CstNode {
name: "question";
children: QuestionCstChildren;
}
export type QuestionCstChildren = {
Be: IToken[];
lhs: ExpressionCstNode[];
comparator: ComparatorCstNode[];
rhs: ExpressionCstNode[];
QuestionMark: IToken[];
};
export interface ConditionalCstNode extends CstNode {
name: "conditional";
children: ConditionalCstChildren;
}
export type ConditionalCstChildren = {
If: IToken[];
So?: IToken[];
Not?: IToken[];
Comma: IToken[];
nonConditionalDialogueLine: NonConditionalDialogueLineCstNode[];
};
export interface ComparatorCstNode extends CstNode {
name: "comparator";
children: ComparatorCstChildren;
}
export type ComparatorCstChildren = {
Not?: IToken[];
_asComparator?: _asComparatorCstNode[];
_simpleComparator?: _simpleComparatorCstNode[];
_moreLessComparator?: _moreLessComparatorCstNode[];
};
export interface _asComparatorCstNode extends CstNode {
name: "_asComparator";
children: _asComparatorCstChildren;
}
export type _asComparatorCstChildren = {
As: IToken[];
adjective: AdjectiveCstNode[];
};
export interface _simpleComparatorCstNode extends CstNode {
name: "_simpleComparator";
children: _simpleComparatorCstChildren;
}
export type _simpleComparatorCstChildren = {
comparative: ComparativeCstNode[];
Than: IToken[];
};
export interface _moreLessComparatorCstNode extends CstNode {
name: "_moreLessComparator";
children: _moreLessComparatorCstChildren;
}
export type _moreLessComparatorCstChildren = {
More?: IToken[];
Less?: IToken[];
adjective: AdjectiveCstNode[];
Than: IToken[];
};
export interface ConstantCstNode extends CstNode {
name: "constant";
children: ConstantCstChildren;
}
export type ConstantCstChildren = {
_simpleConstant?: _simpleConstantCstNode[];
_verbalConstant?: _verbalConstantCstNode[];
};
export interface _simpleConstantCstNode extends CstNode {
name: "_simpleConstant";
children: _simpleConstantCstChildren;
}
export type _simpleConstantCstChildren = {
Nothing: IToken[];
};
export interface _verbalConstantCstNode extends CstNode {
name: "_verbalConstant";
children: _verbalConstantCstChildren;
}
export type _verbalConstantCstChildren = {
Article?: IToken[];
possessive?: PossessiveCstNode[];
unarticulatedVerbalConstant: UnarticulatedVerbalConstantCstNode[];
};
export interface UnarticulatedVerbalConstantCstNode extends CstNode {
name: "unarticulatedVerbalConstant";
children: UnarticulatedVerbalConstantCstChildren;
}
export type UnarticulatedVerbalConstantCstChildren = {
adjective?: AdjectiveCstNode[];
noun: NounCstNode[];
};
export interface ExpressionCstNode extends CstNode {
name: "expression";
children: ExpressionCstChildren;
}
export type ExpressionCstChildren = {
sumExpression?: SumExpressionCstNode[];
differenceExpression?: DifferenceExpressionCstNode[];
productExpression?: ProductExpressionCstNode[];
quotientExpression?: QuotientExpressionCstNode[];
remainderExpression?: RemainderExpressionCstNode[];
factorialExpression?: FactorialExpressionCstNode[];
squareExpression?: SquareExpressionCstNode[];
squareRootExpression?: SquareRootExpressionCstNode[];
cubeExpression?: CubeExpressionCstNode[];
twiceExpression?: TwiceExpressionCstNode[];
atomicExpression?: AtomicExpressionCstNode[];
};
export interface AtomicExpressionCstNode extends CstNode {
name: "atomicExpression";
children: AtomicExpressionCstChildren;
}
export type AtomicExpressionCstChildren = {
Character?: IToken[];
FirstPerson?: IToken[];
SecondPerson?: IToken[];
reflexive?: ReflexiveCstNode[];
constant?: ConstantCstNode[];
};
export interface SumExpressionCstNode extends CstNode {
name: "sumExpression";
children: SumExpressionCstChildren;
}
export type SumExpressionCstChildren = {
The: IToken[];
Sum: IToken[];
Of: IToken[];
lhs: ExpressionCstNode[];
And: IToken[];
rhs: ExpressionCstNode[];
};
export interface DifferenceExpressionCstNode extends CstNode {
name: "differenceExpression";
children: DifferenceExpressionCstChildren;
}
export type DifferenceExpressionCstChildren = {
The: IToken[];
Difference: IToken[];
Between: IToken[];
lhs: ExpressionCstNode[];
And: IToken[];
rhs: ExpressionCstNode[];
};
export interface ProductExpressionCstNode extends CstNode {
name: "productExpression";
children: ProductExpressionCstChildren;
}
export type ProductExpressionCstChildren = {
The: IToken[];
Product: IToken[];
Of: IToken[];
lhs: ExpressionCstNode[];
And: IToken[];
rhs: ExpressionCstNode[];
};
export interface QuotientExpressionCstNode extends CstNode {
name: "quotientExpression";
children: QuotientExpressionCstChildren;
}
export type QuotientExpressionCstChildren = {
The: IToken[];
Quotient: IToken[];
Between: IToken[];
lhs: ExpressionCstNode[];
And: IToken[];
rhs: ExpressionCstNode[];
};
export interface RemainderExpressionCstNode extends CstNode {
name: "remainderExpression";
children: RemainderExpressionCstChildren;
}
export type RemainderExpressionCstChildren = {
The: IToken[];
Remainder: IToken[];
Of: IToken[];
Quotient: IToken[];
Between: IToken[];
lhs: ExpressionCstNode[];
And: IToken[];
rhs: ExpressionCstNode[];
};
export interface FactorialExpressionCstNode extends CstNode {
name: "factorialExpression";
children: FactorialExpressionCstChildren;
}
export type FactorialExpressionCstChildren = {
The: IToken[];
Factorial: IToken[];
Of: IToken[];
expression: ExpressionCstNode[];
};
export interface SquareExpressionCstNode extends CstNode {
name: "squareExpression";
children: SquareExpressionCstChildren;
}
export type SquareExpressionCstChildren = {
The: IToken[];
Square: IToken[];
Of: IToken[];
expression: ExpressionCstNode[];
};
export interface CubeExpressionCstNode extends CstNode {
name: "cubeExpression";
children: CubeExpressionCstChildren;
}
export type CubeExpressionCstChildren = {
The: IToken[];
Cube: IToken[];
Of: IToken[];
expression: ExpressionCstNode[];
};
export interface SquareRootExpressionCstNode extends CstNode {
name: "squareRootExpression";
children: SquareRootExpressionCstChildren;
}
export type SquareRootExpressionCstChildren = {
The: IToken[];
Square: IToken[];
Root: IToken[];
Of: IToken[];
expression: ExpressionCstNode[];
};
export interface TwiceExpressionCstNode extends CstNode {
name: "twiceExpression";
children: TwiceExpressionCstChildren;
}
export type TwiceExpressionCstChildren = {
Twice: IToken[];
expression: ExpressionCstNode[];
};
export interface ICstNodeVisitor<IN, OUT> extends ICstVisitor<IN, OUT> {
program(children: ProgramCstChildren, param?: IN): OUT;
programTitle(children: ProgramTitleCstChildren, param?: IN): OUT;
characterIntro(children: CharacterIntroCstChildren, param?: IN): OUT;
actHeading(children: ActHeadingCstChildren, param?: IN): OUT;
sceneHeading(children: SceneHeadingCstChildren, param?: IN): OUT;
entrance(children: EntranceCstChildren, param?: IN): OUT;
exit(children: ExitCstChildren, param?: IN): OUT;
multiExit(children: MultiExitCstChildren, param?: IN): OUT;
entryExitClause(children: EntryExitClauseCstChildren, param?: IN): OUT;
actSection(children: ActSectionCstChildren, param?: IN): OUT;
sceneSection(children: SceneSectionCstChildren, param?: IN): OUT;
sceneSectionChunk(children: SceneSectionChunkCstChildren, param?: IN): OUT;
speakerClause(children: SpeakerClauseCstChildren, param?: IN): OUT;
dialogueSet(children: DialogueSetCstChildren, param?: IN): OUT;
dialogueLine(children: DialogueLineCstChildren, param?: IN): OUT;
nonConditionalDialogueLine(children: NonConditionalDialogueLineCstChildren, param?: IN): OUT;
noun(children: NounCstChildren, param?: IN): OUT;
adjective(children: AdjectiveCstChildren, param?: IN): OUT;
possessive(children: PossessiveCstChildren, param?: IN): OUT;
reflexive(children: ReflexiveCstChildren, param?: IN): OUT;
comparative(children: ComparativeCstChildren, param?: IN): OUT;
assignment(children: AssignmentCstChildren, param?: IN): OUT;
exclaimAssignment(children: ExclaimAssignmentCstChildren, param?: IN): OUT;
arithAssignment(children: ArithAssignmentCstChildren, param?: IN): OUT;
stdin(children: StdinCstChildren, param?: IN): OUT;
stdout(children: StdoutCstChildren, param?: IN): OUT;
goto(children: GotoCstChildren, param?: IN): OUT;
stackPush(children: StackPushCstChildren, param?: IN): OUT;
stackPop(children: StackPopCstChildren, param?: IN): OUT;
question(children: QuestionCstChildren, param?: IN): OUT;
conditional(children: ConditionalCstChildren, param?: IN): OUT;
comparator(children: ComparatorCstChildren, param?: IN): OUT;
_asComparator(children: _asComparatorCstChildren, param?: IN): OUT;
_simpleComparator(children: _simpleComparatorCstChildren, param?: IN): OUT;
_moreLessComparator(children: _moreLessComparatorCstChildren, param?: IN): OUT;
constant(children: ConstantCstChildren, param?: IN): OUT;
_simpleConstant(children: _simpleConstantCstChildren, param?: IN): OUT;
_verbalConstant(children: _verbalConstantCstChildren, param?: IN): OUT;
unarticulatedVerbalConstant(children: UnarticulatedVerbalConstantCstChildren, param?: IN): OUT;
expression(children: ExpressionCstChildren, param?: IN): OUT;
atomicExpression(children: AtomicExpressionCstChildren, param?: IN): OUT;
sumExpression(children: SumExpressionCstChildren, param?: IN): OUT;
differenceExpression(children: DifferenceExpressionCstChildren, param?: IN): OUT;
productExpression(children: ProductExpressionCstChildren, param?: IN): OUT;
quotientExpression(children: QuotientExpressionCstChildren, param?: IN): OUT;
remainderExpression(children: RemainderExpressionCstChildren, param?: IN): OUT;
factorialExpression(children: FactorialExpressionCstChildren, param?: IN): OUT;
squareExpression(children: SquareExpressionCstChildren, param?: IN): OUT;
cubeExpression(children: CubeExpressionCstChildren, param?: IN): OUT;
squareRootExpression(children: SquareRootExpressionCstChildren, param?: IN): OUT;
twiceExpression(children: TwiceExpressionCstChildren, param?: IN): OUT;
}

View File

@ -0,0 +1,18 @@
import { writeFileSync } from "fs";
import { resolve } from "path";
import { generateCstDts } from "chevrotain";
import { ShakespeareParser } from "./parser";
/**
* This script generates CST types for the Shakespeare parser.
* To run: `yarn ts-node $(pwd)/generate-cst-types.ts` in this directory.
*
* The `$(pwd)` makes the path absolute. Due to some reason, relative paths with ts-node
* aren't working on my side.
*/
const productions = new ShakespeareParser().getGAstProductions();
const dtsString = generateCstDts(productions);
const dtsPath = resolve(__dirname, "./cst.d.ts");
writeFileSync(dtsPath, dtsString);

View File

@ -0,0 +1,54 @@
import { CstNode, IToken, Lexer } from "chevrotain";
import { DocumentRange } from "../../types";
import { ParseError } from "../../worker-errors";
import { ShakespeareParser } from "./parser";
import { AllTokens } from "./tokens";
import { ShakespeareVisitor } from "./visitor";
import { Program } from "./visitor-types";
export class Parser {
private readonly _lexer: Lexer = new Lexer(AllTokens);
private readonly _parser: ShakespeareParser = new ShakespeareParser();
private readonly _visitor: ShakespeareVisitor = new ShakespeareVisitor();
public parse(text: string): Program {
const tokens = this.runLexer(text);
const cst = this.runParser(tokens);
return this.runVisitor(cst);
}
private runLexer(text: string): IToken[] {
const { tokens, errors } = this._lexer.tokenize(text);
if (errors.length > 0) {
const error = errors[0];
throw new ParseError(error.message, {
startLine: error.line ? error.line - 1 : 0,
startCol: error.column && error.column - 1,
endCol: error.column && error.column + error.length - 1,
});
}
return tokens;
}
private runParser(tokens: IToken[]): CstNode {
this._parser.input = tokens;
const parseResult = this._parser.program();
if (this._parser.errors.length > 0) {
const error = this._parser.errors[0];
throw new ParseError(error.message, this.getRange(error.token));
}
return parseResult;
}
private runVisitor(cst: CstNode): Program {
return this._visitor.visit(cst);
}
private getRange(token: IToken): DocumentRange {
const startLine = (token.startLine || 1) - 1;
const startCol = token.startColumn && token.startColumn - 1;
const endCol = token.endColumn;
return { startLine, startCol, endCol };
}
}

View File

@ -0,0 +1,589 @@
import { CstNode, CstParser, ParserMethod } from "chevrotain";
import * as Tokens from "./tokens";
export interface RuleBook<T> {
program: T;
programTitle: T;
characterIntro: T;
actHeading: T;
sceneHeading: T;
entrance: T;
exit: T;
multiExit: T;
entryExitClause: T;
actSection: T;
sceneSection: T;
sceneSectionChunk: T;
speakerClause: T;
dialogueSet: T;
dialogueLine: T;
// ENGLISH
noun: T;
adjective: T;
possessive: T;
reflexive: T;
comparative: T;
// STATEMENTS
assignment: T;
exclaimAssignment: T;
arithAssignment: T;
stdin: T;
stdout: T;
goto: T;
question: T;
conditional: T;
comparator: T;
_asComparator: T;
_simpleComparator: T;
_moreLessComparator: T;
// CONSTANTS
constant: T;
simpleConstant: T;
verbalConstant: T;
unarticulatedVerbalConstant: T;
// EXPRESSION TREE
expression: T;
atomicExpression: T;
sumExpression: T;
differenceExpression: T;
productExpression: T;
quotientExpression: T;
remainderExpression: T;
factorialExpression: T;
squareExpression: T;
cubeExpression: T;
squareRootExpression: T;
twiceExpression: T;
}
export class ShakespeareParser
extends CstParser
implements RuleBook<ParserMethod<[], CstNode>>
{
constructor() {
super(Tokens.AllTokens, {
nodeLocationTracking: "full",
recoveryEnabled: true,
});
this.performSelfAnalysis();
}
program = this.RULE("program", () => {
this.SUBRULE(this.programTitle);
this.MANY(() => this.CONSUME(Tokens.WhitespaceOrNewline));
this.MANY1(() => this.SUBRULE(this.characterIntro));
this.MANY2(() => this.SUBRULE(this.actSection));
});
/** <description> */
programTitle = this.RULE("programTitle", () => {
this.MANY(() => this.CONSUME(Tokens.Word));
this.CONSUME(Tokens.Period);
});
/** `<character-name>, <description>` */
characterIntro = this.RULE("characterIntro", () => {
this.CONSUME(Tokens.Character);
this.CONSUME(Tokens.Comma);
this.MANY(() => this.CONSUME(Tokens.Word));
this.CONSUME(Tokens.SentenceMark);
});
/** `Act <RomanNumeral>: <description>` */
actHeading = this.RULE("actHeading", () => {
this.CONSUME(Tokens.Act);
this.CONSUME(Tokens.RomanNumeral);
this.CONSUME(Tokens.Colon);
this.MANY(() => this.CONSUME(Tokens.Word));
this.CONSUME(Tokens.SentenceMark);
});
/** `Scene <RomanNumeral>: <description>` */
sceneHeading = this.RULE("sceneHeading", () => {
this.CONSUME(Tokens.Scene);
this.CONSUME(Tokens.RomanNumeral);
this.CONSUME(Tokens.Colon);
this.MANY(() => this.CONSUME(Tokens.Word));
this.CONSUME(Tokens.SentenceMark);
});
/** `[Enter <character-name> (and <character-name>)]` */
entrance = this.RULE("entrance", () => {
this.CONSUME(Tokens.SquareBracketOpen);
this.CONSUME(Tokens.Enter);
this.CONSUME1(Tokens.Character);
this.OPTION(() => {
this.CONSUME(Tokens.And);
this.CONSUME2(Tokens.Character);
});
this.CONSUME(Tokens.SquareBracketClose);
});
/** `[Exit <character-name>]` */
exit = this.RULE("exit", () => {
this.CONSUME(Tokens.SquareBracketOpen);
this.CONSUME(Tokens.Exit);
this.CONSUME(Tokens.Character);
this.CONSUME(Tokens.SquareBracketClose);
});
/** `[Exeunt (<character-name> and <character-name>)]` */
multiExit = this.RULE("multiExit", () => {
this.CONSUME(Tokens.SquareBracketOpen);
this.CONSUME(Tokens.Exeunt);
this.OPTION(() => {
this.CONSUME1(Tokens.Character);
this.CONSUME(Tokens.And);
this.CONSUME2(Tokens.Character);
});
this.CONSUME(Tokens.SquareBracketClose);
});
/** Clause for entry or exit of characters */
entryExitClause = this.RULE("entryExitClause", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.entrance) },
{ ALT: () => this.SUBRULE(this.exit) },
{ ALT: () => this.SUBRULE(this.multiExit) },
]);
});
/** Text corresponding to a single act */
actSection = this.RULE("actSection", () => {
this.SUBRULE(this.actHeading);
this.AT_LEAST_ONE(() => this.SUBRULE(this.sceneSection));
});
/** Text corresponding to a single scene */
sceneSection = this.RULE("sceneSection", () => {
this.SUBRULE(this.sceneHeading);
this.AT_LEAST_ONE(() => this.SUBRULE(this.sceneSectionChunk));
});
/** A single item of a scene: dialogue set or entry-exit clause */
sceneSectionChunk = this.RULE("sceneSectionChunk", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.entryExitClause) },
{ ALT: () => this.SUBRULE(this.dialogueSet) },
]);
});
/** `<character-name>:` */
speakerClause = this.RULE("speakerClause", () => {
this.CONSUME(Tokens.Character);
this.CONSUME(Tokens.Colon);
});
/** Set of dialogues spoken by a character */
dialogueSet = this.RULE("dialogueSet", () => {
this.SUBRULE(this.speakerClause);
this.AT_LEAST_ONE(() => this.SUBRULE(this.dialogueLine));
});
/** A single line of dialogue spoken by a character */
dialogueLine = this.RULE("dialogueLine", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.conditional) },
{ ALT: () => this.SUBRULE(this.nonConditionalDialogueLine) },
]);
});
/** Dialogue line possibilities, excluding conditionals */
nonConditionalDialogueLine = this.RULE("nonConditionalDialogueLine", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.assignment) },
{ ALT: () => this.SUBRULE(this.stdin) },
{ ALT: () => this.SUBRULE(this.stdout) },
{ ALT: () => this.SUBRULE(this.goto) },
{ ALT: () => this.SUBRULE(this.stackPush) },
{ ALT: () => this.SUBRULE(this.stackPop) },
{ ALT: () => this.SUBRULE(this.question) },
]);
});
///////////////////////////////
////////// ENGLISH //////////
///////////////////////////////
/** Shakespearean noun */
noun = this.RULE("noun", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.NegativeNoun) },
{ ALT: () => this.CONSUME(Tokens.NeutralNoun) },
{ ALT: () => this.CONSUME(Tokens.PositiveNoun) },
]);
});
/** Shakesperean adjective */
adjective = this.RULE("adjective", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.NegativeAdjective) },
{ ALT: () => this.CONSUME(Tokens.NeutralAdjective) },
{ ALT: () => this.CONSUME(Tokens.PositiveAdjective) },
]);
});
/** Any recognized possessive (my, your, his, her, etc.) */
possessive = this.RULE("possessive", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.FirstPersonPossessive) },
{ ALT: () => this.CONSUME(Tokens.SecondPersonPossessive) },
{ ALT: () => this.CONSUME(Tokens.ThirdPersonPossessive) },
]);
});
/** Any recognized reflexive (myself, thyself, etc.) */
reflexive = this.RULE("reflexive", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.FirstPersonReflexive) },
{ ALT: () => this.CONSUME(Tokens.SecondPersonReflexive) },
]);
});
/** Any recognized comparative (better, punier, etc) */
comparative = this.RULE("comparative", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.PositiveComparative) },
{ ALT: () => this.CONSUME(Tokens.NegativeComparative) },
]);
});
///////////////////////////////
///////// STATEMENTS ////////
///////////////////////////////
/** Assignment of an expression to a character */
assignment = this.RULE("assignment", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.exclaimAssignment) },
{ ALT: () => this.SUBRULE(this.arithAssignment) },
]);
});
/** `<second-person> <unarticulated-constant>` */
exclaimAssignment = this.RULE("exclaimAssignment", () => {
this.CONSUME(Tokens.SecondPerson);
this.SUBRULE(this.unarticulatedVerbalConstant);
this.CONSUME(Tokens.SentenceMark);
});
/** `<second-person> <be> as <adjective> as <expression>` */
arithAssignment = this.RULE("arithAssignment", () => {
this.CONSUME(Tokens.SecondPerson);
this.CONSUME(Tokens.Be);
this.OPTION(() => {
this.CONSUME1(Tokens.As);
this.SUBRULE(this.adjective);
this.CONSUME2(Tokens.As);
});
this.SUBRULE(this.expression);
this.CONSUME(Tokens.SentenceMark);
});
/** `Listen to your heart | Open your mind` */
stdin = this.RULE("stdin", () => {
this.OR([
{
ALT: () => {
this.CONSUME(Tokens.Listen);
this.CONSUME(Tokens.To);
this.CONSUME1(Tokens.SecondPersonPossessive);
this.CONSUME(Tokens.Heart);
},
},
{
ALT: () => {
this.CONSUME(Tokens.Open);
this.CONSUME2(Tokens.SecondPersonPossessive);
this.CONSUME(Tokens.Mind);
},
},
]);
this.CONSUME(Tokens.SentenceMark);
});
/** `Open <your> heart | Speak <your> mind` */
stdout = this.RULE("stdout", () => {
this.OR([
{
ALT: () => {
this.CONSUME(Tokens.Open);
this.CONSUME1(Tokens.SecondPersonPossessive);
this.CONSUME(Tokens.Heart);
},
},
{
ALT: () => {
this.CONSUME(Tokens.Speak);
this.CONSUME2(Tokens.SecondPersonPossessive);
this.CONSUME(Tokens.Mind);
},
},
]);
this.CONSUME(Tokens.SentenceMark);
});
/** (let us|we shall|we must) (return|proceed) to (act|scene) <roman-numeral> */
goto = this.RULE("goto", () => {
this.OR1([
{
ALT: () => {
this.CONSUME(Tokens.Let);
this.CONSUME(Tokens.Us);
},
},
{
ALT: () => {
this.CONSUME1(Tokens.We);
this.CONSUME(Tokens.Shall);
},
},
{
ALT: () => {
this.CONSUME2(Tokens.We);
this.CONSUME(Tokens.Must);
},
},
]);
this.OR2([
{ ALT: () => this.CONSUME(Tokens.Return) },
{ ALT: () => this.CONSUME(Tokens.Proceed) },
]);
this.CONSUME(Tokens.To);
this.OR3([
{ ALT: () => this.CONSUME(Tokens.Act) },
{ ALT: () => this.CONSUME(Tokens.Scene) },
]);
this.CONSUME(Tokens.RomanNumeral);
this.CONSUME(Tokens.SentenceMark);
});
/** `Remember <expression>` */
stackPush = this.RULE("stackPush", () => {
this.CONSUME(Tokens.Remember);
this.SUBRULE(this.expression);
this.CONSUME(Tokens.SentenceMark);
});
/** `Recall <word>*` */
stackPop = this.RULE("stackPop", () => {
this.CONSUME(Tokens.Recall);
this.MANY(() => this.CONSUME(Tokens.Word));
this.CONSUME(Tokens.SentenceMark);
});
/** `<be> <expression> <comparator> <expression>?` */
question = this.RULE("question", () => {
this.CONSUME(Tokens.Be);
this.SUBRULE1(this.expression, { LABEL: "lhs" });
this.SUBRULE(this.comparator);
this.SUBRULE2(this.expression, { LABEL: "rhs" });
this.CONSUME(Tokens.QuestionMark);
});
/** `If so, <non-conditional-dialogue-line>` */
conditional = this.RULE("conditional", () => {
this.CONSUME(Tokens.If);
this.OR([
{ ALT: () => this.CONSUME(Tokens.So) },
{ ALT: () => this.CONSUME(Tokens.Not) },
]);
this.CONSUME(Tokens.Comma);
this.SUBRULE(this.nonConditionalDialogueLine);
});
/** Comparator clause used in questions */
comparator = this.RULE("comparator", () => {
this.OPTION(() => this.CONSUME(Tokens.Not));
this.OR([
{ ALT: () => this.SUBRULE(this._asComparator) },
{ ALT: () => this.SUBRULE(this._simpleComparator) },
{ ALT: () => this.SUBRULE(this._moreLessComparator) },
]);
});
/** `as <adjective> as` */
_asComparator = this.RULE("_asComparator", () => {
this.CONSUME1(Tokens.As);
this.SUBRULE(this.adjective);
this.CONSUME2(Tokens.As);
});
/** `<comparative> than` */
_simpleComparator = this.RULE("_simpleComparator", () => {
this.SUBRULE(this.comparative);
this.CONSUME(Tokens.Than);
});
/** `(more|less) <adjective> than` */
_moreLessComparator = this.RULE("_moreLessComparator", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.More) },
{ ALT: () => this.CONSUME(Tokens.Less) },
]);
this.SUBRULE(this.adjective);
this.CONSUME(Tokens.Than);
});
///////////////////////////////
///////// CONSTANTS /////////
///////////////////////////////
/** Constant expressions */
constant = this.RULE("constant", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.simpleConstant) },
{ ALT: () => this.SUBRULE(this.verbalConstant) },
]);
});
/** Simple keyword-based constant */
simpleConstant = this.RULE("_simpleConstant", () => {
this.CONSUME(Tokens.Nothing);
});
/** Verbally expressed constant */
verbalConstant = this.RULE("_verbalConstant", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.Article) },
{ ALT: () => this.SUBRULE(this.possessive) },
]);
this.SUBRULE(this.unarticulatedVerbalConstant);
});
/** `<adjective>* <noun>`, representing a constant */
unarticulatedVerbalConstant = this.RULE("unarticulatedVerbalConstant", () => {
// Shakespeare only allows non-negative adjectives on positive nouns and
// negative adjectives on negative nouns.
//
// Unfortunately, positive and negative branches cannot be separated in this
// parser since they share an arbitrarily long prefix of neutral adjectives.
// Thus, this branch validation must be done in the CST visitor.
this.MANY(() => this.SUBRULE(this.adjective));
this.SUBRULE(this.noun);
});
///////////////////////////////
////// EXPRESSION TREE //////
///////////////////////////////
/** Root node of an expression tree */
expression = this.RULE("expression", () => {
this.OR([
{ ALT: () => this.SUBRULE(this.sumExpression) },
{ ALT: () => this.SUBRULE(this.differenceExpression) },
{ ALT: () => this.SUBRULE(this.productExpression) },
{ ALT: () => this.SUBRULE(this.quotientExpression) },
{ ALT: () => this.SUBRULE(this.remainderExpression) },
{ ALT: () => this.SUBRULE(this.factorialExpression) },
{ ALT: () => this.SUBRULE(this.squareExpression) },
{ ALT: () => this.SUBRULE(this.squareRootExpression) },
{ ALT: () => this.SUBRULE(this.cubeExpression) },
{ ALT: () => this.SUBRULE(this.twiceExpression) },
{ ALT: () => this.SUBRULE(this.atomicExpression) },
]);
});
/** `<character> | <reflexive> | <constant>` */
atomicExpression = this.RULE("atomicExpression", () => {
this.OR([
{ ALT: () => this.CONSUME(Tokens.Character) },
{ ALT: () => this.CONSUME(Tokens.FirstPerson) },
{ ALT: () => this.CONSUME(Tokens.SecondPerson) },
{ ALT: () => this.SUBRULE(this.reflexive) },
{ ALT: () => this.SUBRULE(this.constant) },
]);
});
/** `the sum of <expression> and <expression>` */
sumExpression = this.RULE("sumExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Sum);
this.CONSUME(Tokens.Of);
this.SUBRULE1(this.expression, { LABEL: "lhs" });
this.CONSUME(Tokens.And);
this.SUBRULE2(this.expression, { LABEL: "rhs" });
});
/** `the difference between <expression> and <expression>` */
differenceExpression = this.RULE("differenceExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Difference);
this.CONSUME(Tokens.Between);
this.SUBRULE1(this.expression, { LABEL: "lhs" });
this.CONSUME(Tokens.And);
this.SUBRULE2(this.expression, { LABEL: "rhs" });
});
/** `the product of <expression> and <expression>` */
productExpression = this.RULE("productExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Product);
this.CONSUME(Tokens.Of);
this.SUBRULE1(this.expression, { LABEL: "lhs" });
this.CONSUME(Tokens.And);
this.SUBRULE2(this.expression, { LABEL: "rhs" });
});
/** `the quotient between <expression> and <expression>` */
quotientExpression = this.RULE("quotientExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Quotient);
this.CONSUME(Tokens.Between);
this.SUBRULE1(this.expression, { LABEL: "lhs" });
this.CONSUME(Tokens.And);
this.SUBRULE2(this.expression, { LABEL: "rhs" });
});
/** `the remainder of the quotient between <expression> and <expression>` */
remainderExpression = this.RULE("remainderExpression", () => {
this.CONSUME1(Tokens.The);
this.CONSUME(Tokens.Remainder);
this.CONSUME(Tokens.Of);
this.CONSUME2(Tokens.The);
this.CONSUME(Tokens.Quotient);
this.CONSUME(Tokens.Between);
this.SUBRULE1(this.expression, { LABEL: "lhs" });
this.CONSUME(Tokens.And);
this.SUBRULE2(this.expression, { LABEL: "rhs" });
});
/** `the factorial of <expression>` */
factorialExpression = this.RULE("factorialExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Factorial);
this.CONSUME(Tokens.Of);
this.SUBRULE(this.expression);
});
/** `the square of <expression>` */
squareExpression = this.RULE("squareExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Square);
this.CONSUME(Tokens.Of);
this.SUBRULE(this.expression);
});
/** `the cube of <expression>` */
cubeExpression = this.RULE("cubeExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Cube);
this.CONSUME(Tokens.Of);
this.SUBRULE(this.expression);
});
/** `the square root of <expression>` */
squareRootExpression = this.RULE("squareRootExpression", () => {
this.CONSUME(Tokens.The);
this.CONSUME(Tokens.Square);
this.CONSUME(Tokens.Root);
this.CONSUME(Tokens.Of);
this.SUBRULE(this.expression);
});
/** `twice <expression>` */
twiceExpression = this.RULE("twiceExpression", () => {
this.CONSUME(Tokens.Twice);
this.SUBRULE(this.expression);
});
}

View File

@ -0,0 +1,509 @@
import { createToken, Lexer } from "chevrotain";
import * as R from "./constants";
export const Word = createToken({
name: "Word",
pattern: /[^\s\n\.\?\!]+/,
});
export const WhitespaceOrNewline = createToken({
name: "WhitespaceOrNewline",
pattern: /[\s\n]+/,
group: Lexer.SKIPPED,
});
export const Whitespace = createToken({
name: "Whitespace",
pattern: /\s+/,
categories: [WhitespaceOrNewline],
group: Lexer.SKIPPED,
});
export const RomanNumeral = createToken({
name: "RomanNumeral",
pattern: /[IVXLCDM]+\b/i,
categories: [Word],
});
export const Enter = createToken({
name: "Enter",
pattern: /enter\b/i,
categories: [Word],
});
export const Exit = createToken({
name: "Exit",
pattern: /exit\b/i,
categories: [Word],
});
export const Exeunt = createToken({
name: "Exeunt",
pattern: /exeunt\b/i,
categories: [Word],
});
export const And = createToken({
name: "And",
pattern: /and\b/i,
categories: [Word],
});
export const Article = createToken({
name: "Article",
pattern: /(a|an|the)\b/i,
categories: [Word],
});
export const Be = createToken({
name: "Be",
pattern: /(am|are|art|be|is)\b/i,
categories: [Word],
});
export const Not = createToken({
name: "Not",
pattern: /not\b/i,
categories: [Word],
});
export const More = createToken({
name: "More",
pattern: /more\b/i,
categories: [Word],
});
export const Less = createToken({
name: "Less",
pattern: /less\b/i,
categories: [Word],
});
export const Than = createToken({
name: "Than",
pattern: /than\b/i,
categories: [Word],
});
export const Let = createToken({
name: "Let",
pattern: /let\b/i,
categories: [Word],
});
export const Us = createToken({
name: "Us",
pattern: /us\b/i,
categories: [Word],
});
export const We = createToken({
name: "We",
pattern: /we\b/i,
categories: [Word],
});
export const Shall = createToken({
name: "Shall",
pattern: /shall\b/i,
categories: [Word],
});
export const Must = createToken({
name: "Must",
pattern: /must\b/i,
categories: [Word],
});
export const Return = createToken({
name: "Return",
pattern: /return\b/i,
categories: [Word],
});
export const Proceed = createToken({
name: "Proceed",
pattern: /proceed\b/i,
categories: [Word],
});
export const Character = createToken({
name: "Character",
pattern: R.CharacterRegex,
categories: [Word],
});
export const FirstPersonPossessive = createToken({
name: "FirstPersonPossessive",
pattern: /(mine|my)\b/i,
categories: [Word],
});
export const FirstPersonReflexive = createToken({
name: "FirstPersonReflexive",
pattern: /myself\b/i,
categories: [Word],
});
export const FirstPerson = createToken({
name: "FirstPerson",
pattern: /(i|me)\b/i,
categories: [Word],
});
export const NegativeAdjective = createToken({
name: "NegativeAdjective",
pattern: R.NegativeAdjectiveRegex,
categories: [Word],
});
export const NegativeComparative = createToken({
name: "NegativeComparative",
pattern: /(punier|smaller|worse)\b/i,
categories: [Word],
});
export const NegativeNoun = createToken({
name: "NegativeNoun",
pattern: R.NegativeNounRegex,
categories: [Word],
});
export const NeutralAdjective = createToken({
name: "NeutralAdjective",
pattern: R.NeutralAdjectiveRegex,
categories: [Word],
});
export const NeutralNoun = createToken({
name: "NeutralNoun",
pattern: R.NeutralNounRegex,
categories: [Word],
});
export const Nothing = createToken({
name: "Nothing",
pattern: /(nothing|zero)\b/i,
categories: [Word],
});
export const PositiveAdjective = createToken({
name: "PositiveAdjective",
pattern: R.PositiveAdjectiveRegex,
categories: [Word],
});
export const PositiveComparative = createToken({
name: "PositiveComparative",
pattern: /(better|bigger|fresher|friendlier|nicer|jollier)\b/i,
categories: [Word],
});
export const PositiveNoun = createToken({
name: "PositiveNoun",
pattern: R.PositiveNounRegex,
categories: [Word],
});
export const SecondPersonPossessive = createToken({
name: "SecondPersonPossessive",
pattern: /(thine|thy|your)\b/i,
categories: [Word],
});
export const SecondPersonReflexive = createToken({
name: "SecondPersonReflexive",
pattern: /(thyself|yourself)\b/i,
categories: [Word],
});
export const SecondPerson = createToken({
name: "SecondPerson",
pattern: /(thee|thou|you)\b/i,
categories: [Word],
});
export const ThirdPersonPossessive = createToken({
name: "ThirdPersonPossessive",
pattern: /(his|her|its|their)\b/i,
categories: [Word],
});
export const Act = createToken({
name: "Act",
pattern: /act\b/i,
categories: [Word],
});
export const Scene = createToken({
name: "Scene",
pattern: /scene\b/i,
categories: [Word],
});
export const As = createToken({
name: "As",
pattern: /as\b/i,
categories: [Word],
});
export const Open = createToken({
name: "Open",
pattern: /open\b/i,
categories: [Word],
});
export const Heart = createToken({
name: "Heart",
pattern: /heart\b/i,
categories: [Word],
});
export const Speak = createToken({
name: "Speak",
pattern: /speak\b/i,
categories: [Word],
});
export const Mind = createToken({
name: "Mind",
pattern: /mind\b/i,
categories: [Word],
});
export const The = createToken({
name: "The",
pattern: /the\b/i,
categories: [Word, Article],
});
export const Between = createToken({
name: "Between",
pattern: /between\b/i,
categories: [Word],
});
export const Of = createToken({
name: "Of",
pattern: /of\b/i,
categories: [Word],
});
export const If = createToken({
name: "If",
pattern: /if\b/i,
categories: [Word],
});
export const So = createToken({
name: "So",
pattern: /so\b/i,
categories: [Word],
});
export const To = createToken({
name: "To",
pattern: /to\b/i,
categories: [Word],
});
export const Listen = createToken({
name: "Listen",
pattern: /listen\b/i,
categories: [Word],
});
export const Recall = createToken({
name: "Recall",
pattern: /recall\b/i,
categories: [Word],
});
export const Remember = createToken({
name: "Remember",
pattern: /remember\b/i,
categories: [Word],
});
////////////////////////////////
/////////// OPERATORS //////////
////////////////////////////////
export const Sum = createToken({
name: "Sum",
pattern: /sum\b/,
categories: [Word],
});
export const Difference = createToken({
name: "Difference",
pattern: /difference\b/,
categories: [Word],
});
export const Product = createToken({
name: "Product",
pattern: /product\b/i,
categories: [Word],
});
export const Quotient = createToken({
name: "Quotient",
pattern: /quotient\b/i,
categories: [Word],
});
export const Remainder = createToken({
name: "Remainder",
pattern: /remainder\b/i,
categories: [Word],
});
export const Factorial = createToken({
name: "Factorial",
pattern: /factorial\b/i,
categories: [Word],
});
export const Square = createToken({
name: "Square",
pattern: /square\b/i,
categories: [Word],
});
export const Root = createToken({
name: "Root",
pattern: /root\b/i,
categories: [Word],
});
export const Cube = createToken({
name: "Cube",
pattern: /cube\b/i,
categories: [Word],
});
export const Twice = createToken({
name: "Twice",
pattern: /twice\b/i,
categories: [Word],
});
export const SentenceMark = createToken({
name: "SentenceMark",
pattern: /\.|\?|\!/,
});
export const SquareBracketOpen = createToken({
name: "SquareBracketOpen",
pattern: /\[/,
});
export const SquareBracketClose = createToken({
name: "SquareBracketClose",
pattern: /\]/,
});
export const Colon = createToken({
name: "Colon",
pattern: /:/,
});
export const Comma = createToken({
name: "Comma",
pattern: /,/,
categories: [Word],
});
export const Period = createToken({
name: "Period",
pattern: /\./,
categories: [SentenceMark],
});
export const QuestionMark = createToken({
name: "QuestionMark",
pattern: /\?/,
categories: [SentenceMark],
});
export const I = createToken({
name: "I",
pattern: /i\b/i,
categories: [RomanNumeral, FirstPerson, Word],
});
export const AllTokens = [
Whitespace,
WhitespaceOrNewline,
Character,
The,
Be,
More,
Less,
Than,
Let,
Us,
We,
Shall,
Must,
Return,
Proceed,
Not,
As,
I,
If,
So,
To,
Enter,
Exit,
Exeunt,
Open,
Heart,
Speak,
Mind,
Listen,
Recall,
Remember,
And,
Nothing,
Between,
Of,
Sum,
Difference,
Product,
Quotient,
Remainder,
Factorial,
Square,
Root,
Cube,
Twice,
Period,
QuestionMark,
Article,
Act,
Scene,
Comma,
Colon,
SquareBracketOpen,
SquareBracketClose,
SentenceMark,
FirstPersonPossessive,
FirstPersonReflexive,
FirstPerson,
NegativeAdjective,
NegativeComparative,
NegativeNoun,
NeutralAdjective,
NeutralNoun,
PositiveAdjective,
PositiveComparative,
PositiveNoun,
SecondPersonPossessive,
SecondPersonReflexive,
SecondPerson,
ThirdPersonPossessive,
RomanNumeral,
Word,
];

View File

@ -0,0 +1,323 @@
import { DocumentRange } from "../../../types";
import * as C from "./cst";
/** Type that contains information about what AST the visitor returns for each rule */
export type ShakespeareVisitorTypes = {
program(children: C.ProgramCstChildren, param?: any): Program;
programTitle(children: C.ProgramTitleCstChildren, param?: any): null;
characterIntro(
children: C.CharacterIntroCstChildren,
param?: any
): CharacterIntro;
actHeading(children: C.ActHeadingCstChildren, param?: any): ActHeading;
sceneHeading(children: C.SceneHeadingCstChildren, param?: any): SceneHeading;
entrance(children: C.EntranceCstChildren, param?: any): EntryExitClause;
exit(children: C.ExitCstChildren, param?: any): EntryExitClause;
multiExit(children: C.MultiExitCstChildren, param?: any): EntryExitClause;
entryExitClause(
children: C.EntryExitClauseCstChildren,
param?: any
): EntryExitClause;
actSection(children: C.ActSectionCstChildren, param?: any): ActSection;
sceneSection(children: C.SceneSectionCstChildren, param?: any): SceneSection;
sceneSectionChunk(
children: C.SceneSectionChunkCstChildren,
param?: any
): SceneSectionChunk;
speakerClause(
children: C.SpeakerClauseCstChildren,
param?: any
): SpeakerClause;
dialogueSet(children: C.DialogueSetCstChildren, param?: any): DialogueSet;
dialogueLine(children: C.DialogueCstChildren, param?: any): DialogueLine;
nonConditionalDialogueLine(
children: C.NonConditionalDialogueLineCstChildren,
param?: any
): NonConditionalDialogueLine;
/////////// ENGLISH ///////////
noun(children: C.NounCstChildren, param?: any): 1 | 0 | -1;
adjective(children: C.AdjectiveCstChildren, param?: any): 1 | 0 | -1;
possessive(
children: C.PossessiveCstChildren,
param?: any
): "first" | "second" | "third";
reflexive(children: C.ReflexiveCstChildren, param?: any): "first" | "second";
comparative(children: C.ComparativeCstChildren, param?: any): 1 | -1;
/////////// STATEMENTS ///////////
assignment(children: C.AssignmentCstChildren, param?: any): AssignmentLine;
exclaimAssignment(
children: C.ExclaimAssignmentCstChildren,
param?: any
): AssignmentLine;
arithAssignment(
children: C.ArithAssignmentCstChildren,
param?: any
): AssignmentLine;
stdin(children: C.StdinCstChildren, param?: any): StdinLine;
stdout(children: C.StdoutCstChildren, param?: any): StdoutLine;
goto(children: C.GotoCstChildren, param?: any): GotoLine;
stackPush(children: C.StackPushCstChildren, param?: any): StackPushLine;
stackPop(children: C.StackPopCstChildren, param?: any): StackPopLine;
question(children: C.QuestionCstChildren, param?: any): QuestionLine;
conditional(children: C.ConditionalCstChildren, param?: any): ConditionalLine;
comparator(
children: C.ComparatorCstChildren,
param?: any
): ComparisonOperator;
_asComparator(
children: C._asComparatorCstChildren,
param?: any
): ComparisonOperator;
_simpleComparator(
children: C._simpleComparatorCstChildren,
param?: any
): ComparisonOperator;
_moreLessComparator(
children: C._moreLessComparatorCstChildren,
param?: any
): ComparisonOperator;
////////////// CONSTANTS //////////////
constant(children: C.ConstantCstChildren, param?: any): Constant;
_simpleConstant(
children: C._simpleConstantCstChildren,
param?: any
): Constant;
_verbalConstant(
children: C._verbalConstantCstChildren,
param?: any
): Constant;
unarticulatedVerbalConstant(
children: C.UnarticulatedVerbalConstantCstChildren,
param?: any
): Constant;
/////////// EXPRESSION TREE ///////////
expression(children: C.ExpressionCstChildren, param?: any): Expression;
atomicExpression(
children: C.AtomicExpressionCstChildren,
param?: any
): AtomicExpression;
sumExpression(
children: C.SumExpressionCstChildren,
param?: any
): BinaryExpression;
differenceExpression(
children: C.DifferenceExpressionCstChildren,
param?: any
): BinaryExpression;
productExpression(
children: C.ProductExpressionCstChildren,
param?: any
): BinaryExpression;
quotientExpression(
children: C.QuotientExpressionCstChildren,
param?: any
): BinaryExpression;
remainderExpression(
children: C.RemainderExpressionCstChildren,
param?: any
): BinaryExpression;
factorialExpression(
children: C.FactorialExpressionCstChildren,
param?: any
): UnaryExpression;
squareExpression(
children: C.SquareExpressionCstChildren,
param?: any
): UnaryExpression;
cubeExpression(
children: C.CubeExpressionCstChildren,
param?: any
): UnaryExpression;
squareRootExpression(
children: C.SquareRootExpressionCstChildren,
param?: any
): UnaryExpression;
twiceExpression(
children: C.TwiceExpressionCstChildren,
param?: any
): UnaryExpression;
};
/** We don't parse roman numerals and instead just treat them as string IDs */
export type RomanNumeral = string;
/** AST for a complete Shakespeare program */
export type Program = {
characters: CharacterIntro[];
acts: ActSection[];
};
/** Character declaration at the start of a program */
export type CharacterIntro = Character;
/** Heading of an act - only the act number is kept */
export type ActHeading = RomanNumeral;
/** Heading of a scene - only the scene number is kept */
export type SceneHeading = RomanNumeral;
/** Clause for entry or exit of one or more characters */
export type EntryExitClause = {
type: "entry" | "exit";
/**
* List of characters entering or leaving.
* `null` is used for `[Exeunt]` clauses.
*/
characters: Character[] | null;
range: DocumentRange;
};
/** Details of a single act */
export type ActSection = {
id: ActHeading;
scenes: SceneSection[];
};
/** Details of a single scene */
export type SceneSection = {
id: SceneHeading;
items: SceneSectionItem[];
};
/** An execution atom of a single scene */
export type SceneSectionItem = EntryExitClause | DialogueItem;
/** A dialogue set or entry-exit clause belonging to a scene */
export type SceneSectionChunk = EntryExitClause | DialogueSet;
/** `<character-name>:` clause that starts a dialogue set */
export type SpeakerClause = Character;
/** Set of dialogues spoken by a character */
export type DialogueSet = {
type: "dialogue-set";
speaker: SpeakerClause;
lines: DialogueLine[];
};
/** A single dialogue item containing speaker and line */
export type DialogueItem = {
type: "dialogue-item";
speaker: Character;
line: DialogueLine;
};
/** Single dialogue line spoken by the current character */
export type DialogueLine = NonConditionalDialogueLine | ConditionalLine;
/** Dialogue line excluding conditional */
export type NonConditionalDialogueLine =
| AssignmentLine
| StdinLine
| StdoutLine
| GotoLine
| StackPushLine
| StackPopLine
| QuestionLine;
/** Dialogue line representing an assignment operation */
export type AssignmentLine = {
type: "assign";
value: Expression;
range: DocumentRange;
};
/** Dialogue line representing an STDOUT operation */
export type StdoutLine = {
type: "stdout";
outType: "num" | "char";
range: DocumentRange;
};
/** Dialogue line representing an STDIN operation */
export type StdinLine = {
type: "stdin";
inType: "num" | "char";
range: DocumentRange;
};
/** Dialogue line representing a goto operation */
export type GotoLine = {
type: "goto";
targetType: "act" | "scene";
target: RomanNumeral;
range: DocumentRange;
};
/** Dialogue line representing a stack push */
export type StackPushLine = {
type: "stack-push";
expr: Expression;
range: DocumentRange;
};
/** Dialogue line representing a stack pop */
export type StackPopLine = {
type: "stack-pop";
range: DocumentRange;
};
/** Dialogue line representing a question */
export type QuestionLine = {
type: "question";
comparator: ComparisonOperator;
lhs: Expression;
rhs: Expression;
range: DocumentRange;
};
/** Dialogue line representing a conditional (`If so/not, ...`) */
export type ConditionalLine = {
type: "conditional";
invert: boolean;
consequent: NonConditionalDialogueLine;
range: DocumentRange;
};
/** Comparison operator used in a question */
export type ComparisonOperator = {
invert?: boolean;
type: "==" | ">" | "<";
};
/** Verbal or simple constants evaluate to number */
export type Constant = {
type: "constant";
value: number;
};
/** Name of a character */
export type Character = {
type: "character";
name: string;
};
/** Reference to a character with pronoun/reflexive */
export type CharacterRef = {
type: "characterRef";
ref: "first" | "second";
};
/** Leaf of an expression tree */
export type AtomicExpression = Constant | Character | CharacterRef;
/** Expression with binary operator */
export type BinaryExpression = {
type: "binary";
opType: "+" | "-" | "*" | "/" | "%";
lhs: Expression;
rhs: Expression;
};
/** Expression with unary operator */
export type UnaryExpression = {
type: "unary";
opType: "!" | "sq" | "cube" | "sqrt" | "twice";
operand: Expression;
};
/** Root of an expression tree or subtree */
export type Expression = AtomicExpression | BinaryExpression | UnaryExpression;

View File

@ -0,0 +1,617 @@
import { CstNode, CstNodeLocation } from "chevrotain";
import * as C from "./cst";
import * as V from "./visitor-types";
import { ShakespeareParser } from "./parser";
import { ParseError } from "../../worker-errors";
import { DocumentRange } from "../../types";
const parserInstance = new ShakespeareParser();
const BaseShakespeareVisitor = parserInstance.getBaseCstVisitorConstructor();
export class ShakespeareVisitor
extends BaseShakespeareVisitor
implements V.ShakespeareVisitorTypes
{
/**
* Characters of the program currently being visited. This field is populated
* in the `program` method, and then read by rule visitors for validating
* character names used in the program.
*/
private _characters: V.Character[] = [];
constructor() {
super();
// Visitor validation throws error for utility methods. There
// doesn't seem to be another way to allow private methods.
// this.validateVisitor();
}
/**
* Type-safe wrapper around Chevrotain's `visit` function.
* @param node CST node to visit
* @returns Visit result of node
*/
private visitNode<T extends CstNode>(
node: T
// @ts-ignore TS complains, but does the job anyway
): ReturnType<V.ShakespeareVisitorTypes[T["name"]]> {
return this.visit(node);
}
/**
* Convert character name in source code to uniform character name
* @param image Character name used in source code
*/
private toCharacterId(image: string): string {
return image // "the \n Ghost" ...
.split(/[\s\n]+/) // ... -> ["the", "Ghost"]
.map((s) => s[0].toUpperCase() + s.slice(1)) // ... -> ["The", "Ghost"]
.join(" "); // ... -> "The Ghost"
}
/**
* Convert a Chevrotain location object to a DocumentRange. Note
* that this assumes that the Chevrotain location object is
* fully populated with no undefined fields.
*/
private toRange(cstLocation: CstNodeLocation): DocumentRange {
const startLine = (cstLocation.startLine || 1) - 1;
const endLine = cstLocation.endLine && cstLocation.endLine - 1;
const startCol = cstLocation.startColumn && cstLocation.startColumn - 1;
const endCol = cstLocation.endColumn; // Chevrotain `endColumn` is inclusive
return { startLine, endLine, startCol, endCol };
}
/**
* Creates DocumentRange representing the range from the starting token to the
* ending token, both inclusive.
* @param start Range of the starting token
* @param end Range of the ending token
*/
private joinAndGetRange(
start: DocumentRange,
end: DocumentRange
): DocumentRange {
return {
startLine: start.startLine,
startCol: start.startCol,
endLine: end.endLine,
endCol: end.endCol,
};
}
/**
* Check if usage of a character name is valid, ie. if the name is
* declared at the top of the program.
* @param character Character to check
* @param range Location of the character usage in source code
*/
private validateCharacter(
character: V.Character,
range: DocumentRange
): void {
const charId = this.toCharacterId(character.name);
if (!this._characters.find((c) => c.name === charId)) {
throw new ParseError(
`Character '${character.name}' is not declared`,
range
);
}
}
program(children: C.ProgramCstChildren): V.Program {
if (children.characterIntro == null) children.characterIntro = [];
if (children.actSection == null) children.actSection = [];
const chars = children.characterIntro.map((c) => this.visitNode(c));
this._characters = chars; // this must run before rest of program is visited
const acts = children.actSection.map((a) => this.visitNode(a));
return { characters: chars, acts };
}
programTitle(): null {
throw new Error("Method not implemented.");
}
characterIntro(children: C.CharacterIntroCstChildren): V.CharacterIntro {
const charId = this.toCharacterId(children.Character[0].image);
return { type: "character", name: charId };
}
actHeading(children: C.ActHeadingCstChildren): V.ActHeading {
return children.RomanNumeral[0].image.toUpperCase();
}
sceneHeading(children: C.SceneHeadingCstChildren): V.SceneHeading {
return children.RomanNumeral[0].image.toUpperCase();
}
entrance(children: C.EntranceCstChildren): V.EntryExitClause {
const chars: V.Character[] = children.Character.map((c) => ({
type: "character",
name: this.toCharacterId(c.image),
}));
const range = this.joinAndGetRange(
this.toRange(children.SquareBracketOpen[0]),
this.toRange(children.SquareBracketClose[0])
);
chars.forEach((c) => this.validateCharacter(c, range));
return { type: "entry", characters: chars, range };
}
exit(children: C.ExitCstChildren): V.EntryExitClause {
const charId = this.toCharacterId(children.Character[0].image);
const range = this.joinAndGetRange(
this.toRange(children.SquareBracketOpen[0]),
this.toRange(children.SquareBracketClose[0])
);
this.validateCharacter({ type: "character", name: charId }, range);
return {
type: "exit",
characters: [{ type: "character", name: charId }],
range,
};
}
multiExit(children: C.MultiExitCstChildren): V.EntryExitClause {
const range = this.joinAndGetRange(
this.toRange(children.SquareBracketOpen[0]),
this.toRange(children.SquareBracketClose[0])
);
// `[Exeunt]`: all characters exit
if (children.Character == null)
return { type: "exit", characters: null, range };
const chars: V.Character[] = children.Character.map((c) => ({
type: "character",
name: this.toCharacterId(c.image),
}));
chars.forEach((c) => this.validateCharacter(c, range));
return { type: "exit", characters: chars, range };
}
entryExitClause(children: C.EntryExitClauseCstChildren): V.EntryExitClause {
if (children.entrance) return this.visitNode(children.entrance[0]);
else if (children.exit) return this.visitNode(children.exit[0]);
else if (children.multiExit) return this.visitNode(children.multiExit[0]);
else throw new Error("No matched subrule.");
}
actSection(children: C.ActSectionCstChildren): V.ActSection {
if (children.sceneSection == null) children.sceneSection = [];
const id = this.visitNode(children.actHeading[0]);
const scenes = children.sceneSection.map((item) => this.visitNode(item));
return { id, scenes };
}
sceneSection(children: C.SceneSectionCstChildren): V.SceneSection {
if (children.sceneSectionChunk == null) children.sceneSectionChunk = [];
const id = this.visitNode(children.sceneHeading[0]);
const items = children.sceneSectionChunk.flatMap<V.SceneSectionItem>(
(item) => {
const itemAst = this.visitNode(item);
if (itemAst.type !== "dialogue-set") return itemAst;
// Flatten the dialogue set into list of dialogue items
return itemAst.lines.map((line) => ({
type: "dialogue-item",
speaker: itemAst.speaker,
line,
}));
}
);
return { id, items };
}
sceneSectionChunk(
children: C.SceneSectionChunkCstChildren
): V.SceneSectionChunk {
if (children.dialogueSet) return this.visitNode(children.dialogueSet[0]);
else if (children.entryExitClause)
return this.visitNode(children.entryExitClause[0]);
else throw new Error("No matched subrule.");
}
speakerClause(children: C.SpeakerClauseCstChildren): V.SpeakerClause {
const charId = this.toCharacterId(children.Character[0].image);
this.validateCharacter(
{ type: "character", name: charId },
this.toRange(children.Character[0])
);
return { type: "character", name: charId };
}
dialogueSet(children: C.DialogueSetCstChildren): V.DialogueSet {
if (children.dialogueLine == null) children.dialogueLine = [];
const speaker = this.visitNode(children.speakerClause[0]);
const lines = children.dialogueLine.map((line) => this.visitNode(line));
return { type: "dialogue-set", lines, speaker };
}
dialogueLine(children: C.DialogueLineCstChildren): V.DialogueLine {
if (children.conditional) return this.visitNode(children.conditional[0]);
else if (children.nonConditionalDialogueLine)
return this.visitNode(children.nonConditionalDialogueLine[0]);
else throw new Error("No matched subrule.");
}
nonConditionalDialogueLine(
children: C.NonConditionalDialogueLineCstChildren
): V.NonConditionalDialogueLine {
if (children.assignment) return this.visitNode(children.assignment[0]);
else if (children.stdin) return this.visitNode(children.stdin[0]);
else if (children.stdout) return this.visitNode(children.stdout[0]);
else if (children.goto) return this.visitNode(children.goto[0]);
else if (children.stackPush) return this.visitNode(children.stackPush[0]);
else if (children.stackPop) return this.visitNode(children.stackPop[0]);
else if (children.question) return this.visitNode(children.question[0]);
else throw new Error("No matched subrule.");
}
///////////////////////////////
////////// ENGLISH //////////
///////////////////////////////
noun(children: C.NounCstChildren) {
if (children.NegativeNoun) return -1 as const;
else if (children.NeutralNoun) return 0 as const;
else if (children.PositiveNoun) return 1 as const;
throw new Error("Invalid token found");
}
adjective(children: C.AdjectiveCstChildren) {
if (children.NegativeAdjective) return -1 as const;
else if (children.NeutralAdjective) return 0 as const;
else if (children.PositiveAdjective) return 1 as const;
throw new Error("Invalid token found");
}
possessive(children: C.PossessiveCstChildren) {
if (children.FirstPersonPossessive) return "first" as const;
else if (children.SecondPersonPossessive) return "second" as const;
else if (children.ThirdPersonPossessive) return "third" as const;
throw new Error("Invalid token found");
}
reflexive(children: C.ReflexiveCstChildren) {
if (children.FirstPersonReflexive) return "first" as const;
else if (children.SecondPersonReflexive) return "second" as const;
throw new Error("Invalid token found");
}
comparative(children: C.ComparativeCstChildren) {
if (children.NegativeComparative) return -1 as const;
else if (children.PositiveComparative) return 1 as const;
throw new Error("Invalid token found");
}
///////////////////////////////
///////// STATEMENTS ////////
///////////////////////////////
assignment(children: C.AssignmentCstChildren): V.AssignmentLine {
if (children.exclaimAssignment != null)
return this.visitNode(children.exclaimAssignment[0]);
else if (children.arithAssignment != null)
return this.visitNode(children.arithAssignment[0]);
else throw new Error("No matched subrule");
}
exclaimAssignment(
children: C.ExclaimAssignmentCstChildren
): V.AssignmentLine {
const startRange = this.toRange(children.SecondPerson[0]);
const endRange = this.toRange(children.SentenceMark[0]);
return {
type: "assign",
value: this.visitNode(children.unarticulatedVerbalConstant[0]),
range: this.joinAndGetRange(startRange, endRange),
};
}
arithAssignment(children: C.ArithAssignmentCstChildren): V.AssignmentLine {
const startRange = this.toRange(children.SecondPerson[0]);
const endRange = this.toRange(children.SentenceMark[0]);
return {
type: "assign",
value: this.visitNode(children.expression[0]),
range: this.joinAndGetRange(startRange, endRange),
};
}
stdin(children: C.StdinCstChildren): V.StdinLine {
const startToken =
children.Open != null ? children.Open[0] : children.Listen![0];
const endRange = this.toRange(children.SentenceMark[0]);
return {
type: "stdin",
inType: children.Heart == null ? "char" : "num",
range: this.joinAndGetRange(this.toRange(startToken), endRange),
};
}
stdout(children: C.StdoutCstChildren): V.StdoutLine {
const startToken =
children.Open != null ? children.Open[0] : children.Speak![0];
const endRange = this.toRange(children.SentenceMark[0]);
return {
type: "stdout",
outType: children.Heart == null ? "char" : "num",
range: this.joinAndGetRange(this.toRange(startToken), endRange),
};
}
goto(children: C.GotoCstChildren): V.GotoLine {
const startToken = children.Let != null ? children.Let[0] : children.We![0];
const endRange = this.toRange(children.SentenceMark[0]);
return {
type: "goto",
targetType: children.Act == null ? "scene" : "act",
target: children.RomanNumeral[0].image.toUpperCase(),
range: this.joinAndGetRange(this.toRange(startToken), endRange),
};
}
stackPush(children: C.StackPushCstChildren): V.StackPushLine {
return {
type: "stack-push",
expr: this.visitNode(children.expression[0]),
range: this.joinAndGetRange(
this.toRange(children.Remember[0]),
this.toRange(children.SentenceMark[0])
),
};
}
stackPop(children: C.StackPopCstChildren): V.StackPopLine {
return {
type: "stack-pop",
range: this.joinAndGetRange(
this.toRange(children.Recall[0]),
this.toRange(children.SentenceMark[0])
),
};
}
question(children: C.QuestionCstChildren): V.QuestionLine {
const comparator = this.visitNode(children.comparator[0]);
const lhs = this.visitNode(children.lhs[0]);
const rhs = this.visitNode(children.rhs[0]);
const range = this.joinAndGetRange(
this.toRange(children.Be[0]),
this.toRange(children.QuestionMark[0])
);
return { type: "question", comparator, lhs, rhs, range };
}
conditional(children: C.ConditionalCstChildren): V.ConditionalLine {
const consequent = this.visitNode(children.nonConditionalDialogueLine[0]);
const invert = children.Not != null;
const startRange = this.toRange(children.If[0]);
const range = this.joinAndGetRange(startRange, consequent.range);
return { type: "conditional", consequent, invert, range };
}
comparator(children: C.ComparatorCstChildren): V.ComparisonOperator {
const invert = children.Not != null;
let comparator: V.ComparisonOperator = { type: "==" };
if (children._asComparator) {
comparator = this.visitNode(children._asComparator[0]);
} else if (children._simpleComparator) {
comparator = this.visitNode(children._simpleComparator[0]);
} else if (children._moreLessComparator) {
comparator = this.visitNode(children._moreLessComparator[0]);
} else throw new Error("No matched subrule.");
comparator.invert = invert;
return comparator;
}
_asComparator(): V.ComparisonOperator {
return { type: "==" };
}
_simpleComparator(
children: C._simpleComparatorCstChildren
): V.ComparisonOperator {
const comperative = this.visitNode(children.comparative[0]);
return { type: comperative === -1 ? "<" : ">" };
}
_moreLessComparator(
children: C._moreLessComparatorCstChildren
): V.ComparisonOperator {
// "<": more <negative-adj> OR less <positive-adj>
// ">": more <positive-adj> OR less <negative-adj>
const adjValue = this.visitNode(children.adjective[0]);
if (adjValue === 0)
throw new ParseError(
"Cannot use neutral adjective as a comparator",
this.toRange(children.adjective[0].location!)
);
if (children.More) return { type: adjValue === -1 ? "<" : ">" };
else if (children.Less) return { type: adjValue === -1 ? ">" : "<" };
else throw new Error("Unexpected missing token");
}
///////////////////////////////
///////// CONSTANTS /////////
///////////////////////////////
constant(children: C.ConstantCstChildren): V.Constant {
if (children._simpleConstant != null)
return this.visitNode(children._simpleConstant[0]);
else if (children._verbalConstant != null)
return this.visitNode(children._verbalConstant[0]);
throw new Error("Unexpected missing subrule");
}
_simpleConstant(): V.Constant {
return { type: "constant", value: 0 };
}
_verbalConstant(children: C._verbalConstantCstChildren): V.Constant {
return this.visitNode(children.unarticulatedVerbalConstant[0]);
}
unarticulatedVerbalConstant(
children: C.UnarticulatedVerbalConstantCstChildren
) {
if (children.adjective == null) children.adjective = [];
if (children.noun == null) throw new Error("Missing noun token");
let nounValue = this.visitNode(children.noun[0]);
if (nounValue === -1) {
// Negative noun: all adjectives must be neutral or negative
for (let i = 0; i < children.adjective.length; i++) {
let adjectiveValue = this.visitNode(children.adjective[i]);
if (adjectiveValue !== 0 && adjectiveValue !== -1)
throw new ParseError(
"Negative noun only allows negative adjectives",
this.toRange(children.adjective[i].location!)
);
}
const value = -1 * 2 ** children.adjective.length;
return { type: "constant" as const, value };
} else {
// Positive noun: all adjectives must be neutral or positive
for (let i = 0; i < children.adjective.length; i++) {
let adjectiveValue = this.visitNode(children.adjective[i]);
if (adjectiveValue !== 0 && adjectiveValue !== 1)
throw new ParseError(
"Positive noun only allows positive or neutral adjectives",
this.toRange(children.adjective[i].location!)
);
}
const value = 1 * 2 ** children.adjective.length;
return { type: "constant" as const, value };
}
}
///////////////////////////////
////// EXPRESSION TREE //////
///////////////////////////////
expression(children: C.ExpressionCstChildren): V.Expression {
if (children.atomicExpression != null)
return this.visitNode(children.atomicExpression[0]);
else if (children.sumExpression != null)
return this.visitNode(children.sumExpression[0]);
else if (children.differenceExpression != null)
return this.visitNode(children.differenceExpression[0]);
else if (children.productExpression != null)
return this.visitNode(children.productExpression[0]);
else if (children.quotientExpression != null)
return this.visitNode(children.quotientExpression[0]);
else if (children.remainderExpression != null)
return this.visitNode(children.remainderExpression[0]);
else if (children.factorialExpression != null)
return this.visitNode(children.factorialExpression[0]);
else if (children.squareExpression != null)
return this.visitNode(children.squareExpression[0]);
else if (children.cubeExpression != null)
return this.visitNode(children.cubeExpression[0]);
else if (children.squareRootExpression != null)
return this.visitNode(children.squareRootExpression[0]);
else if (children.twiceExpression != null)
return this.visitNode(children.twiceExpression[0]);
else throw new Error("No matched subrule");
}
atomicExpression(
children: C.AtomicExpressionCstChildren
): V.AtomicExpression {
if (children.Character != null) {
const charId = this.toCharacterId(children.Character[0].image);
this.validateCharacter(
{ type: "character", name: charId },
this.toRange(children.Character[0])
);
return { type: "character" as const, name: charId };
} else if (children.FirstPerson != null) {
return { type: "characterRef", ref: "first" };
} else if (children.SecondPerson != null) {
return { type: "characterRef", ref: "second" };
} else if (children.reflexive != null) {
const ref = this.visitNode(children.reflexive[0]);
return { type: "characterRef", ref };
} else if (children.constant != null) {
return this.visitNode(children.constant[0]);
} else throw new Error("No matched subrule");
}
private visitBinaryExpression(
code: V.BinaryExpression["opType"],
lhs: C.ExpressionCstNode[],
rhs: C.ExpressionCstNode[]
): V.BinaryExpression {
if (lhs.length !== 1 || rhs.length !== 1)
throw new Error("Unexpected operands in binary expression");
return {
type: "binary",
opType: code,
lhs: this.visitNode(lhs[0]),
rhs: this.visitNode(rhs[0]),
};
}
private visitUnaryExpression(
code: V.UnaryExpression["opType"],
expr: C.ExpressionCstNode[]
): V.UnaryExpression {
if (expr.length !== 1)
throw new Error("Unexpected operands in unary expression");
return { type: "unary", opType: code, operand: this.visitNode(expr[0]) };
}
sumExpression(children: C.SumExpressionCstChildren): V.BinaryExpression {
return this.visitBinaryExpression("+", children.lhs, children.rhs);
}
differenceExpression(
children: C.DifferenceExpressionCstChildren
): V.BinaryExpression {
return this.visitBinaryExpression("-", children.lhs, children.rhs);
}
productExpression(
children: C.ProductExpressionCstChildren
): V.BinaryExpression {
return this.visitBinaryExpression("*", children.lhs, children.rhs);
}
quotientExpression(
children: C.QuotientExpressionCstChildren
): V.BinaryExpression {
return this.visitBinaryExpression("/", children.lhs, children.rhs);
}
remainderExpression(
children: C.RemainderExpressionCstChildren
): V.BinaryExpression {
return this.visitBinaryExpression("%", children.lhs, children.rhs);
}
factorialExpression(
children: C.FactorialExpressionCstChildren
): V.UnaryExpression {
return this.visitUnaryExpression("!", children.expression);
}
squareExpression(children: C.SquareExpressionCstChildren): V.UnaryExpression {
return this.visitUnaryExpression("sq", children.expression);
}
cubeExpression(children: C.CubeExpressionCstChildren): V.UnaryExpression {
return this.visitUnaryExpression("cube", children.expression);
}
squareRootExpression(
children: C.SquareRootExpressionCstChildren
): V.UnaryExpression {
return this.visitUnaryExpression("sqrt", children.expression);
}
twiceExpression(children: C.TwiceExpressionCstChildren): V.UnaryExpression {
return this.visitUnaryExpression("twice", children.expression);
}
}