diff --git a/.gitignore b/.gitignore index d3f11de..8f028e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ -# https://git-scm.com/docs/gitignore -# https://help.github.com/articles/ignoring-files -# Example .gitignore files: https://github.com/github/gitignore -/bower_components/ -/node_modules/ \ No newline at end of file +/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..c120edf --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# nix-parser + +TODO + +## 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 + +My income consists largely of donations for my projects. 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. + +## 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 + +TODO + +## API + +TODO diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8a27f08 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,31 @@ +var gulp = require("gulp"); +var presetES2015 = require("@joepie91/gulp-preset-es2015"); +var presetPegjs = require("@joepie91/gulp-preset-pegjs"); + +var sources = { + babel: ["src/**/*.js"], + pegjs: ["src/**/*.pegjs"] +} + +gulp.task('babel', function() { + return gulp.src(sources.babel) + .pipe(presetES2015({ + basePath: __dirname + })) + .pipe(gulp.dest("lib/")); +}); + +gulp.task('pegjs', function() { + return gulp.src(sources.pegjs) + .pipe(presetPegjs({ + basePath: __dirname + })) + .pipe(gulp.dest("lib/")); +}); + +gulp.task("watch", function () { + gulp.watch(sources.babel, ["babel"]); + gulp.watch(sources.pegjs, ["pegjs"]); +}); + +gulp.task("default", ["babel", "pegjs", "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..72648d0 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "nix-parser", + "version": "1.0.0", + "description": "Parser for Nix expressions", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://git.cryto.net/joepie91/node-nix-parser.git" + }, + "keywords": [ + "nix", + "parser", + "nixos" + ], + "author": "Sven Slootweg", + "license": "WTFPL", + "dependencies": {}, + "devDependencies": { + "@joepie91/gulp-preset-es2015": "^1.0.1", + "@joepie91/gulp-preset-pegjs": "^1.0.0", + "babel-preset-es2015": "^6.6.0", + "gulp": "^3.9.1" + } +} diff --git a/src/expression.pegjs b/src/expression.pegjs new file mode 100644 index 0000000..b12108f --- /dev/null +++ b/src/expression.pegjs @@ -0,0 +1,179 @@ +{ + function concatRepeat(first, rest, restIndex) { + return [first].concat(rest.map(function(item) { + return item[restIndex]; + })); + } + + function unnestFunctionCalls(first, nested, last) { + var callStack = nested.concat([last]); + + function createFunctionCall(i) { + var func; + + if (i === 0) { + func = first; + } else { + func = createFunctionCall(i - 1); + } + + return { + type: "functionCall", + argument: callStack[i], + function: func + }; + } + + return createFunctionCall(callStack.length - 1); + } +} + +start + = _ expression:expression* _ { return expression; } + +// Character classes +whitespace + = "\t" + / "\n" + / "\r" + / " " + +stringLiteralCharacter + = !('"' / "\\") char:. { return char; } + / "\\" escapableCharacter + +numberLiteralCharacter + = [0-9.] + +escapableCharacter + = '"' + / "n" + / "t" + / "r" + +commentCharacter + = [^\n\r] + +// Literals +stringLiteral + = '"' chars:stringLiteralCharacter+ '"' { return {type: "stringLiteral", value: chars.join("")} } + +numberLiteral + = chars:numberLiteralCharacter+ { return {type: "numberLiteral", value: chars.join("")} } + +// Utilities +_ + = (whitespace / comment)* {} + +// Language constructs +expression + = additive + / operand + +comment + = "#" chars:commentCharacter* _ { return {type: "comment", text: chars.join("")} } + +operand + = group + / letBlock + / stringLiteral + / numberLiteral + / functionDefinition + / recursiveSet + / set + / possiblyNestedFunctionCall + / identifier + +additive + = left:subtractive + _ "+" + _ right:additive { return {type: "operation", operator: "+", left: left, right: right} } + / subtractive + +subtractive + = left:multiplicative + _ "-" + _ right:subtractive { return {type: "operation", operator: "-", left: left, right: right} } + / multiplicative + +multiplicative + = left:operand + _ "*" + _ right:multiplicative { return {type: "operation", operator: "*", left: left, right: right} } + / operand + +identifier + = literal:stringLiteral { return {type: "identifier", identifier: literal.value} } + / chars:[a-z.]i+ { return {type: "identifier", identifier: text()} } + +group + = "(" + _ expression:expression + _ ")" { return {type: "group", expression: expression} } + +functionDefinition + = argument:functionDefinitionArgument + _ ":" + _ body:expression { return {type: "functionDefinition", argument: argument, body: body} } + +functionDefinitionArgument + = identifier + / setPattern + +//functionCallWithParens +// = functionName:expression "(" +// _ arg:expression +// _ ")" {return {type: "functionCall", name: functionName, argument: arg} } +// +//functionCallWithoutParens +// = functionName:expression " " +// _ arg:expression {return {type: "functionCall", name: functionName, argument: arg} } +// +//functionCall +// = functionCallWithParens +// / functionCallWithoutParens + +possiblyNestedFunctionCall + = first:expression nested:expression* last:expression { return unnestFunctionCalls(first, nested, last); } + +assignment + = _ identifier:identifier + _ "=" + _ expression:expression + _ ";" { return {type: "assignment", identifier: identifier, expression: expression} } + +assignmentList + = items:assignment* { return items; } + +bindingList + = items:assignment* { return {type: "bindings", assignments: items} } + +set + = "{" + _ items:assignmentList + _ "}" { return {type: "set", assignments: items} } + +recursiveSet + = "rec" + _ setData:set { return {type: "recursiveSet", assignments: setData.assignments} } + +letBlock + = "let" + _ bindingList:bindingList + _ "in" + _ expression:expression { return {type: "letBlock", bindings: bindingList.assignments, expression: expression} } + +setPattern + = "{" + _ args:setPatternVariableList + _ "}" { return {type: "setPattern", variables: args} } + +setPatternVariableList + = firstItem:setPatternVariable otherItems:(_ "," _ setPatternVariable)* { return concatRepeat(firstItem, otherItems, 3); } + +setPatternVariable + = restParameter + / identifier + +restParameter + = "..." { return {type: "restParameter"} } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a5b11bd --- /dev/null +++ b/src/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const expressionParser = require("./expression"); + +module.exports = function(body) { + return expressionParser.parse(body); +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..898d003 --- /dev/null +++ b/test.js @@ -0,0 +1,16 @@ +'use strict'; + +const util = require("util"); +const fs = require("fs"); +const parse = require("./"); + +function fullInspect(obj) { + return util.inspect(obj, {colors: true, depth: null, customInspect: false}) +} + +try { + let contents = fs.readFileSync(process.argv[2]).toString(); + console.log(fullInspect(parse(contents))); +} catch (err) { + console.log(fullInspect(err)) +} \ No newline at end of file diff --git a/test/function-call-default.nix b/test/function-call-default.nix new file mode 100644 index 0000000..2e7e46e --- /dev/null +++ b/test/function-call-default.nix @@ -0,0 +1 @@ +builtins.isInt 4 \ No newline at end of file diff --git a/test/function-call-parens.nix b/test/function-call-parens.nix new file mode 100644 index 0000000..91e2f73 --- /dev/null +++ b/test/function-call-parens.nix @@ -0,0 +1 @@ +builtins.isInt(4) \ No newline at end of file diff --git a/test/let-block.nix b/test/let-block.nix new file mode 100644 index 0000000..f74ce65 --- /dev/null +++ b/test/let-block.nix @@ -0,0 +1,7 @@ +let + h = "Hello"; + w = "World"; +in +{ + helloWorld = h + X + X; +} \ No newline at end of file diff --git a/test/nested-function-call.nix b/test/nested-function-call.nix new file mode 100644 index 0000000..95588ba --- /dev/null +++ b/test/nested-function-call.nix @@ -0,0 +1 @@ +builtins.isInt 4 10 12 \ No newline at end of file diff --git a/test/set-function.nix b/test/set-function.nix new file mode 100644 index 0000000..32f3c22 --- /dev/null +++ b/test/set-function.nix @@ -0,0 +1,7 @@ +{ system, bootStdenv, noSysDirs, gccWithCC, gccWithProfiling +, config, crossSystem, platform, lib +, pkgsWithOverrides +, ... }: + rec { + thing = config; + } \ No newline at end of file diff --git a/test/simple.nix b/test/simple.nix new file mode 100644 index 0000000..0ceb090 --- /dev/null +++ b/test/simple.nix @@ -0,0 +1,5 @@ +# code goes here +{ + v="understood"; # Another comment + b=2; +}