From dd89c6e11264f8f016831b1ce699f376dc5282d7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 20 Mar 2015 12:04:33 +0100 Subject: [PATCH] Make loose parser work with minor interface changes introduced by modularization --- acorn_loose.js | 10 ++-- src/index.js | 128 ++----------------------------------------------- src/options.js | 119 +++++++++++++++++++++++++++++++++++++++++++++ src/state.js | 4 ++ test/driver.js | 2 +- 5 files changed, 133 insertions(+), 130 deletions(-) create mode 100644 src/options.js diff --git a/acorn_loose.js b/acorn_loose.js index b8207e242b..b2165715ec 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -41,13 +41,13 @@ acorn.defaultOptions.tabSize = 4; exports.parse_dammit = function(input, options) { - var p = new LooseParser(options, input); + var p = new LooseParser(input, options); p.next(); return p.parseTopLevel(); }; var LooseParser = exports.LooseParser = function(input, options) { - this.toks = new acorn.Parser(input, options); + this.toks = acorn.tokenizer(input, options) this.options = this.toks.options; this.input = this.toks.input; this.tok = this.last = {type: tt.eof, start: 0, end: 0}; @@ -149,9 +149,9 @@ if (this.options.locations) { this.toks.curLine = 1; - this.toks.lineStart = acorn.lineBreak.lastIndex = 0; + this.toks.lineStart = acorn.lineBreakG.lastIndex = 0; var match; - while ((match = acorn.lineBreak.exec(this.input)) && match.index < pos) { + while ((match = acorn.lineBreakG.exec(this.input)) && match.index < pos) { ++this.toks.curLine; this.toks.lineStart = match.index + match[0].length; } @@ -273,7 +273,7 @@ lp.canInsertSemicolon = function() { return this.tok.type === tt.eof || this.tok.type === tt.braceR || - acorn.newline.test(this.input.slice(this.last.end, this.tok.start)); + acorn.lineBreak.test(this.input.slice(this.last.end, this.tok.start)); }; lp.semicolon = function() { diff --git a/src/index.js b/src/index.js index 0842ab400d..055f99b77c 100644 --- a/src/index.js +++ b/src/index.js @@ -19,15 +19,15 @@ // [dammit]: acorn_loose.js // [walk]: util/walk.js -import {Parser} from "./state" -import {has, isArray} from "./util" -import {SourceLocation} from "./location" +import {Parser, plugins} from "./state" +import {getOptions} from "./options" import "./parseutil" import "./statement" import "./lval" import "./expression" export {Parser} from "./state" +export {defaultOptions} from "./options" export {SourceLocation} from "./location" export {getLineInfo} from "./location" export {Node} from "./node" @@ -35,7 +35,7 @@ export {TokenType, types as tokTypes} from "./tokentype" export {TokContext, types as tokContexts} from "./tokencontext" export {isIdentifierChar, isIdentifierStart} from "./identifier" export {Token} from "./tokenize" -export {isNewLine, lineBreak} from "./whitespace" +export {isNewLine, lineBreak, lineBreakG} from "./whitespace" export const version = "0.12.1" @@ -53,126 +53,6 @@ export function parse(input, options) { return p.parseTopLevel(p.options.program || p.startNodeAt(startPos)) } -// A second optional argument can be given to further configure -// the parser process. These options are recognized: - -export const defaultOptions = { - // `ecmaVersion` indicates the ECMAScript version to parse. Must - // be either 3, or 5, or 6. This influences support for strict - // mode, the set of reserved words, support for getters and - // setters and other features. - ecmaVersion: 5, - // Source type ("script" or "module") for different semantics - sourceType: "script", - // `onInsertedSemicolon` can be a callback that will be called - // when a semicolon is automatically inserted. It will be passed - // th position of the comma as an offset, and if `locations` is - // enabled, it is given the location as a `{line, column}` object - // as second argument. - onInsertedSemicolon: null, - // `onTrailingComma` is similar to `onInsertedSemicolon`, but for - // trailing commas. - onTrailingComma: null, - // By default, reserved words are not enforced. Disable - // `allowReserved` to enforce them. When this option has the - // value "never", reserved words and keywords can also not be - // used as property names. - allowReserved: true, - // When enabled, a return at the top level is not considered an - // error. - allowReturnOutsideFunction: false, - // When enabled, import/export statements are not constrained to - // appearing at the top of the program. - allowImportExportEverywhere: false, - // When enabled, hashbang directive in the beginning of file - // is allowed and treated as a line comment. - allowHashBang: false, - // When `locations` is on, `loc` properties holding objects with - // `start` and `end` properties in `{line, column}` form (with - // line being 1-based and column 0-based) will be attached to the - // nodes. - locations: false, - // A function can be passed as `onToken` option, which will - // cause Acorn to call that function with object in the same - // format as tokenize() returns. Note that you are not - // allowed to call the parser from the callback—that will - // corrupt its internal state. - onToken: null, - // A function can be passed as `onComment` option, which will - // cause Acorn to call that function with `(block, text, start, - // end)` parameters whenever a comment is skipped. `block` is a - // boolean indicating whether this is a block (`/* */`) comment, - // `text` is the content of the comment, and `start` and `end` are - // character offsets that denote the start and end of the comment. - // When the `locations` option is on, two more parameters are - // passed, the full `{line, column}` locations of the start and - // end of the comments. Note that you are not allowed to call the - // parser from the callback—that will corrupt its internal state. - onComment: null, - // Nodes have their start and end characters offsets recorded in - // `start` and `end` properties (directly on the node, rather than - // the `loc` object, which holds line/column data. To also add a - // [semi-standardized][range] `range` property holding a `[start, - // end]` array with the same numbers, set the `ranges` option to - // `true`. - // - // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 - ranges: false, - // It is possible to parse multiple files into a single AST by - // passing the tree produced by parsing the first file as - // `program` option in subsequent parses. This will add the - // toplevel forms of the parsed file to the `Program` (top) node - // of an existing parse tree. - program: null, - // When `locations` is on, you can pass this to record the source - // file in every node's `loc` object. - sourceFile: null, - // This value, if given, is stored in every node, whether - // `locations` is on or off. - directSourceFile: null, - // When enabled, parenthesized expressions are represented by - // (non-standard) ParenthesizedExpression nodes - preserveParens: false, - plugins: {} -} - -// Registered plugins - -export const plugins = {} - -// Interpret and default an options object - -function getOptions(opts) { - let options = {} - for (let opt in defaultOptions) - options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt] - - if (isArray(options.onToken)) { - var tokens = options.onToken; - options.onToken = (token) => tokens.push(token) - } - if (isArray(options.onComment)) - options.onComment = pushComment(options, options.onComment); - - return options; -} - -function pushComment(options, array) { - return function (block, text, start, end, startLoc, endLoc) { - let comment = { - type: block ? 'Block' : 'Line', - value: text, - start: start, - end: end - } - if (options.locations) - comment.loc = new SourceLocation(this, startLoc, endLoc) - if (options.ranges) - comment.range = [start, end] - array.push(comment) - } -} - // This function tries to parse a single expression at a given // offset in a string. Useful for parsing mixed-language formats // that embed JavaScript expressions. diff --git a/src/options.js b/src/options.js new file mode 100644 index 0000000000..713345a6b3 --- /dev/null +++ b/src/options.js @@ -0,0 +1,119 @@ +import {has, isArray} from "./util" +import {SourceLocation} from "./location" + +// A second optional argument can be given to further configure +// the parser process. These options are recognized: + +export const defaultOptions = { + // `ecmaVersion` indicates the ECMAScript version to parse. Must + // be either 3, or 5, or 6. This influences support for strict + // mode, the set of reserved words, support for getters and + // setters and other features. + ecmaVersion: 5, + // Source type ("script" or "module") for different semantics + sourceType: "script", + // `onInsertedSemicolon` can be a callback that will be called + // when a semicolon is automatically inserted. It will be passed + // th position of the comma as an offset, and if `locations` is + // enabled, it is given the location as a `{line, column}` object + // as second argument. + onInsertedSemicolon: null, + // `onTrailingComma` is similar to `onInsertedSemicolon`, but for + // trailing commas. + onTrailingComma: null, + // By default, reserved words are not enforced. Disable + // `allowReserved` to enforce them. When this option has the + // value "never", reserved words and keywords can also not be + // used as property names. + allowReserved: true, + // When enabled, a return at the top level is not considered an + // error. + allowReturnOutsideFunction: false, + // When enabled, import/export statements are not constrained to + // appearing at the top of the program. + allowImportExportEverywhere: false, + // When enabled, hashbang directive in the beginning of file + // is allowed and treated as a line comment. + allowHashBang: false, + // When `locations` is on, `loc` properties holding objects with + // `start` and `end` properties in `{line, column}` form (with + // line being 1-based and column 0-based) will be attached to the + // nodes. + locations: false, + // A function can be passed as `onToken` option, which will + // cause Acorn to call that function with object in the same + // format as tokenize() returns. Note that you are not + // allowed to call the parser from the callback—that will + // corrupt its internal state. + onToken: null, + // A function can be passed as `onComment` option, which will + // cause Acorn to call that function with `(block, text, start, + // end)` parameters whenever a comment is skipped. `block` is a + // boolean indicating whether this is a block (`/* */`) comment, + // `text` is the content of the comment, and `start` and `end` are + // character offsets that denote the start and end of the comment. + // When the `locations` option is on, two more parameters are + // passed, the full `{line, column}` locations of the start and + // end of the comments. Note that you are not allowed to call the + // parser from the callback—that will corrupt its internal state. + onComment: null, + // Nodes have their start and end characters offsets recorded in + // `start` and `end` properties (directly on the node, rather than + // the `loc` object, which holds line/column data. To also add a + // [semi-standardized][range] `range` property holding a `[start, + // end]` array with the same numbers, set the `ranges` option to + // `true`. + // + // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + ranges: false, + // It is possible to parse multiple files into a single AST by + // passing the tree produced by parsing the first file as + // `program` option in subsequent parses. This will add the + // toplevel forms of the parsed file to the `Program` (top) node + // of an existing parse tree. + program: null, + // When `locations` is on, you can pass this to record the source + // file in every node's `loc` object. + sourceFile: null, + // This value, if given, is stored in every node, whether + // `locations` is on or off. + directSourceFile: null, + // When enabled, parenthesized expressions are represented by + // (non-standard) ParenthesizedExpression nodes + preserveParens: false, + plugins: {} +} + +// Interpret and default an options object + +export function getOptions(opts) { + let options = {} + for (let opt in defaultOptions) + options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt] + + if (isArray(options.onToken)) { + var tokens = options.onToken; + options.onToken = (token) => tokens.push(token) + } + if (isArray(options.onComment)) + options.onComment = pushComment(options, options.onComment); + + return options; +} + +function pushComment(options, array) { + return function (block, text, start, end, startLoc, endLoc) { + let comment = { + type: block ? 'Block' : 'Line', + value: text, + start: start, + end: end + } + if (options.locations) + comment.loc = new SourceLocation(this, startLoc, endLoc) + if (options.ranges) + comment.range = [start, end] + array.push(comment) + } +} + diff --git a/src/state.js b/src/state.js index 3ef232de3d..f14060be5b 100644 --- a/src/state.js +++ b/src/state.js @@ -59,6 +59,10 @@ Parser.prototype.extend = function(name, f) { this[name] = f(this[name]) } +// Registered plugins + +export const plugins = {} + Parser.prototype.loadPlugins = function(plugins) { for (let name in plugins) { let plugin = exports.plugins[name] diff --git a/test/driver.js b/test/driver.js index d8998b930b..46e29e547c 100644 --- a/test/driver.js +++ b/test/driver.js @@ -27,7 +27,7 @@ try { var ast = parse(test.code, testOpts); } catch(e) { - if (!(e instanceof SyntaxError)) throw e; + if (!(e instanceof SyntaxError)) { console.log(e.stack); throw e; } if (test.error) { if (e.message == test.error) callback("ok", test.code); else callback("fail", test.code,