From 21a6de06d5a50cf9188cee2935bb76eeb85fd332 Mon Sep 17 00:00:00 2001 From: Futago-za Ryuu Date: Sat, 17 Mar 2018 04:08:05 +0000 Subject: [PATCH] Optional features This commit enables optional features that are enabled by default in the generated parser. For now, only some of the helpers and filename are generated based on this new option, but this will change in the future most likely. Resolves #421 --- bin/options.js | 1 + bin/peg.js | 2 + docs/guides/javascript-api.md | 1 + lib/compiler/index.js | 1 + lib/compiler/passes/generate-js.js | 157 ++++++++++++++++++++--------- lib/parser.js | 29 +++--- lib/typings/api.d.ts | 15 +++ lib/typings/generated-parser.d.ts | 1 + 8 files changed, 146 insertions(+), 61 deletions(-) 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;