horatio
Version:
A javascript compiler for the Shakespeare Programming Language
569 lines (483 loc) • 15.5 kB
JavaScript
import Token from './token';
import Tokenizer from './tokenizer';
import AST from './ast';
/**
* Parses an SPL program and generates an AST.
* @memberof Horatio
* @param {string} input - The SPL program to parse
*/
export default class Parser {
constructor(input) {
this.tokenizer = new Tokenizer(input);
this.currentToken = null;
}
/**
* Accept the current token if it matches an expected kind
* @param {number} expectedKind - The byte value of the expected token
* @throws {SyntaxError} - Throws syntax error if current token kind does not match expected token kind.
*/
accept(expectedKind) {
if (this.currentToken.kind === expectedKind) {
this.currentToken = this.tokenizer.nextToken();
} else {
throw new Error("Syntax Error - Unexpected Token: " + JSON.stringify(this.currentToken));
}
}
/**
* Accept the current token regardless of kind
*/
acceptIt() {
this.currentToken = this.tokenizer.nextToken();
}
/**
* Parse the SPL program and return an AST
* @returns {AST.Program} - The program AST.
*/
parse() {
this.currentToken = this.tokenizer.nextToken();
let program = this.parseProgram();
//console.log(program);
if (this.currentToken !== -1) {
throw new Error("Syntax Error - unexpected end of program");
}
return program;
}
/* Parsers */
parseProgram() {
let comment = this.parseComment();
this.accept(Token.PERIOD);
let declarations = [this.parseDeclaration()];
while (this.currentToken.kind===Token.CHARACTER) {
declarations.push(this.parseDeclaration());
}
let parts = [this.parsePart()];
while (this.currentToken.kind===Token.ACT) {
parts.push(this.parsePart());
}
return new AST.Program(comment, declarations, parts);
}
parseComment() {
let comment = "";
while (this.currentToken.kind!==Token.PERIOD) {
comment += this.currentToken.sequence + " ";
this.acceptIt();
}
return new AST.Comment(comment.trim());
}
parseDeclaration() {
let character = new AST.Character(this.currentToken.sequence);
this.accept(Token.CHARACTER);
this.accept(Token.COMMA);
let comment = this.parseComment();
this.accept(Token.PERIOD);
return new AST.Declaration(character, comment);
}
parsePart() {
this.accept(Token.ACT);
let numeral = new AST.Numeral(this.currentToken.sequence);
this.accept(Token.ROMAN_NUMERAL);
this.accept(Token.COLON);
let comment = this.parseComment();
this.accept(Token.PERIOD);
let subparts = [this.parseSubPart()];
while (this.currentToken.kind===Token.SCENE) {
subparts.push(this.parseSubPart());
}
return new AST.Part(numeral, comment, subparts);
}
parseSubPart() {
this.accept(Token.SCENE);
let numeral = new AST.Numeral(this.currentToken.sequence);
this.accept(Token.ROMAN_NUMERAL);
this.accept(Token.COLON);
let comment = this.parseComment();
this.accept(Token.PERIOD);
let stage = this.parseStage();
return new AST.Subpart(numeral, comment, stage);
}
parseStage() {
let start_presence, end_presence;
if (this.currentToken.kind===Token.LEFT_BRACKET) {
start_presence = this.parsePresence();
}
let dialogue = this.parseDialogue();
if (this.currentToken.kind===Token.LEFT_BRACKET) {
end_presence = this.parsePresence();
}
return new AST.Stage(dialogue, start_presence, end_presence);
}
parsePresence() {
this.accept(Token.LEFT_BRACKET);
let c1, c2, ret;
switch (this.currentToken.kind) {
case Token.ENTER:
this.acceptIt();
c1 = new AST.Character(this.currentToken.sequence);
c2 = null;
this.accept(Token.CHARACTER);
if (this.currentToken.kind===Token.AMPERSAND) {
this.acceptIt();
c2 = new AST.Character(this.currentToken.sequence);
this.accept(Token.CHARACTER);
}
ret = new AST.Enter(c1, c2);
break;
case Token.EXIT:
this.acceptIt();
let character = new AST.Character(this.currentToken.sequence);
this.accept(Token.CHARACTER);
ret = new AST.Exit(character);
break;
case Token.EXEUNT:
this.acceptIt();
if (this.currentToken.kind===Token.CHARACTER) {
c1 = new AST.Character(this.currentToken.sequence);
this.acceptIt();
this.accept(Token.AMPERSAND);
c2 = new AST.Character(this.currentToken.sequence);
this.accept(Token.CHARACTER);
ret = new AST.Exeunt(c1, c2);
} else {
ret = new AST.Exeunt();
}
break;
}
this.accept(Token.RIGHT_BRACKET);
return ret;
}
parseDialogue() {
let lines = [this.parseLine()];
while (this.currentToken.kind===Token.CHARACTER) {
lines.push(this.parseLine());
}
return new AST.Dialogue(lines);
}
parseLine() {
let character = new AST.Character(this.currentToken.sequence);
this.accept(Token.CHARACTER);
this.accept(Token.COLON);
let sentences = [this.parseSentence()];
function isSentence(token) {
switch(token) {
case Token.BE:
case Token.BE_COMPARATIVE:
case Token.IF_SO:
case Token.IMPERATIVE:
case Token.INPUT_INTEGER:
case Token.INPUT_CHAR:
case Token.OUTPUT_INTEGER:
case Token.OUTPUT_CHAR:
case Token.REMEMBER:
case Token.RECALL:
return true;
default:
return false;
}
}
while (isSentence(this.currentToken.kind)) {
sentences.push(this.parseSentence());
}
return new AST.Line(character, sentences);
}
parseSentence() {
let sentence;
switch (this.currentToken.kind) {
case Token.BE:
sentence = this.parseAssignment();
//this.accept(Token.PERIOD);
this.acceptIt();
break;
case Token.BE_COMPARATIVE:
sentence = this.parseQuestion();
this.accept(Token.QUESTION_MARK);
break;
case Token.IF_SO:
sentence = this.parseResponse();
this.accept(Token.PERIOD);
break;
case Token.IMPERATIVE:
sentence = this.parseGoto();
this.accept(Token.PERIOD);
break;
case Token.INPUT_INTEGER:
case Token.INPUT_CHAR:
sentence = this.parseInput();
this.accept(Token.EXCLAMATION_POINT);
break;
case Token.OUTPUT_INTEGER:
case Token.OUTPUT_CHAR:
sentence = this.parseOutput();
this.accept(Token.EXCLAMATION_POINT);
break;
case Token.REMEMBER:
sentence = this.parseRemember();
this.accept(Token.EXCLAMATION_POINT);
break;
case Token.RECALL:
sentence = this.parseRecall();
this.accept(Token.EXCLAMATION_POINT);
break;
}
return sentence;
}
parseBe() {
let be;
if (this.currentToken.kind===Token.BE) {
be = new AST.Be(this.currentToken.sequence);
this.acceptIt();
}
return be;
}
parseAssignment() {
let be = this.parseBe();
if (this.currentToken.kind===Token.AS) {
this.acceptIt();
this.parseAdjective();
this.accept(Token.AS);
}
let value = this.parseValue();
return new AST.AssignmentSentence(be, value);
}
parseValue() {
let value, pronoun;
if (this.currentToken.kind===Token.ARTICLE) {
this.acceptIt();
}
switch (this.currentToken.kind) {
case Token.UNARY_OPERATOR:
value = this.parseUnaryOperation();
break;
case Token.ARITHMETIC_OPERATOR:
value = this.parseArithmeticOperation();
break;
case Token.POSITIVE_ADJECTIVE:
case Token.NEUTRAL_ADJECTIVE:
case Token.NEGATIVE_ADJECTIVE:
case Token.POSITIVE_NOUN:
case Token.NEUTRAL_NOUN:
case Token.NEGATIVE_NOUN:
value = this.parseConstant();
break;
case Token.FIRST_PERSON_PRONOUN:
pronoun = new AST.FirstPersonPronoun(this.currentToken.sequence);
value = new AST.PronounValue(pronoun);
this.acceptIt();
break;
case Token.SECOND_PERSON_PRONOUN:
pronoun = new AST.SecondPersonPronoun(this.currentToken.sequence);
value = new AST.PronounValue(pronoun);
this.acceptIt();
break;
default:
throw new Error("Syntax Error - Unknown Token: "+this.currentToken.sequence);
}
return value;
}
parseUnaryOperation() {
let operator = new AST.UnaryOperator(this.currentToken.sequence);
this.accept(Token.UNARY_OPERATOR);
let value = this.parseValue();
return new AST.UnaryOperationValue(operator, value);
}
parseArithmeticOperation() {
if (this.currentToken.kind===Token.ARTICLE) {
this.acceptIt();
}
let operator = new AST.ArithmeticOperator(this.currentToken.sequence);
this.accept(Token.ARITHMETIC_OPERATOR);
let value_1 = this.parseValue();
this.accept(Token.AND);
let value_2 = this.parseValue();
return new AST.ArithmeticOperationValue(operator, value_1, value_2);
}
parseConstant() {
if (this.currentToken.kind===Token.ARTICLE) {
this.acceptIt();
}
switch (this.currentToken.kind) {
case Token.NEUTRAL_ADJECTIVE:
case Token.NEUTRAL_NOUN:
throw new Error("Syntax Error - Constant Value cannot start with neutral adjective or noun.");
case Token.POSITIVE_ADJECTIVE:
case Token.POSITIVE_NOUN:
return this.parsePositiveConstant();
case Token.NEGATIVE_ADJECTIVE:
case Token.NEGATIVE_NOUN:
return this.parseNegativeConstant();
default:
throw new Error("Syntax Error - Unknown Token: "+this.currentToken.sequence);
}
}
parsePositiveConstant() {
let adjectives = [];
let adjective;
while (this.currentToken.kind!==Token.POSITIVE_NOUN) {
switch (this.currentToken.kind) {
case Token.POSITIVE_ADJECTIVE:
adjective = new AST.PositiveAdjective(this.currentToken.sequence);
adjectives.push(adjective);
this.acceptIt();
break;
case Token.NEUTRAL_ADJECTIVE:
adjective = new AST.NeutralAdjective(this.currentToken.sequence);
adjectives.push(adjective);
this.acceptIt();
break;
case Token.NEGATIVE_ADJECTIVE:
throw new Error("Syntax Error - Cannot mix positive and negative words in constant assignment.");
}
}
let noun = new AST.PositiveNoun(this.currentToken.sequence);
this.accept(Token.POSITIVE_NOUN);
return new AST.PositiveConstantValue(noun, adjectives);
}
parseNegativeConstant() {
let adjectives = [];
let adjective;
while (this.currentToken.kind!==Token.NEGATIVE_NOUN) {
switch (this.currentToken.kind) {
case Token.NEGATIVE_ADJECTIVE:
adjective = new AST.NegativeAdjective(this.currentToken.sequence);
adjectives.push(adjective);
this.acceptIt();
break;
case Token.NEUTRAL_ADJECTIVE:
adjective = new AST.NeutralAdjective(this.currentToken.sequence);
adjectives.push(adjective);
this.acceptIt();
break;
case Token.POSITIVE_ADJECTIVE:
throw new Error("Syntax Error - Cannot mix positive and negative words in constant assignment.");
}
}
let noun = new AST.NegativeNoun(this.currentToken.sequence);
this.accept(Token.NEGATIVE_NOUN);
return new AST.NegativeConstantValue(noun, adjectives);
}
parseQuestion() {
let be = this.parseBeComparative();
let comparison = this.parseComparative();
let value = this.parseValue();
return new AST.QuestionSentence(be, comparison, value);
}
parseBeComparative() {
let be_comparative;
if (this.currentToken.kind===Token.BE_COMPARATIVE) {
be_comparative = new AST.BeComparative(this.currentToken.sequence);
}
return be_comparative;
}
parseComparative() {
let comparison, comparative, adjective;
switch (this.currentToken.kind) {
case Token.POSITIVE_COMPARATIVE:
comparative = new AST.PositiveComparative(this.currentToken.sequence);
comparison = new AST.GreaterThanComparison(comparative);
this.acceptIt();
this.accept(Token.THAN);
break;
case Token.NEGATIVE_COMPARATIVE:
comparative = new AST.NegativeComparative(this.currentToken.sequence);
comparison = new AST.LesserThanComparison(comparative);
this.acceptIt();
this.accept(Token.THAN);
break;
case Token.AS:
this.acceptIt();
adjective = this.parseAdjective();
comparison = new AST.EqualToComparison(adjective);
this.accept(Token.AS);
break;
case Token.NOT:
this.acceptIt();
comparative = this.parseComparative();
comparison = new AST.InverseComparison(comparative);
//switch (this.currentToken.kind) {
// case Token.POSITIVE_COMPARATIVE:
// case Token.NEGATIVE_COMPARATIVE:
// this.acceptIt();
// this.accept(Token.THAN);
// break;
//}
break;
}
return comparison;
}
parseResponse() {
this.accept(Token.IF_SO);
this.accept(Token.COMMA);
let goto = this.parseGoto();
return new AST.ResponseSentence(goto);
}
parseGoto() {
this.accept(Token.IMPERATIVE);
this.accept(Token.RETURN);
this.accept(Token.TO);
this.accept(Token.SCENE);
let numeral = new AST.Numeral(this.currentToken.sequence);
this.accept(Token.ROMAN_NUMERAL);
return new AST.Goto(numeral);
}
parseInput() {
let sequence = this.currentToken.sequence;
let ret;
switch (this.currentToken.kind) {
case Token.INPUT_INTEGER:
ret = new AST.IntegerInputSentence(sequence);
break;
case Token.INPUT_CHAR:
ret = new AST.CharInputSentence(sequence);
break;
}
this.acceptIt();
return ret;
}
parseOutput() {
let sequence = this.currentToken.sequence;
let ret;
switch (this.currentToken.kind) {
case Token.OUTPUT_INTEGER:
ret = new AST.IntegerOutputSentence(sequence);
break;
case Token.OUTPUT_CHAR:
ret = new AST.CharOutputSentence(sequence);
break;
}
this.acceptIt();
return ret;
}
parseRemember() {
this.accept(Token.REMEMBER);
let pronoun;
switch (this.currentToken.kind) {
case Token.FIRST_PERSON_PRONOUN:
pronoun = new AST.FirstPersonPronoun(this.currentToken.sequence);
this.acceptIt();
break;
case Token.SECOND_PERSON_PRONOUN:
pronoun = new AST.SecondPersonPronoun(this.currentToken.sequence);
this.acceptIt();
break;
}
return new AST.RememberSentence(pronoun);
}
parseRecall() {
this.accept(Token.RECALL);
this.accept(Token.COMMA);
let comment = "";
while (this.currentToken.kind!==Token.EXCLAMATION_POINT) {
comment += this.currentToken.sequence + " ";
this.acceptIt();
}
return new AST.RecallSentence(comment.trim());
}
parseAdjective() {
switch (this.currentToken.kind) {
case Token.POSITIVE_ADJECTIVE:
case Token.NEUTRAL_ADJECTIVE:
case Token.NEGATIVE_ADJECTIVE:
this.acceptIt();
break;
}
}
}