Skip to content

Commit

Permalink
docs(parser): add grammar rules to JSDoc comments into parser product…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
ghaiklor committed Jul 8, 2017
1 parent b7ea9ff commit b2aa1ea
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 26 deletions.
44 changes: 19 additions & 25 deletions src/parser/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Parser {
}

/**
* Production for an empty node.
* empty:
*
* @returns {NoOperation}
*/
Expand All @@ -60,7 +60,6 @@ class Parser {
}

/**
* Production for a variable.
* variable: IDENTIFIER
*
* @returns {Variable}
Expand All @@ -72,8 +71,6 @@ class Parser {
}

/**
* Production for parsing basic units of a language.
* It consists of unary operators, integers and expressions.
* factor: PLUS factor
* | MINUS factor
* | INTEGER
Expand All @@ -99,15 +96,15 @@ class Parser {
const node = this.expr();
this.eat(Token.RIGHT_PARENTHESIS);
return node;
} else if (token.is(Token.IDENTIFIER)) {
return this.variable();
}

Parser.error(`Unexpected token in "factor" production: ${token}`);
return this.variable();
}

/**
* Production for parsing terminals.
* term: factor ASTERISK term
* | factor SLASH term
* | factor
*
* @returns {Node}
*/
Expand All @@ -119,7 +116,7 @@ class Parser {

if (token.is(Token.ASTERISK)) {
this.eat(Token.ASTERISK);
} else if (token.is(Token.SLASH)) {
} else {
this.eat(Token.SLASH);
}

Expand All @@ -130,7 +127,9 @@ class Parser {
}

/**
* Production for parsing expressions.
* expr: term PLUS expr
* | term MINUS expr
* | term
*
* @returns {Node}
*/
Expand All @@ -142,7 +141,7 @@ class Parser {

if (token.is(Token.PLUS)) {
this.eat(Token.PLUS);
} else if (token.is(Token.MINUS)) {
} else {
this.eat(Token.MINUS);
}

Expand All @@ -153,7 +152,6 @@ class Parser {
}

/**
* Production for an assignment statement.
* assignmentStatement: variable ASSIGN expr
*
* @returns {Assign}
Expand All @@ -168,10 +166,9 @@ class Parser {
}

/**
* Production for a compound statements.
* compoundStatement: BEGIN statementList END
*
* @returns {Node}
* @returns {Compound}
*/
compoundStatement() {
this.eat(Token.BEGIN);
Expand All @@ -185,8 +182,9 @@ class Parser {
}

/**
* Production for a statement.
* statement: compoundStatement | assignmentStatement | empty
* statement: compoundStatement
* | assignmentStatement
* | empty
*
* @returns {Node}
*/
Expand All @@ -205,8 +203,8 @@ class Parser {
}

/**
* Production for a statement list.
* statementList: statement | statement SEMI statementList
* statementList: statement
* | statement SEMICOLON statementList
*
* @returns {Array<Node>}
*/
Expand All @@ -227,7 +225,6 @@ class Parser {
}

/**
* Production for a program.
* program: compoundStatement DOT
*
* @returns {Node}
Expand All @@ -240,19 +237,16 @@ class Parser {

/**
* Parses an input source program and returns an AST.
* It uses all the grammar rules above to parse tokens and build AST from it.
*
* @returns {Node}
* @example
* const parser = new Parser('2 + 5');
* const parser = new Parser('BEGIN END.');
*
* parser.parse(); // return an object that represents an AST of source program
*/
parse() {
const node = this.program();

if (this.currentToken.is(Token.EOF)) return node;

Parser.error(`Invalid program, I didn't get EOF symbol`);
return this.program();
}

/**
Expand Down
105 changes: 104 additions & 1 deletion test/parser/Parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,109 @@ describe('Parser', () => {
assert.throws(() => parser.eat(Token.ASTERISK), Error, '[Parser]\nYou provided unexpected token type "ASTERISK" while current token is Token(INTEGER, 5)');
});

it('Should properly return NoOperation node for an empty production', () => {
const parser = new Parser('');

assert.instanceOf(parser.empty(), AST.NoOperation);
});

it('Should properly return Variable node from variable production', () => {
const parser = new Parser('x y');
const x = parser.variable();
const y = parser.variable();

assert.instanceOf(x, AST.Variable);
assert.instanceOf(x.getToken(), Token);
assert.ok(x.getToken().is(Token.IDENTIFIER));
assert.equal(x.getName(), 'x');
assert.instanceOf(y, AST.Variable);
assert.equal(y.getName(), 'y');
assert.instanceOf(y.getToken(), Token);
assert.ok(y.getToken().is(Token.IDENTIFIER));
});

it('Should properly handle factor production', () => {
const parser = new Parser('+1 -2 3 (4) a');

assert.instanceOf(parser.factor(), AST.UnaryOperator);
assert.instanceOf(parser.factor(), AST.UnaryOperator);
assert.instanceOf(parser.factor(), AST.Number);
assert.instanceOf(parser.factor(), AST.Number);
assert.instanceOf(parser.factor(), AST.Variable);
});

it('Should properly handle term production', () => {
const parser = new Parser('1 * 2 / 3');

assert.instanceOf(parser.term(), AST.BinaryOperator);
});

it('Should properly handle expr production', () => {
const parser = new Parser('1 + 2 - 3');

assert.instanceOf(parser.expr(), AST.BinaryOperator);
});

it('Should properly handle assignmentStatement production', () => {
const parser = new Parser('a := 2');
const node = parser.assignmentStatement();

assert.instanceOf(node, AST.Assign);
assert.instanceOf(node.getVariable(), AST.Variable);
assert.equal(node.getVariable().getName(), 'a');
assert.instanceOf(node.getToken(), Token);
assert.ok(node.getToken().is(Token.ASSIGN));
assert.instanceOf(node.getExpression(), AST.Number);
assert.equal(node.getExpression().getValue(), 2);
});

it('Should properly handle compoundStatement production', () => {
const parser = new Parser('BEGIN a := 2 END');
const node = parser.compoundStatement();

assert.instanceOf(node, AST.Compound);
assert.equal(node.getChildren().length, 1);
assert.instanceOf(node.getChildren()[0], AST.Assign);
assert.equal(node.getChildren()[0].getVariable().getName(), 'a');
assert.ok(node.getChildren()[0].getToken().is(Token.ASSIGN));
assert.equal(node.getChildren()[0].getExpression().getValue(), 2);
});

it('Should properly handle statement production', () => {
const parser = new Parser('BEGIN END');
const node = parser.statement();

assert.instanceOf(node, AST.Compound);
assert.equal(node.getChildren().length, 1);
assert.instanceOf(node.getChildren()[0], AST.NoOperation);
});

it('Should properly handle statementList production', () => {
const parser = new Parser('a := 2; b := 3;');
const nodes = parser.statementList();

assert.isArray(nodes);
assert.equal(nodes.length, 3);
assert.instanceOf(nodes[0], AST.Assign);
assert.equal(nodes[0].getVariable().getName(), 'a');
assert.ok(nodes[0].getToken().is(Token.ASSIGN));
assert.equal(nodes[0].getExpression().getValue(), 2);
assert.instanceOf(nodes[1], AST.Assign);
assert.equal(nodes[1].getVariable().getName(), 'b');
assert.ok(nodes[1].getToken().is(Token.ASSIGN));
assert.equal(nodes[1].getExpression().getValue(), 3);
assert.instanceOf(nodes[2], AST.NoOperation);
});

it('Should properly handle program production', () => {
const parser = new Parser('BEGIN END.');
const node = parser.program();

assert.instanceOf(node, AST.Compound);
assert.equal(node.getChildren().length, 1);
assert.instanceOf(node.getChildren()[0], AST.NoOperation);
});

it('Should properly parse a simple program', () => {
const program = `BEGIN x:= 2; y:= x + 5; END.`;
const parser = new Parser(program);
Expand Down Expand Up @@ -82,6 +185,6 @@ describe('Parser', () => {
it('Should properly throw an error if provide parser with wrong syntax structure', () => {
const parser = new Parser('BEGIN x:= 100 + / 5');

assert.throws(() => parser.parse(), Error, '[Parser]\nUnexpected token in "factor" production: Token(SLASH, /)');
assert.throws(() => parser.parse(), Error, '[Parser]\nYou provided unexpected token type "IDENTIFIER" while current token is Token(SLASH, /)');
});
});

0 comments on commit b2aa1ea

Please sign in to comment.