diff --git a/bin/options.js b/bin/options.js index 976e82a..76e4fe6 100644 --- a/bin/options.js +++ b/bin/options.js @@ -17,6 +17,7 @@ let options = { "format": "commonjs", "optimize": "speed", "output": "source", + "parser": {}, "plugins": [], "trace": false }; diff --git a/bin/peg.js b/bin/peg.js index 976ef9a..7e05881 100644 --- a/bin/peg.js +++ b/bin/peg.js @@ -34,6 +34,7 @@ function abort( message ) { // Main let inputStream, outputStream; +options.parser = options.parser || {}; if ( options.inputFile === "-" ) { @@ -48,6 +49,7 @@ if ( options.inputFile === "-" ) { } else { inputStream = fs.createReadStream( options.inputFile ); + options.parser.filename = options.inputFile; } diff --git a/docs/guides/javascript-api.md b/docs/guides/javascript-api.md index 232b83b..6ee0935 100644 --- a/docs/guides/javascript-api.md +++ b/docs/guides/javascript-api.md @@ -97,6 +97,7 @@ cache | `false` | makes the generated parser cache results, avoiding exponential context | `{}` | contains a map of variables used by `peg.util.vm.runInContext()` when the `output` option is set to `"parser"` dependencies | `{}` | parser dependencies, the value is an object which maps variables used to access the dependencies to module IDs used to load them; valid only when `format` is set to `"amd"`, `"commonjs"`, `"es"`, or `"umd"` exportVar | `null` | name of an optional global variable into which the generated parser object is assigned to when no module loader is detected; valid only when `format` is set to `"globals"` or `"umd"` +features | `null` | map of optional features that are set to `true` by default: `"text"`, `"offset"`, `"range"`, `"location"`, `"expected"`, `"error"` and `"filename"` format | `"bare"` | format of the generated parser (`"amd"`, `"bare"`, `"commonjs"`, `"es"`, `"globals"`, or `"umd"`); valid only when `output` is set to `"source"` header | `null` | adds additional comments or content after the `Generated by ...` comment; this option is only handled if it's an array or a string: optimize | `"speed"` | selects between optimizing the generated parser for parsing speed (`"speed"`) or code size (`"size"`) diff --git a/lib/compiler/index.js b/lib/compiler/index.js index 169810a..246e4c7 100644 --- a/lib/compiler/index.js +++ b/lib/compiler/index.js @@ -70,6 +70,7 @@ const compiler = { context: {}, dependencies: {}, exportVar: null, + features: null, format: "bare", header: null, optimize: "speed", diff --git a/lib/compiler/passes/generate-js.js b/lib/compiler/passes/generate-js.js index fe94e89..1d9bced 100644 --- a/lib/compiler/passes/generate-js.js +++ b/lib/compiler/passes/generate-js.js @@ -1,4 +1,4 @@ -/* eslint no-mixed-operators: 0, prefer-const: 0 */ +/* eslint no-mixed-operators: 0, prefer-const: 0, eqeqeq: 0 */ "use strict"; @@ -9,6 +9,18 @@ function generateJS( ast, session, options ) { const op = session.opcodes; + /* Features that should be generated in the parser. */ + const features = options.features || {}; + function use( feature, use ) { + + return feature in features + ? !! features[ feature ] + : use == null + ? true + : !! use; + + } + /* These only indent non-empty lines to avoid trailing whitespace. */ const lineMatchRE = /^([^`\r\n]+?(?:`[^`]*?`[^\r\n]*?)?)$/gm; function indent2( code ) { @@ -1283,43 +1295,85 @@ function generateJS( ast, session, options ) { } + if ( use( "text" ) ) { + + parts.push( [ + "", + " function text() {", + " return input.substring(peg$savedPos, peg$currPos);", + " }", + ].join( "\n" ) ); + + } + + if ( use( "offset" ) ) { + + parts.push( [ + "", + " function offset() {", + " return peg$savedPos;", + " }", + ].join( "\n" ) ); + + } + + if ( use( "range" ) ) { + + parts.push( [ + "", + " function range() {", + " return [peg$savedPos, peg$currPos];", + " }", + ].join( "\n" ) ); + + } + + if ( use( "location" ) ) { + + parts.push( [ + "", + " function location() {", + " return peg$computeLocation(peg$savedPos, peg$currPos);", + " }", + ].join( "\n" ) ); + + } + + if ( use( "expected" ) ) { + + parts.push( [ + "", + " function expected(description, location) {", + " location = location !== undefined", + " ? location", + " : peg$computeLocation(peg$savedPos, peg$currPos);", + "", + " throw peg$buildStructuredError(", + " [peg$otherExpectation(description)],", + " input.substring(peg$savedPos, peg$currPos),", + " location", + " );", + " }", + ].join( "\n" ) ); + + } + + if ( use( "error" ) ) { + + parts.push( [ + "", + " function error(message, location) {", + " location = location !== undefined", + " ? location", + " : peg$computeLocation(peg$savedPos, peg$currPos);", + "", + " throw peg$buildSimpleError(message, location);", + " }", + ].join( "\n" ) ); + + } + parts.push( [ - "", - " function text() {", - " return input.substring(peg$savedPos, peg$currPos);", - " }", - "", - " function offset() {", - " return peg$savedPos;", - " }", - "", - " function range() {", - " return [peg$savedPos, peg$currPos];", - " }", - "", - " function location() {", - " return peg$computeLocation(peg$savedPos, peg$currPos);", - " }", - "", - " function expected(description, location) {", - " location = location !== undefined", - " ? location", - " : peg$computeLocation(peg$savedPos, peg$currPos);", - "", - " throw peg$buildStructuredError(", - " [peg$otherExpectation(description)],", - " input.substring(peg$savedPos, peg$currPos),", - " location", - " );", - " }", - "", - " function error(message, location) {", - " location = location !== undefined", - " ? location", - " : peg$computeLocation(peg$savedPos, peg$currPos);", - "", - " throw peg$buildSimpleError(message, location);", - " }", "", " function peg$literalExpectation(text, ignoreCase) {", " return { type: \"literal\", text: text, ignoreCase: ignoreCase };", @@ -1376,22 +1430,27 @@ function generateJS( ast, session, options ) { " }", " }", "", + use( "filename" ) ? " var peg$VALIDFILENAME = typeof options.filename === \"string\" && options.filename.length > 0;" : "", " function peg$computeLocation(startPos, endPos) {", + " var loc = {};", + "", + use( "filename" ) ? " if ( peg$VALIDFILENAME ) loc.filename = options.filename;" : "", + "", " var startPosDetails = peg$computePosDetails(startPos);", - " var endPosDetails = peg$computePosDetails(endPos);", + " loc.start = {", + " offset: startPos,", + " line: startPosDetails.line,", + " column: startPosDetails.column", + " };", "", - " return {", - " start: {", - " offset: startPos,", - " line: startPosDetails.line,", - " column: startPosDetails.column", - " },", - " end: {", - " offset: endPos,", - " line: endPosDetails.line,", - " column: endPosDetails.column", - " }", + " var endPosDetails = peg$computePosDetails(endPos);", + " loc.end = {", + " offset: endPos,", + " line: endPosDetails.line,", + " column: endPosDetails.column", " };", + "", + " return loc;", " }", "", " function peg$begin() {", diff --git a/lib/parser.js b/lib/parser.js index cad3a6f..451d022 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -468,22 +468,27 @@ function peg$parse(input, options) { } } + var peg$VALIDFILENAME = typeof options.filename === "string" && options.filename.length > 0; function peg$computeLocation(startPos, endPos) { + var loc = {}; + + if ( peg$VALIDFILENAME ) loc.filename = options.filename; + var startPosDetails = peg$computePosDetails(startPos); - var endPosDetails = peg$computePosDetails(endPos); + loc.start = { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }; - return { - start: { - offset: startPos, - line: startPosDetails.line, - column: startPosDetails.column - }, - end: { - offset: endPos, - line: endPosDetails.line, - column: endPosDetails.column - } + var endPosDetails = peg$computePosDetails(endPos); + loc.end = { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column }; + + return loc; } function peg$begin() { diff --git a/lib/typings/api.d.ts b/lib/typings/api.d.ts index b7f0f43..1df174d 100644 --- a/lib/typings/api.d.ts +++ b/lib/typings/api.d.ts @@ -364,6 +364,7 @@ declare namespace peg { context?: { [ name: string ]: any; }; dependencies?: { [ name: string ]: string; }; exportVar?: string; + features?: IGeneratedParserFeatures; format?: FormatOptions; header?: string | string[]; optimize?: OptimizeOptions; @@ -379,6 +380,7 @@ declare namespace peg { context: { [ name: string ]: any; }; dependencies: { [ name: string ]: string; }; exportVar: string; + features: IGeneratedParserFeatures; format: FormatOptions; header: string | string[]; optimize: OptimizeOptions; @@ -387,6 +389,19 @@ declare namespace peg { } + interface IGeneratedParserFeatures { + + [ key: string ]: boolean; + text: boolean; + offset: boolean; + range: boolean; + location: boolean; + expected: boolean; + error: boolean; + filename: boolean; + + } + interface ICompilerPass { ( node: Grammar ): void; diff --git a/lib/typings/generated-parser.d.ts b/lib/typings/generated-parser.d.ts index 0beacfb..8e8840c 100644 --- a/lib/typings/generated-parser.d.ts +++ b/lib/typings/generated-parser.d.ts @@ -129,6 +129,7 @@ declare namespace generatedparser { interface IOptions { [ key: string ]: any; + filename?: string; startRule?: string; tracer?: ITracer;