From 071b427f052cc723d6fadc77229a7b4e3f2785c6 Mon Sep 17 00:00:00 2001 From: mohayonao Date: Sat, 28 Jun 2014 07:47:22 +0900 Subject: [PATCH] :sparkles: fix to refine BinaryExpressionParser --- src/sc/lang/compiler/parser/binop-expr.js | 183 ++++++----- .../lang/compiler/parser/binop-expr_test.js | 295 ++++++++++++++++++ 2 files changed, 396 insertions(+), 82 deletions(-) create mode 100644 src/sc/lang/compiler/parser/binop-expr_test.js diff --git a/src/sc/lang/compiler/parser/binop-expr.js b/src/sc/lang/compiler/parser/binop-expr.js index f51daa9..6f6aaf9 100644 --- a/src/sc/lang/compiler/parser/binop-expr.js +++ b/src/sc/lang/compiler/parser/binop-expr.js @@ -8,6 +8,18 @@ var Node = sc.lang.compiler.Node; var Parser = sc.lang.compiler.Parser; + /* + BinaryExpression : + LeftHandSideExpression BinaryExpressionOperator BinaryExpressionAdverb(opts) BinaryExpression + + BinaryExpressionOperator : + /[-+*\/%<=>!?&|@]+/ + LabelLiteral + + BinaryExpressionAdverb : + . Identifier + . IntegerLiteral + */ Parser.addParseMethod("BinaryExpression", function() { return new BinaryExpressionParser(this).parse(); }); @@ -15,9 +27,14 @@ function BinaryExpressionParser(parent) { Parser.call(this, parent); - // TODO: fix (this.binaryPrecedence = sc.config.binaryPrecedence) + // TODO: + // replace + // this.binaryPrecedence = sc.config.binaryPrecedence; + // + // remove below var binaryPrecedence; if (sc.config.binaryPrecedence) { + // istanbul ignore next if (typeof sc.config.binaryPrecedence === "object") { binaryPrecedence = sc.config.binaryPrecedence; } else { @@ -31,63 +48,62 @@ BinaryExpressionParser.prototype.parse = function() { var marker = this.createMarker(); - var left = this.parseLeftHandSideExpression(); - var operator = this.lookahead; - var prec = calcBinaryPrecedence(operator, this.binaryPrecedence); + var expr = this.parseLeftHandSideExpression(); + + var prec = this.calcBinaryPrecedence(this.lookahead); if (prec === 0) { - return left; + return expr; } - this.lex(); - operator.prec = prec; - operator.adverb = this.parseAdverb(); + var operator = this.parseBinaryExpressionOperator(prec); - return this.sortByBinaryPrecedence(left, operator, marker); + return this.sortByBinaryPrecedence(expr, operator, marker); }; - // TODO: fix to read easily - BinaryExpressionParser.prototype.sortByBinaryPrecedence = function(left, operator, marker) { - var markers = [ marker, this.createMarker() ]; - var right = this.parseLeftHandSideExpression(); + BinaryExpressionParser.prototype.calcBinaryPrecedence = function(token) { + if (token.type === Token.Label) { + return 255; + } + + if (token.type === Token.Punctuator) { + var operator = token.value; + if (operator === "=") { + return 0; + } + if (isBinaryOperator(operator)) { + return this.binaryPrecedence[operator] || 255; + } + } - var stack = [ left, operator, right ]; + return 0; + }; - var prec, expr; - while ((prec = calcBinaryPrecedence(this.lookahead, this.binaryPrecedence)) > 0) { - prec = sortByBinaryPrecedence(prec, stack, markers); + BinaryExpressionParser.prototype.parseBinaryExpressionOperator = function(prec) { + var operator = this.lex(); + operator.prec = prec; + operator.adverb = this.parseBinaryExpressionAdverb(); + return operator; + }; - // Shift. - var token = this.lex(); - token.prec = prec; - token.adverb = this.parseAdverb(); + BinaryExpressionParser.prototype.sortByBinaryPrecedence = function(left, operator, marker) { + var markerStack = [ marker, this.createMarker() ]; + var exprOpStack = [ left, operator, this.parseLeftHandSideExpression() ]; - stack.push(token); + var prec; + while ((prec = this.calcBinaryPrecedence(this.lookahead)) > 0) { + sortByBinaryPrecedence(prec, exprOpStack, markerStack); - markers.push(this.createMarker()); - expr = this.parseLeftHandSideExpression(); - stack.push(expr); - } + operator = this.parseBinaryExpressionOperator(prec); - // Final reduce to clean-up the stack. - var i = stack.length - 1; - expr = stack[i]; - markers.pop(); - while (i > 1) { - expr = Node.createBinaryExpression(stack[i - 1], stack[i - 2], expr); - i -= 2; - marker = markers.pop(); - marker.update().apply(expr); + markerStack.push(this.createMarker()); + exprOpStack.push(operator, this.parseLeftHandSideExpression()); } - return expr; + return reduceBinaryExpressionStack(exprOpStack, markerStack); }; - /* TODO: ??? - Adverb : - . PrimaryExpression - */ - BinaryExpressionParser.prototype.parseAdverb = function() { + BinaryExpressionParser.prototype.parseBinaryExpressionAdverb = function() { if (!this.match(".")) { return null; } @@ -97,61 +113,64 @@ var lookahead = this.lookahead; var adverb = this.parsePrimaryExpression(); - if (adverb.type === Syntax.Literal) { + if (isInteger(adverb)) { return adverb; } - if (adverb.type === Syntax.Identifier) { - adverb.type = Syntax.Literal; - adverb.value = adverb.name; - adverb.valueType = Token.SymbolLiteral; - delete adverb.name; - return adverb; + if (isAdverb(adverb)) { + return this.createMarker(adverb).update().apply( + Node.createLiteral({ type: Token.SymbolLiteral, value: adverb.name }) + ); } - this.throwUnexpected(lookahead); - - return null; + return this.throwUnexpected(lookahead); }; - function calcBinaryPrecedence(token, binaryPrecedence) { - var prec = 0; - - switch (token.type) { - case Token.Punctuator: - if (token.value !== "=") { - if (binaryPrecedence.hasOwnProperty(token.value)) { - prec = binaryPrecedence[token.value]; - } else if (/^[-+*\/%<=>!?&|@]+$/.test(token.value)) { - prec = 255; - } - } - break; - case Token.Label: - prec = 255; - break; + function sortByBinaryPrecedence(prec, exprOpStack, markerStack) { + while (isNeedSort(prec, exprOpStack)) { + var right = exprOpStack.pop(); + var operator = exprOpStack.pop(); + var left = exprOpStack.pop(); + markerStack.pop(); + exprOpStack.push(peek(markerStack).update().apply( + Node.createBinaryExpression(operator, left, right) + )); + } + } + + function reduceBinaryExpressionStack(exprOpStack, markerStack) { + markerStack.pop(); + + var expr = exprOpStack.pop(); + while (exprOpStack.length) { + expr = markerStack.pop().update().apply( + Node.createBinaryExpression(exprOpStack.pop(), exprOpStack.pop(), expr) + ); } - return prec; + return expr; } - function sortByBinaryPrecedence(prec, stack, markers) { - // Reduce: make a binary expression from the three topmost entries. - while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { - var right = stack.pop(); - var operator = stack.pop(); - var left = stack.pop(); - var expr = Node.createBinaryExpression(operator, left, right); - markers.pop(); + function peek(stack) { + return stack[stack.length - 1]; + } - var marker = markers.pop(); - marker.update().apply(expr); + function isNeedSort(prec, exprOpStack) { + return exprOpStack.length > 2 && prec <= exprOpStack[exprOpStack.length - 2].prec; + } - stack.push(expr); + function isBinaryOperator(operator) { + return /^[-+*\/%<=>!?&|@]+$/.test(operator); + } - markers.push(marker); - } + function isInteger(node) { + return node.valueType === Token.IntegerLiteral; + } - return prec; + function isAdverb(node) { + if (node.type === Syntax.Identifier) { + return /^[a-z]$/.test(node.name.charAt(0)); + } + return false; } })(sc); diff --git a/src/sc/lang/compiler/parser/binop-expr_test.js b/src/sc/lang/compiler/parser/binop-expr_test.js new file mode 100644 index 0000000..a3bfe45 --- /dev/null +++ b/src/sc/lang/compiler/parser/binop-expr_test.js @@ -0,0 +1,295 @@ +(function() { + "use strict"; + + require("./installer"); + + var Syntax = sc.lang.compiler.Syntax; + var Token = sc.lang.compiler.Token; + var Parser = sc.lang.compiler.Parser; + var Lexer = sc.lang.compiler.Lexer; + + describe("sc.lang.compiler.Parser", function() { + describe("parseBinaryExpression", function() { + it("parse", function() { + _.chain({ + "a = 1": { + type: Syntax.Identifier, + name: "a", + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + "a, b": { + type: Syntax.Identifier, + name: "a", + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + "1 + 2": { + type: Syntax.BinaryExpression, + operator: "+", + left: { + type: Syntax.Literal, + value: "1", + valueType: Token.IntegerLiteral, + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + right: { + type: Syntax.Literal, + value: "2", + valueType: Token.IntegerLiteral, + range: [ 4, 5 ], + loc: { + start: { line: 1, column: 4 }, + end: { line: 1, column: 5 }, + } + }, + range: [ 0, 5 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 5 }, + } + }, + "1 + 2 * 3": { // (1 + 2) * 3 + type: Syntax.BinaryExpression, + operator: "*", + left: { + type: Syntax.BinaryExpression, + operator: "+", + left: { + type: Syntax.Literal, + value: "1", + valueType: Token.IntegerLiteral, + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + right: { + type: Syntax.Literal, + value: "2", + valueType: Token.IntegerLiteral, + range: [ 4, 5 ], + loc: { + start: { line: 1, column: 4 }, + end: { line: 1, column: 5 }, + } + }, + range: [ 0, 5 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 5 }, + } + }, + right: { + type: Syntax.Literal, + value: "3", + valueType: Token.IntegerLiteral, + range: [ 8, 9 ], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 9 }, + } + }, + range: [ 0, 9 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 9 }, + } + }, + "1 max: 2": { + type: Syntax.BinaryExpression, + operator: "max", + left: { + type: Syntax.Literal, + value: "1", + valueType: Token.IntegerLiteral, + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + right: { + type: Syntax.Literal, + value: "2", + valueType: Token.IntegerLiteral, + range: [ 7, 8 ], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 8 }, + } + }, + range: [ 0, 8 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 8 }, + } + }, + "1 +.0 2": { + type: Syntax.BinaryExpression, + operator: "+", + left: { + type: Syntax.Literal, + value: "1", + valueType: Token.IntegerLiteral, + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + right: { + type: Syntax.Literal, + value: "2", + valueType: Token.IntegerLiteral, + range: [ 6, 7 ], + loc: { + start: { line: 1, column: 6 }, + end: { line: 1, column: 7 }, + } + }, + adverb: { + type: Syntax.Literal, + value: "0", + valueType: Token.IntegerLiteral, + range: [ 4, 5 ], + loc: { + start: { line: 1, column: 4 }, + end: { line: 1, column: 5 }, + } + }, + range: [ 0, 7 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 7 }, + } + }, + "1 +.f 2": { + type: Syntax.BinaryExpression, + operator: "+", + left: { + type: Syntax.Literal, + value: "1", + valueType: Token.IntegerLiteral, + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + right: { + type: Syntax.Literal, + value: "2", + valueType: Token.IntegerLiteral, + range: [ 6, 7 ], + loc: { + start: { line: 1, column: 6 }, + end: { line: 1, column: 7 }, + } + }, + adverb: { + type: Syntax.Literal, + value: "f", + valueType: Token.SymbolLiteral, + range: [ 4, 5 ], + loc: { + start: { line: 1, column: 4 }, + end: { line: 1, column: 5 }, + } + }, + range: [ 0, 7 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 7 }, + } + } + }).pairs().each(function(items) { + var p = new Parser(null, new Lexer(items[0], { loc: true, range: true } )); + expect(p.parseBinaryExpression(), items[0]).to.eql(items[1]); + }); + }); + it("parse with binaryPrecedence", function() { + sc.config.binaryPrecedence = true; + _.chain({ + "1 + 2 * 3": { // 1 + (2 * 3) + type: Syntax.BinaryExpression, + operator: "+", + left: { + type: Syntax.Literal, + value: "1", + valueType: Token.IntegerLiteral, + range: [ 0, 1 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, + } + }, + right: { + type: Syntax.BinaryExpression, + operator: "*", + left: { + type: Syntax.Literal, + value: "2", + valueType: Token.IntegerLiteral, + range: [ 4, 5 ], + loc: { + start: { line: 1, column: 4 }, + end: { line: 1, column: 5 }, + } + }, + right: { + type: Syntax.Literal, + value: "3", + valueType: Token.IntegerLiteral, + range: [ 8, 9 ], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 9 }, + } + }, + range: [ 4, 9 ], + loc: { + start: { line: 1, column: 4 }, + end: { line: 1, column: 9 }, + } + }, + range: [ 0, 9 ], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 9 }, + } + } + }).pairs().each(function(items) { + var p = new Parser(null, new Lexer(items[0], { loc: true, range: true } )); + expect(p.parseBinaryExpression(), items[0]).to.eql(items[1]); + }); + sc.config.binaryPrecedence = false; + }); + it("error", function() { + _.chain({ + "1 +.[] 2": "unexpected token [", + "1 +.0.0 2": "unexpected number", + "1 +.nil 2": "unexpected token nil", + "1 +.Nil 2": "unexpected identifier", + "1 +._ 2": "unexpected identifier", + }).pairs().each(function(items) { + var p = new Parser(null, new Lexer(items[0], { loc: true, range: true } )); + expect(function() { + p.parseBinaryExpression(); + }).to.throw(items[1]); + }); + }); + }); + }); +})();