From bdc5b1d4cdd8784115e116f3324ce45262ea9130 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Mon, 18 May 2020 15:04:19 +0100 Subject: [PATCH] add basic support for parsing new expressions --- src/expression/parse.js | 156 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 142 insertions(+), 14 deletions(-) diff --git a/src/expression/parse.js b/src/expression/parse.js index 49c68df..eaceaf4 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -51,7 +51,7 @@ class RootExpression extends ParsedExpression { class TypeCastExpression extends ParsedExpression { /** * - * @param {string} cast_type + * @param {ParsedExpression} cast_type * @param {ParsedExpression} rhs */ constructor(cast_type, rhs) { @@ -143,6 +143,44 @@ class MemberExpression extends QualifierExpression { } } +class BracketedExpression extends ParsedExpression { + constructor(expression, qualified_terms) { + super(); + this.expression = expression; + this.qualified_terms = qualified_terms; + } +} + +class ParsedNewExpression extends ParsedExpression {} + +class NewObjectExpression extends ParsedNewExpression { + /** + * @param {RootExpression} ctr_call + * @param {QualifierExpression[]} post_ctr_qualifiers + * @param {string} method_body + */ + constructor(ctr_call, post_ctr_qualifiers, method_body) { + super(); + this.ctr_call = ctr_call; + this.qualified_terms = post_ctr_qualifiers; + this.method_body = method_body; + } +} + +class NewArrayExpression extends ParsedNewExpression { + /** + * @param {RootExpression} type + * @param {ArrayIndexExpression[]} arrdim_initers + * @param {QualifierExpression[]} post_ctr_qualifiers + */ + constructor(type, arrdim_initers, post_ctr_qualifiers) { + super(); + this.type = type; + this.arrdim_initers = arrdim_initers; + this.post_ctr_qualifiers = post_ctr_qualifiers; + } +} + /** * Remove characters from the expression followed by any leading whitespace/comments * @param {ExpressionText} e @@ -226,17 +264,21 @@ function parseBracketOrCastExpression(e) { if (!strip(e, ')')) { return null; } - if (res instanceof RootExpression) { - if (/^(int|long|byte|short|double|float|char|boolean)$/.test(res.root_term) && !res.qualified_terms.length) { - // primitive typecast - const castexpr = parse_expression_term(e); - if (!castexpr) { - return null; - } - res = new TypeCastExpression(res.root_term, castexpr); + // note - a bracketed expression followed by another bracketed expression is assumed to be a cast: + // double d = (double)(float)5; - is ok + // XYZ xyz = (new XYZ)(1,2,3); - nope + // - this will still need to be resolved for +/- e.g (int)+5 vs (some.field)+5 + if (/^[\w"'(!~]/.test(e.expr)) { + // typecast + const castexpr = parse_expression(e); + if (!castexpr) { + return null; } + return new TypeCastExpression(res, castexpr); } - return res; + + const qt = parse_qualified_terms(e); + return new BracketedExpression(res, qt); } /** @@ -257,6 +299,89 @@ function parseUnaryExpression(e, unop) { return res; } +/** + * @param {ExpressionText} e + * @param {RootExpression} ctr + * @param {QualifierExpression[]} ctr_qualifiers + * @param {QualifierExpression[]} post_ctr_qualifiers + */ +function parseNewObjectExpression(e, ctr, ctr_qualifiers, post_ctr_qualifiers) { + const ctr_call = new RootExpression(ctr.root_term, ctr.root_term_type, ctr_qualifiers); + let method_body = null; + if (!post_ctr_qualifiers.length) { + // if there are no qualifiers following the constructor, look for an anonymous method body + if (e.expr.startsWith('{')) { + // don't parse it - just scan for the closing brace + const brace_re = /\/\*[\d\D]*?\*\/|\/\/.*|".*?"|".*|'.'?|(\{)|(\})/g; + let balance = 0, body_end = e.expr.length; + for (let m; m = brace_re.exec(e.expr); ) { + if (m[1]) balance++; + else if (m[2] && (--balance === 0)) { + body_end = m.index + 1; + break; + } + } + method_body = e.expr.slice(0, body_end); + strip(e, method_body.length); + } + } + return new NewObjectExpression(ctr_call, post_ctr_qualifiers, method_body); +} + +/** + * @param {ExpressionText} e + * @param {RootExpression} ctr + * @param {Number} first_array_qualifier_idx + */ +function parseNewArrayExpression(e, ctr, first_array_qualifier_idx) { + let arrdim_initers = []; + let i = first_array_qualifier_idx; + for (; i < ctr.qualified_terms.length; i++) { + const term = ctr.qualified_terms[i]; + if (term instanceof ArrayIndexExpression) { + arrdim_initers.push(term); + continue; + } + break; + } + const type = new RootExpression(ctr.root_term, ctr.root_term_type, ctr.qualified_terms.slice(0, first_array_qualifier_idx)); + return new NewArrayExpression(type, arrdim_initers, ctr.qualified_terms.slice(i)); +} + +/** + * @param {ExpressionText} e + */ +function parseNewExpression(e) { + const ctr = parse_expression_term(e); + if (!(ctr instanceof RootExpression)) { + return null; + } + let new_expression = null; + ctr.qualified_terms.find((qt,idx) => { + if (qt instanceof MethodCallExpression) { + // new object contructor - split into constructor qualifiers and post-ctr-qualifiers + const qualified_terms = ctr.qualified_terms.slice(); + const ctr_qualifiers = qualified_terms.splice(0, idx + 1); + new_expression = parseNewObjectExpression(e, ctr, ctr_qualifiers, qualified_terms); + return true; + } + if (qt instanceof ArrayIndexExpression) { + // new array constructor + // in java, multi-dimensional array constructors have priority over array accessors + // e.g new int[2][1] - is a 2D array, + // (new int[2])[1] - is the 1th element of a 1D array + new_expression = parseNewArrayExpression(e, ctr, idx); + return true; + } + }); + if (!new_expression) { + // treat unqualified new expressions as object constructors with no parameters + // eg. new XYZ === new XYZ() + new_expression = parseNewObjectExpression(e, ctr, ctr.qualified_terms, []); + } + return new_expression; +} + /** * @param {ExpressionText} e */ @@ -279,10 +404,6 @@ function parse_expression_term(e) { if (qualified_terms === null) { return null; } - // the root term is not allowed to be a method call - if (qualified_terms[0] instanceof MethodCallExpression) { - return null; - } return new RootExpression(root_term[0], root_term_type, qualified_terms); } @@ -299,6 +420,11 @@ function getBinaryOperator(s) { * @returns {ParsedExpression} */ function parse_expression(e) { + const newop = e.expr.match(/^new\b/); + if (newop) { + strip(e, 3); + return parseNewExpression(e); + } const prefix_incdec = e.expr.match(/^(?:(\+\+)|\-\-)(?=[a-zA-Z_])/); if (prefix_incdec) { strip(e, 2); @@ -355,6 +481,8 @@ module.exports = { ExpressionText, MemberExpression, MethodCallExpression, + NewArrayExpression, + NewObjectExpression, parse_expression, ParsedExpression, QualifierExpression,