commit 9ddff7120c2027a72df9db899c2a386bcaceddc6 Author: Sven Slootweg Date: Sun Mar 5 15:18:09 2017 +0100 Initial commit; v1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f028e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/lib/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..096746c --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +/node_modules/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..99cd16a --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# better-peg-tracer + +A better terminal-based tracer for debugging PEG.js. Better documentation coming soon. + +![Screenshot of better-peg-tracer on a terminal](https://git.cryto.net/joepie91/better-peg-tracer/raw/master/screenshot.png) + +## License + +[WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), whichever you prefer. A donation and/or attribution are appreciated, but not required. + +## Donate + +Maintaining open-source projects takes a lot of time, and the more donations I receive, the more time I can dedicate to open-source. If this module is useful to you, consider [making a donation](http://cryto.net/~joepie91/donate.html)! + +You can donate using Bitcoin, PayPal, Flattr, cash-in-mail, SEPA transfers, and pretty much anything else. Thank you! + +## Contributing + +Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the files in `src/`, not those in `lib/`. + +Build tool of choice is `gulp`; simply run `gulp` while developing, and it will watch for changes. + +Be aware that by making a pull request, you agree to release your modifications under the licenses stated above. + +## Usage + +```javascript +const pegjs = require("pegjs"); +const createBetterPegTracer = require("better-peg-tracer"); + +let stringToParse = ` +foo{{{}} +{{align:center}bar +qux {class:test1,test2}baz{/class}{/align}} +`.trim(); + +let parser = pegjs.buildParser(pegGrammar, { + trace: true +}); + +let results = parser.parse(stringToParse, { + tracer: createBetterPegTracer(stringToParse) +}); +``` + +## API + +### createBetterPegTracer(inputString) + +Creates a tracer for the specified `inputString`. + +* __inputString__: The string that is going to be parsed. This is necessary for snippet display to work. diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..1769501 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,18 @@ +var gulp = require("gulp"); +var presetES2015 = require("@joepie91/gulp-preset-es2015"); + +var source = ["src/**/*.js"] + +gulp.task('babel', function() { + return gulp.src(source) + .pipe(presetES2015({ + basePath: __dirname + })) + .pipe(gulp.dest("lib/")); +}); + +gulp.task("watch", function () { + gulp.watch(source, ["babel"]); +}); + +gulp.task("default", ["babel", "watch"]); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..507257b --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require("./lib"); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b9cb2b8 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "better-peg-tracer", + "version": "1.0.0", + "description": "A better tracer for PEG.js", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "gulp": "gulp" + }, + "repository": { + "type": "git", + "url": "http://git.cryto.net/joepie91/better-peg-tracer.git" + }, + "keywords": [ + "PEG", + "PEG.js", + "tracer", + "parser", + "debugger" + ], + "author": "Sven Slootweg", + "license": "WTFPL", + "dependencies": { + "chalk": "^1.1.3", + "default-value": "^1.0.0", + "pad": "^1.1.0" + }, + "devDependencies": { + "@joepie91/gulp-preset-es2015": "^1.0.1", + "babel-preset-es2015": "^6.6.0", + "gulp": "^3.9.1" + } +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..81721ce Binary files /dev/null and b/screenshot.png differ diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..14f0b99 --- /dev/null +++ b/src/index.js @@ -0,0 +1,99 @@ +'use strict'; + +const pad = require("pad"); +const chalk = Object.assign(require("chalk"), {enabled: true}); +const defaultValue = require("default-value"); + +module.exports = function createBetterPegTracer(parsedString) { + let currentLevel = 0; + let lastWasFail = false + let parsedStringLines = parsedString.split("\n"); + let lastSeenRule = []; + + return { + trace: function trace(event) { + if (event.type === "rule.fail" || event.type === "rule.match") { + currentLevel -= 1; + } + + let ruleIndent = pad("", currentLevel * 4); + let eventName = event.type.replace(/^rule\./, ""); + let isMultiline = (parsedStringLines.length > 1); + + let startLine = event.location.start.line; + let endLine = event.location.end.line; + let startColumn = event.location.start.column; + let endColumn = event.location.end.column; + + let firstLine = parsedStringLines[startLine - 1]; + let lastLine = parsedStringLines[endLine - 1]; + + let affectedString, color, startPosition, endPosition, position; + + if (isMultiline) { + startPosition = `${pad(4, startLine)}:${pad(2, startColumn)}`; + + if (startLine !== endLine || startColumn !== endColumn) { + endPosition = `${pad(4, endLine)}:${pad(2, endColumn)}`; + } + } else { + startPosition = startColumn; + + if (startColumn !== endColumn) { + endPosition = endColumn; + } + } + + position = `${pad(8, startPosition)} ${chalk.white("│")} ${pad(8, defaultValue(endPosition, ""))}`; + + if (startLine === endLine) { + if (startColumn === endColumn) { + /* Not reading a range, but rather attempting to find a match from the current point. */ + if (event.location.start.offset < parsedString.length) { + affectedString = chalk.gray(`${firstLine.slice(startColumn - 1)} ...`); + } else { + affectedString = ""; + } + } else { + affectedString = firstLine.slice(startColumn - 1, endColumn - 1); + } + } else { + affectedString = `${firstLine.slice(startColumn - 1)} ... ${lastLine.slice(0, endColumn - 1)}`; + } + + if (event.type === "rule.enter") { + lastSeenRule[currentLevel] = event.rule; + } + + if (event.type === "rule.match") { + color = chalk.green; + } else if (event.type === "rule.fail") { + color = chalk.red; + } else { + color = chalk.white; + } + + let crumbs = lastSeenRule.slice(0, currentLevel).concat([color(event.rule)]); + let limitedCrumbs; + + if (crumbs.length > 6) { + limitedCrumbs = crumbs.slice(0, 2).concat([`... ${crumbs.length - 5} hidden ...`]).concat(crumbs.slice(-3)); + } else { + limitedCrumbs = crumbs; + } + + let line = [ + position, + pad(eventName, 5), + chalk.gray(pad(limitedCrumbs.join(" -> "), "90")), + affectedString + ].join(` ${chalk.white("│")} `); + + process.stdout.write(color(line + "\n")); + + if (event.type === "rule.enter") { + currentLevel += 1; + } + } + } +}