├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist └── speg.js ├── package-lock.json ├── package.json ├── src ├── exceptions.js ├── rd_parser.js ├── speg.js ├── speg_module_visitor.js ├── speg_parser.js └── speg_visitor.js ├── test ├── exceptions.spec.js ├── rd_parser.combo.spec.js ├── rd_parser.peg.spec.js ├── rd_parser.spec.js ├── speg.fixtures.js ├── speg.invalid.fixtures.js ├── speg_fixtures │ ├── escape.peg │ ├── escape.peg.json │ ├── escape.peg.txt │ ├── pegjs_example.peg │ ├── pegjs_example.peg.json │ ├── pegjs_example.peg.txt │ ├── simple_and_predicate.peg │ ├── simple_and_predicate.peg.json │ ├── simple_and_predicate.peg.txt │ ├── simple_eof.peg │ ├── simple_eof.peg.json │ ├── simple_eof.peg.txt │ ├── simple_not_predicate.peg │ ├── simple_not_predicate.peg.json │ ├── simple_not_predicate.peg.txt │ ├── simple_one_or_more.peg │ ├── simple_one_or_more.peg.json │ ├── simple_one_or_more.peg.txt │ ├── simple_optional.peg │ ├── simple_optional.peg.json │ ├── simple_optional.peg.txt │ ├── simple_ordered_choice.peg │ ├── simple_ordered_choice.peg.json │ ├── simple_ordered_choice.peg.txt │ ├── simple_regex.peg │ ├── simple_regex.peg.json │ ├── simple_regex.peg.txt │ ├── simple_rule_call.peg │ ├── simple_rule_call.peg.json │ ├── simple_rule_call.peg.txt │ ├── simple_rule_call_underscore.peg │ ├── simple_rule_call_underscore.peg.json │ ├── simple_rule_call_underscore.peg.txt │ ├── simple_sequence.peg │ ├── simple_sequence.peg.json │ ├── simple_sequence.peg.txt │ ├── simple_string.peg │ ├── simple_string.peg.json │ ├── simple_string.peg.txt │ ├── simple_zero_or_more.peg │ ├── simple_zero_or_more.peg.json │ ├── simple_zero_or_more.peg.txt │ ├── stringstring.peg │ ├── stringstring.peg.json │ ├── stringstring.peg.txt │ ├── tag.peg.json │ ├── tag.peg.txt │ ├── tag.peg1 │ ├── tags.peg.json │ ├── tags.peg.txt │ ├── tags.peg1 │ ├── url.peg │ ├── url.peg.json │ └── url.peg.txt ├── speg_grammar_fixtures │ ├── invalid │ │ ├── double_space_with_wrong_end.peg │ │ ├── empty_and_predicate.peg │ │ ├── empty_one_or_more.peg │ │ ├── empty_optional.peg │ │ ├── empty_zero_or_more.peg │ │ ├── grammar_numbers_at_start.peg │ │ ├── rule_numbers_at_start.peg │ │ ├── simple_regex_char_unescaped.peg │ │ └── simple_string_no_space.peg │ └── valid │ │ ├── and_predicate.peg │ │ ├── grammar_camel_case.peg │ │ ├── grammar_lower_numbers.peg │ │ ├── grammar_lower_undescore.peg │ │ ├── not_predicate.peg │ │ ├── one_or_more.peg │ │ ├── optional.peg │ │ ├── ordered_choice.peg │ │ ├── rule_call.peg │ │ ├── rule_call_numbers.peg │ │ ├── rule_call_underscrore.peg │ │ ├── sequence.peg │ │ ├── simple_regex_char.peg │ │ ├── simple_string.peg │ │ ├── simple_string_no_space.peg │ │ ├── simple_string_space_before_grammar.peg │ │ ├── single_char_rule_call.peg │ │ ├── tag.peg │ │ ├── tags_url.peg │ │ ├── url.peg │ │ └── zero_or_more.peg ├── speg_invalid_fixtures │ ├── optional-negation.peg │ ├── optional-negation.peg.error │ ├── optional-negation.peg.txt │ ├── sequence.peg │ ├── sequence.peg.error │ └── sequence.peg.txt ├── speg_module.fixtures.js └── speg_parser.fixtures.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = false 8 | 9 | # Matches multiple files with brace expansion notation 10 | # Set default charset 11 | [*.{js,py}] 12 | charset = utf-8 13 | 14 | # 4 space indentation 15 | [*.py] 16 | indent_style = space 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "accessor-pairs": "error", 10 | "array-bracket-spacing": [ 11 | "error", 12 | "never" 13 | ], 14 | "array-callback-return": "error", 15 | "arrow-body-style": "error", 16 | "arrow-parens": "error", 17 | "arrow-spacing": "error", 18 | "block-scoped-var": "error", 19 | "block-spacing": [ 20 | "error", 21 | "always" 22 | ], 23 | "brace-style": [ 24 | "error", 25 | "1tbs", 26 | { 27 | "allowSingleLine": true 28 | } 29 | ], 30 | "callback-return": "error", 31 | "camelcase": "off", 32 | "capitalized-comments": "error", 33 | "class-methods-use-this": "error", 34 | "comma-dangle": "error", 35 | "comma-spacing": "off", 36 | "comma-style": [ 37 | "error", 38 | "last" 39 | ], 40 | "complexity": "error", 41 | "computed-property-spacing": [ 42 | "error", 43 | "never" 44 | ], 45 | "consistent-return": "error", 46 | "consistent-this": "error", 47 | "curly": "error", 48 | "default-case": "error", 49 | "dot-location": "off", 50 | "dot-notation": "error", 51 | "eol-last": "error", 52 | "eqeqeq": "error", 53 | "func-call-spacing": "error", 54 | "func-name-matching": "error", 55 | "func-names": [ 56 | "error", 57 | "never" 58 | ], 59 | "func-style": [ 60 | "error", 61 | "declaration" 62 | ], 63 | "generator-star-spacing": "error", 64 | "global-require": "error", 65 | "guard-for-in": "error", 66 | "handle-callback-err": "error", 67 | "id-blacklist": "error", 68 | "id-length": "off", 69 | "id-match": "error", 70 | "indent": "off", 71 | "init-declarations": "off", 72 | "jsx-quotes": "error", 73 | "key-spacing": "error", 74 | "keyword-spacing": [ 75 | "error", 76 | { 77 | "after": true, 78 | "before": true 79 | } 80 | ], 81 | "line-comment-position": "error", 82 | "linebreak-style": [ 83 | "error", 84 | "unix" 85 | ], 86 | "lines-around-comment": "error", 87 | "lines-around-directive": "error", 88 | "max-depth": "error", 89 | "max-len": "off", 90 | "max-lines": "off", 91 | "max-nested-callbacks": "error", 92 | "max-params": "error", 93 | "max-statements": "off", 94 | "max-statements-per-line": "off", 95 | "multiline-ternary": [ 96 | "error", 97 | "never" 98 | ], 99 | "new-cap": "error", 100 | "new-parens": "error", 101 | "newline-after-var": "off", 102 | "newline-before-return": "off", 103 | "newline-per-chained-call": "error", 104 | "no-alert": "error", 105 | "no-array-constructor": "error", 106 | "no-await-in-loop": "error", 107 | "no-bitwise": "error", 108 | "no-caller": "error", 109 | "no-catch-shadow": "error", 110 | "no-confusing-arrow": "error", 111 | "no-continue": "error", 112 | "no-div-regex": "error", 113 | "no-duplicate-imports": "error", 114 | "no-else-return": "off", 115 | "no-empty-function": "off", 116 | "no-eq-null": "error", 117 | "no-eval": "error", 118 | "no-extend-native": "error", 119 | "no-extra-bind": "error", 120 | "no-extra-label": "error", 121 | "no-extra-parens": "off", 122 | "no-floating-decimal": "error", 123 | "no-implicit-globals": "off", 124 | "no-implied-eval": "error", 125 | "no-inline-comments": "error", 126 | "no-inner-declarations": [ 127 | "error", 128 | "functions" 129 | ], 130 | "no-invalid-this": "error", 131 | "no-iterator": "error", 132 | "no-label-var": "error", 133 | "no-labels": "error", 134 | "no-lone-blocks": "error", 135 | "no-lonely-if": "error", 136 | "no-loop-func": "error", 137 | "no-magic-numbers": "off", 138 | "no-mixed-operators": "error", 139 | "no-mixed-requires": "error", 140 | "no-multi-spaces": "off", 141 | "no-multi-str": "error", 142 | "no-multiple-empty-lines": "error", 143 | "no-native-reassign": "error", 144 | "no-negated-condition": "error", 145 | "no-negated-in-lhs": "error", 146 | "no-nested-ternary": "error", 147 | "no-new": "error", 148 | "no-new-func": "error", 149 | "no-new-object": "error", 150 | "no-new-require": "error", 151 | "no-new-wrappers": "error", 152 | "no-octal-escape": "error", 153 | "no-param-reassign": [ 154 | "error", 155 | { 156 | "props": false 157 | } 158 | ], 159 | "no-path-concat": "error", 160 | "no-plusplus": [ 161 | "error", 162 | { 163 | "allowForLoopAfterthoughts": true 164 | } 165 | ], 166 | "no-process-env": "error", 167 | "no-process-exit": "error", 168 | "no-proto": "error", 169 | "no-prototype-builtins": "error", 170 | "no-restricted-globals": "error", 171 | "no-restricted-imports": "error", 172 | "no-restricted-modules": "error", 173 | "no-restricted-properties": "error", 174 | "no-restricted-syntax": "error", 175 | "no-return-assign": "error", 176 | "no-return-await": "error", 177 | "no-script-url": "error", 178 | "no-self-compare": "error", 179 | "no-sequences": "error", 180 | "no-shadow": "error", 181 | "no-shadow-restricted-names": "error", 182 | "no-spaced-func": "error", 183 | "no-sync": "warn", 184 | "no-tabs": "off", 185 | "no-template-curly-in-string": "error", 186 | "no-ternary": "off", 187 | "no-throw-literal": "error", 188 | "no-trailing-spaces": "off", 189 | "no-undef-init": "error", 190 | "no-undefined": "error", 191 | "no-underscore-dangle": "error", 192 | "no-unmodified-loop-condition": "error", 193 | "no-unneeded-ternary": "error", 194 | "no-unused-expressions": "error", 195 | "no-use-before-define": "off", 196 | "no-useless-call": "error", 197 | "no-useless-computed-key": "error", 198 | "no-useless-concat": "error", 199 | "no-useless-constructor": "error", 200 | "no-useless-escape": "off", 201 | "no-useless-rename": "error", 202 | "no-useless-return": "error", 203 | "no-var": "off", 204 | "no-void": "error", 205 | "no-warning-comments": "error", 206 | "no-whitespace-before-property": "error", 207 | "no-with": "error", 208 | "object-curly-newline": "off", 209 | "object-curly-spacing": [ 210 | "error", 211 | "always" 212 | ], 213 | "object-property-newline": [ 214 | "error", 215 | { 216 | "allowMultiplePropertiesPerLine": true 217 | } 218 | ], 219 | "object-shorthand": "off", 220 | "one-var": "off", 221 | "one-var-declaration-per-line": "error", 222 | "operator-assignment": [ 223 | "error", 224 | "always" 225 | ], 226 | "operator-linebreak": "error", 227 | "padded-blocks": "off", 228 | "prefer-arrow-callback": "off", 229 | "prefer-const": "error", 230 | "prefer-numeric-literals": "error", 231 | "prefer-reflect": "off", 232 | "prefer-rest-params": "off", 233 | "prefer-spread": "off", 234 | "prefer-template": "off", 235 | "quote-props": "off", 236 | "quotes": "off", 237 | "radix": "error", 238 | "require-await": "error", 239 | "require-jsdoc": "off", 240 | "rest-spread-spacing": "error", 241 | "semi": "off", 242 | "semi-spacing": [ 243 | "error", 244 | { 245 | "after": true, 246 | "before": false 247 | } 248 | ], 249 | "sort-imports": "error", 250 | "sort-keys": "off", 251 | "sort-vars": "error", 252 | "space-before-blocks": "off", 253 | "space-before-function-paren": "off", 254 | "space-in-parens": [ 255 | "error", 256 | "never" 257 | ], 258 | "space-infix-ops": "off", 259 | "space-unary-ops": "error", 260 | "spaced-comment": "error", 261 | "strict": [ 262 | "error", 263 | "never" 264 | ], 265 | "symbol-description": "error", 266 | "template-curly-spacing": "error", 267 | "unicode-bom": [ 268 | "error", 269 | "never" 270 | ], 271 | "valid-jsdoc": "error", 272 | "vars-on-top": "off", 273 | "wrap-iife": "off", 274 | "wrap-regex": "error", 275 | "yield-star-spacing": "error", 276 | "yoda": [ 277 | "error", 278 | "never" 279 | ] 280 | } 281 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # node-waf configuration 18 | .lock-wscript 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directories 24 | node_modules 25 | jspm_packages 26 | 27 | # Optional npm cache directory 28 | .npm 29 | 30 | # Optional REPL history 31 | .node_repl_history 32 | 33 | # IDE 34 | .idea/ 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directories 29 | node_modules 30 | jspm_packages 31 | 32 | # Optional npm cache directory 33 | .npm 34 | 35 | # Optional REPL history 36 | .node_repl_history 37 | 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "8" 5 | - "7" 6 | script: 7 | - npm link 8 | - npm link simplepeg 9 | - npm run test-travis 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SimplePEG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | 3 | [![Join the chat at https://gitter.im/SimplePEG/JavaScript](https://badges.gitter.im/SimplePEG/JavaScript.svg)](https://gitter.im/SimplePEG/JavaScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Greenkeeper badge](https://badges.greenkeeper.io/SimplePEG/JavaScript.svg)](https://greenkeeper.io/) 5 | [![Build Status](https://travis-ci.org/SimplePEG/JavaScript.svg?branch=master)](https://travis-ci.org/SimplePEG/JavaScript) 6 | [![Coverage Status](https://coveralls.io/repos/github/SimplePEG/JavaScript/badge.svg?branch=master)](https://coveralls.io/github/SimplePEG/JavaScript?branch=master) 7 | 8 | JavaScript version ( Browser and Node.js ) of SimplePEG. A very simple implementation of [PEG](https://en.wikipedia.org/wiki/Parsing_expression_grammar) parser generator. 9 | 10 | ``` 11 | const simplepeg = require('simplepeg'); 12 | const parser = new simplepeg.SPEG(); 13 | 14 | parser.parse_grammar('GRAMMAR test a->"A";'); 15 | const ast = parser.parse_text('A'); 16 | console.log(JSON.stringify(ast, null, 4)); 17 | ``` 18 | 19 | # Grammar example 20 | url.peg 21 | ``` 22 | GRAMMAR url 23 | 24 | url -> scheme "://" host pathname search hash?; 25 | scheme -> "http" "s"?; 26 | host -> hostname port?; 27 | hostname -> segment ("." segment)*; 28 | segment -> [a-z0-9-]+; 29 | port -> ":" [0-9]+; 30 | pathname -> "/" [^ ?]*; 31 | search -> ("?" [^ #]*)?; 32 | hash -> "#" [^ ]*; 33 | ``` 34 | -------------------------------------------------------------------------------- /dist/speg.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define("simplepeg", [], factory); 6 | else if(typeof exports === 'object') 7 | exports["simplepeg"] = factory(); 8 | else 9 | root["simplepeg"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { 50 | /******/ configurable: false, 51 | /******/ enumerable: true, 52 | /******/ get: getter 53 | /******/ }); 54 | /******/ } 55 | /******/ }; 56 | /******/ 57 | /******/ // getDefaultExport function for compatibility with non-harmony modules 58 | /******/ __webpack_require__.n = function(module) { 59 | /******/ var getter = module && module.__esModule ? 60 | /******/ function getDefault() { return module['default']; } : 61 | /******/ function getModuleExports() { return module; }; 62 | /******/ __webpack_require__.d(getter, 'a', getter); 63 | /******/ return getter; 64 | /******/ }; 65 | /******/ 66 | /******/ // Object.prototype.hasOwnProperty.call 67 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 68 | /******/ 69 | /******/ // __webpack_public_path__ 70 | /******/ __webpack_require__.p = ""; 71 | /******/ 72 | /******/ // Load entry module and return exports 73 | /******/ return __webpack_require__(__webpack_require__.s = 1); 74 | /******/ }) 75 | /************************************************************************/ 76 | /******/ ([ 77 | /* 0 */ 78 | /***/ (function(module, exports) { 79 | 80 | function get_last_error(state) { 81 | if (state.lastExpectations.length < 1) { 82 | return false; 83 | } 84 | var lines = state.text.split('\n'); 85 | var last_exp_position = state.lastExpectations.reduce(function(a, b){ 86 | return Math.max(a, b.position); 87 | }, state.position); 88 | var dedupedExpectations = state.lastExpectations.filter(function(expectation) { 89 | return expectation.position === last_exp_position; 90 | }).filter(function(expectation, index, array) { 91 | for (var i = 0; i < array.length; i++) { 92 | if (array[i].rule === expectation.rule && i !== index) { 93 | return false; 94 | } 95 | } 96 | return true; 97 | }); 98 | 99 | var last_position = 0; 100 | var line_of_error = ''; 101 | var error_line_number; 102 | var position_of_error = 0; 103 | 104 | for (var i = 0; i < lines.length; i++) { 105 | var line_lenght = lines[i].length + 1; 106 | 107 | if (last_exp_position >= last_position && 108 | last_exp_position < (last_position + line_lenght)) { 109 | line_of_error = lines[i]; 110 | position_of_error = last_exp_position - last_position; 111 | error_line_number = i + 1; 112 | break; 113 | } 114 | 115 | last_position += line_lenght; 116 | } 117 | 118 | var str_error_ln = '' + error_line_number; 119 | var error_ln_length = str_error_ln.length; 120 | var unexpected_char = 'EOF'; 121 | if (last_exp_position < state.text.length) { 122 | unexpected_char = state.text[last_exp_position]; 123 | } 124 | var unexpected = 'Unexpected "' + unexpected_char + '"'; 125 | var expected_rules = dedupedExpectations.map(function(exp){ return exp.rule }); 126 | var expected = ' expected (' + expected_rules.join(' or ') + ')'; 127 | var pointer = (new Array(position_of_error + 3 + error_ln_length)).join('-') + '^'; 128 | var extra = line_of_error + '\n' + pointer; 129 | return unexpected + expected + '\n' + str_error_ln + ': ' + extra; 130 | } 131 | 132 | function string(rule) { 133 | return function(state) { 134 | state.lastExpectations = []; 135 | if (state.text.substr(state.position, rule.length) === rule) { 136 | var ast = { 137 | type: 'string', 138 | match: rule, 139 | start_position: state.position, 140 | end_position: state.position += rule.length 141 | }; 142 | return ast; 143 | } else { 144 | state.lastExpectations = [{ 145 | type: 'string', 146 | rule: rule, 147 | position: state.position 148 | }]; 149 | return false; 150 | } 151 | } 152 | } 153 | 154 | function regex_char(rule) { 155 | return function(state) { 156 | state.lastExpectations = []; 157 | var match = state.text.substr(state.position).match(rule); 158 | if (match && match.index === 0) { 159 | var ast = { 160 | type: 'regex_char', 161 | match: match[0], 162 | start_position: state.position, 163 | end_position: state.position += match[0].length 164 | }; 165 | return ast; 166 | } else { 167 | state.lastExpectations = [{ 168 | type: 'regex_char', 169 | rule: rule, 170 | position: state.position 171 | }]; 172 | return false; 173 | } 174 | } 175 | } 176 | 177 | function sequence(parsers) { 178 | return function(state) { 179 | var asts = []; 180 | var expectations = []; 181 | var start_position = state.position; 182 | for (var i = 0; i < parsers.length; i++) { 183 | var ast = parsers[i](state); 184 | expectations = expectations.concat(state.lastExpectations); 185 | if (ast) { 186 | asts.push(ast); 187 | } else { 188 | state.lastExpectations = expectations; 189 | return false; 190 | } 191 | } 192 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 193 | state.lastExpectations = expectations; 194 | return { 195 | type: 'sequence', 196 | match: match, 197 | children: asts, 198 | start_position: start_position, 199 | end_position: state.position 200 | }; 201 | } 202 | } 203 | 204 | function ordered_choice(parsers) { 205 | return function(state) { 206 | var expectations = []; 207 | var initialState = { 208 | text: state.text, 209 | position: state.position 210 | }; 211 | for (var i = 0; i < parsers.length; i++) { 212 | var ast = parsers[i](state); 213 | if (ast) { 214 | return { 215 | type: 'ordered_choice', 216 | match: ast.match, 217 | children: [ast], 218 | start_position: initialState.position, 219 | end_position: state.position 220 | }; 221 | } else { 222 | state.text = initialState.text; 223 | state.position = initialState.position; 224 | expectations = expectations.concat(state.lastExpectations); 225 | } 226 | } 227 | state.lastExpectations = expectations; 228 | return false; 229 | } 230 | } 231 | 232 | function zero_or_more(parser) { 233 | return function(state) { 234 | var ast = true; 235 | var asts = []; 236 | var start_position = state.position; 237 | while (ast) { 238 | var state_position = state.position; 239 | ast = parser(state); 240 | if (ast) { 241 | asts.push(ast); 242 | } else { 243 | state.position = state_position; 244 | } 245 | } 246 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 247 | return { 248 | type: 'zero_or_more', 249 | match: asts.length > 0 ? match : null, 250 | children: asts, 251 | start_position: start_position, 252 | end_position: state.position 253 | } 254 | } 255 | } 256 | 257 | function one_or_more(parser) { 258 | return function(state) { 259 | var ast = true; 260 | var asts = []; 261 | var start_position = state.position; 262 | while (ast) { 263 | var state_position = state.position; 264 | ast = parser(state); 265 | if (ast) { 266 | asts.push(ast); 267 | } else { 268 | state.position = state_position; 269 | } 270 | } 271 | if (asts.length > 0) { 272 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 273 | return { 274 | type: 'one_or_more', 275 | match: match, 276 | children: asts, 277 | start_position: start_position, 278 | end_position: state.position 279 | } 280 | } else { 281 | return false; 282 | } 283 | } 284 | } 285 | 286 | function optional(parser) { 287 | return function(state) { 288 | var start_position = state.position; 289 | var ast = parser(state); 290 | var asts = []; 291 | if (ast) { 292 | asts.push(ast); 293 | } else { 294 | state.position = start_position; 295 | } 296 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 297 | return { 298 | type: 'optional', 299 | match: asts.length > 0 ? match : null, 300 | children: asts.length > 0 ? asts: null, 301 | start_position: start_position, 302 | end_position: state.position 303 | } 304 | } 305 | } 306 | 307 | function and_predicate(parser) { 308 | return function(state) { 309 | var currentState = { 310 | text: state.text, 311 | position: state.position 312 | }; 313 | var ast = parser(state); 314 | if (ast) { 315 | state.text = currentState.text; 316 | state.position = currentState.position; 317 | return { 318 | type: 'and_predicate', 319 | match: null, 320 | children: [ast], 321 | start_position: state.position, 322 | end_position: state.position 323 | } 324 | } else { 325 | return false; 326 | } 327 | } 328 | } 329 | 330 | function not_predicate(parser) { 331 | return function(state) { 332 | var currentState = { 333 | text: state.text, 334 | position: state.position 335 | }; 336 | var ast = parser(state); 337 | if (ast) { 338 | state.text = currentState.text; 339 | state.position = currentState.position; 340 | state.lastExpectations = [{ 341 | type: 'not_predicate', 342 | children: [ast], 343 | position: state.position 344 | }]; 345 | return false; 346 | } else { 347 | state.lastExpectations = []; 348 | return { 349 | type: 'not_predicate', 350 | match: null, 351 | children: [], 352 | start_position: state.position, 353 | end_position: state.position 354 | }; 355 | } 356 | } 357 | } 358 | 359 | function end_of_file() { 360 | return function(state) { 361 | if (state.text.length === state.position) { 362 | state.lastExpectations = []; 363 | return { 364 | type: 'end_of_file', 365 | match: null, 366 | children: [], 367 | start_position: state.position, 368 | end_position: state.position 369 | } 370 | } else { 371 | state.lastExpectations = [{ 372 | type: 'end_of_file', 373 | rule: 'EOF', 374 | position: state.position 375 | }]; 376 | return false; 377 | } 378 | } 379 | } 380 | 381 | function rec(callback) { 382 | return function() { 383 | return callback().apply(this, arguments) 384 | } 385 | } 386 | 387 | function action(name, func) { 388 | return function(){ 389 | var ast = func.apply(this, arguments); 390 | if (ast) { 391 | ast.action = name; 392 | } 393 | return ast; 394 | } 395 | } 396 | 397 | function call_rule_by_name(name) { 398 | return function(state){ 399 | var rule = state.rules.filter(function(r){ 400 | return r.name === name; 401 | })[0]; 402 | if (!rule) { 403 | throw new Error('failed to find rule by name - ' + name); 404 | } 405 | var ast = rule.parser(state); 406 | return ast; 407 | } 408 | } 409 | 410 | 411 | module.exports = { 412 | get_last_error: get_last_error, 413 | string: string, 414 | regex_char: regex_char, 415 | sequence: sequence, 416 | ordered_choice: ordered_choice, 417 | zero_or_more: zero_or_more, 418 | one_or_more: one_or_more, 419 | optional: optional, 420 | and_predicate: and_predicate, 421 | not_predicate: not_predicate, 422 | end_of_file: end_of_file, 423 | rec: rec, 424 | action: action, 425 | call_rule_by_name: call_rule_by_name 426 | }; 427 | 428 | 429 | /***/ }), 430 | /* 1 */ 431 | /***/ (function(module, exports, __webpack_require__) { 432 | 433 | var SPEG_actions_visitor = __webpack_require__(2).SPEG_actions_visitor; 434 | var SPEG_module_visitor = __webpack_require__(3).SPEG_module_visitor; 435 | var SPEG_parser = __webpack_require__(4); 436 | var rd = __webpack_require__(0); 437 | var ex = __webpack_require__(5); 438 | 439 | function SPEG() { 440 | this.parser = new SPEG_parser(); 441 | this.visitor = new SPEG_actions_visitor(); 442 | this.speg_parser = null; 443 | this.state = null; 444 | } 445 | 446 | SPEG.prototype.parse_grammar = function(grammar) { 447 | this.speg_parser = null; 448 | var speg_ast = this.parser.parse(grammar); 449 | if (speg_ast) { 450 | this.speg_parser = this.visitor.visit(speg_ast); 451 | } else { 452 | throw new ex.GrammarParseError('Failed to parse grammar: \n\n' + this.parser.get_last_error()); 453 | } 454 | }; 455 | 456 | SPEG.prototype.parse_text = function(text) { 457 | if (this.speg_parser) { 458 | var rules = this.speg_parser.children; 459 | var first_rule = rules[0]; 460 | var first_rule_parser = first_rule.parser; 461 | this.state = { text: text, position: 0, rules: rules }; 462 | var ast = first_rule_parser(this.state); 463 | if (ast) { 464 | return ast; 465 | } else { 466 | throw new ex.TextParseError('Failed to parse text: \n\n' + rd.get_last_error(this.state)) 467 | } 468 | } else { 469 | throw Error('You need grammar to parse text. Call parseGrammar first'); 470 | } 471 | }; 472 | 473 | SPEG.prototype.parse = function(grammar, text) { 474 | var speg_ast = this.parser.parse(grammar); 475 | if (speg_ast) { 476 | var generated_parser = this.visitor.visit(speg_ast); 477 | var rules = generated_parser.children; 478 | var first_rule = rules[0]; 479 | var first_rule_parser = first_rule.parser; 480 | this.state = { text: text, position: 0, rules: rules }; 481 | var ast = first_rule_parser(this.state); 482 | if (ast) { 483 | return ast; 484 | } else { 485 | throw new ex.TextParseError('Failed to parse text: \n\n' + rd.get_last_error(this.state)) 486 | } 487 | } else { 488 | throw new ex.GrammarParseError('Failed to parse grammar: \n\n' + this.parser.get_last_error()) 489 | } 490 | }; 491 | 492 | SPEG.prototype.parse_module = function(grammar) { 493 | this.module_visitor = new SPEG_module_visitor(); 494 | var speg_ast = this.parser.parse(grammar); 495 | if (speg_ast) { 496 | return this.module_visitor.visit(speg_ast); 497 | } else { 498 | throw new ex.GrammarParseError('Failed to parse grammar: \n\n' + this.parser.get_last_error()); 499 | } 500 | } 501 | 502 | module.exports = { 503 | SPEG: SPEG, 504 | rd: rd, 505 | TextParseError: ex.TextParseError, 506 | GrammarParseError: ex.GrammarParseError 507 | }; 508 | 509 | 510 | /***/ }), 511 | /* 2 */ 512 | /***/ (function(module, exports, __webpack_require__) { 513 | 514 | var rd = __webpack_require__(0); 515 | 516 | function SPEG_actions_visitor() { 517 | this.actions = new SPEG_actions(); 518 | } 519 | SPEG_actions_visitor.prototype.visit = function(node) { 520 | if (node.children) { 521 | node.children = node.children.map(function(child){ 522 | return this.visit(child); 523 | }, this); 524 | } 525 | if (this.actions && node.action) { 526 | return this.actions[node.action](node); 527 | } 528 | return node; 529 | }; 530 | 531 | function SPEG_actions() {} 532 | 533 | SPEG_actions.prototype.noop = function(node) { 534 | return node; 535 | }; 536 | 537 | SPEG_actions.prototype.peg = function(node) { 538 | return node.children[3]; 539 | }; 540 | 541 | SPEG_actions.prototype.parsing_body = function(node) { 542 | node.children = node.children.map(function(child){ 543 | return child.children[0]; 544 | }); 545 | return node; 546 | }; 547 | 548 | SPEG_actions.prototype.parsing_rule = function(node) { 549 | var rule = node.children[4]; 550 | var ruleName = node.children[0].match; 551 | return { 552 | name: ruleName, 553 | parser: function(state) { 554 | var start = state.position; 555 | var ast = rule(state); 556 | if (ast) { 557 | ast.rule = ruleName; 558 | if (!state.succesfullRules) { 559 | state.succesfullRules = []; 560 | } 561 | state.succesfullRules.push({ 562 | rule: ast.rule, 563 | match: ast.match, 564 | start_position: ast.start_position, 565 | end_position: ast.end_position 566 | }); 567 | } else { 568 | if (!state.failedRules) { 569 | state.failedRules = []; 570 | } 571 | state.failedRules.push({ 572 | rule: ruleName, 573 | start_position: start 574 | }); 575 | } 576 | return ast; 577 | } 578 | } 579 | }; 580 | 581 | SPEG_actions.prototype.parsing_expression = function(node) { 582 | return node.children[0]; 583 | }; 584 | 585 | SPEG_actions.prototype.parsing_sequence = function(node) { 586 | var head = [node.children[0].children[0]]; 587 | var tail = node.children[1].children.map(function(child){ 588 | return child.children[1].children[0]; 589 | }); 590 | return rd.sequence(head.concat(tail)); 591 | }; 592 | 593 | SPEG_actions.prototype.parsing_ordered_choice = function(node) { 594 | var head = [node.children[0]]; 595 | var tail = node.children[1].children.map(function(child){ 596 | return child.children[3]; 597 | }); 598 | return rd.ordered_choice(head.concat(tail)); 599 | }; 600 | 601 | SPEG_actions.prototype.parsing_sub_expression = function(node) { 602 | return function(state) { 603 | var result = node.children[1].children[0].call(this, state); 604 | var tags = node.children[0].children.map(function(tag_node){ 605 | return tag_node.children[0].match; 606 | }); 607 | if (result) { 608 | if (tags.length > 0) { 609 | if (result.tags) { 610 | result.tags = tags.concat(result.tags); 611 | } else { 612 | result.tags = tags; 613 | } 614 | } 615 | } else { 616 | if (!state.failedTags) { 617 | state.failedTags = []; 618 | } 619 | state.failedTags.push.apply(state.failedTags, tags); 620 | } 621 | return result; 622 | } 623 | }; 624 | 625 | SPEG_actions.prototype.parsing_group = function(node) { 626 | return node.children[2]; 627 | }; 628 | 629 | SPEG_actions.prototype.parsing_atomic_expression = function(node) { 630 | return node.children[0]; 631 | }; 632 | 633 | SPEG_actions.prototype.parsing_not_predicate = function(node) { 634 | return rd.not_predicate(node.children[1].children[0]); 635 | }; 636 | 637 | SPEG_actions.prototype.parsing_and_predicate = function(node) { 638 | return rd.and_predicate(node.children[1].children[0]); 639 | }; 640 | 641 | SPEG_actions.prototype.parsing_zero_or_more = function(node) { 642 | return rd.zero_or_more(node.children[0].children[0]); 643 | }; 644 | 645 | SPEG_actions.prototype.parsing_one_or_more = function(node) { 646 | return rd.one_or_more(node.children[0].children[0]); 647 | }; 648 | 649 | SPEG_actions.prototype.parsing_optional = function(node) { 650 | return rd.optional(node.children[0].children[0]); 651 | }; 652 | 653 | SPEG_actions.prototype.parsing_string = function(node) { 654 | return rd.string(node.children[1].match 655 | .replace(/\\\\/g, '\\') 656 | .replace(/\\"/g, '"') 657 | ); 658 | }; 659 | 660 | SPEG_actions.prototype.parsing_regex_char = function(node) { 661 | return rd.regex_char(node.children[0].match); 662 | }; 663 | 664 | SPEG_actions.prototype.parsing_rule_call = function(node) { 665 | return rd.call_rule_by_name(node.match); 666 | }; 667 | 668 | SPEG_actions.prototype.parsing_end_of_file = function() { 669 | return rd.end_of_file(); 670 | }; 671 | 672 | module.exports = { 673 | SPEG_actions_visitor: SPEG_actions_visitor 674 | }; 675 | 676 | 677 | /***/ }), 678 | /* 3 */ 679 | /***/ (function(module, exports, __webpack_require__) { 680 | 681 | var rd = __webpack_require__(0); 682 | 683 | function SPEG_module_visitor() { 684 | this.actions = new SPEG_actions(); 685 | } 686 | SPEG_module_visitor.prototype.visit = function(node) { 687 | this.firstRule = null; 688 | if (node.children) { 689 | node.children = node.children.map(function(child){ 690 | return this.visit(child); 691 | }, this); 692 | } 693 | if (this.actions && node.action) { 694 | return this.actions[node.action](node); 695 | } 696 | return node; 697 | }; 698 | 699 | function SPEG_actions() {} 700 | 701 | SPEG_actions.prototype.noop = function(node) { 702 | return node; 703 | }; 704 | 705 | SPEG_actions.prototype.peg = function(node) { 706 | var module_source = 707 | 'var simplepeg = require(\'simplepeg\');\n' + 708 | 'var rd = simplepeg.rd;\n' + 709 | 'function SPEG() {\n' + 710 | ' this.state = null;\n' + 711 | '}\n' + 712 | 'SPEG.prototype.parse_text = function(text) {\n' + 713 | ' this.state = { text: text, position: 0 };\n' + 714 | ' var ast = ' + this.firstRule + '()(this.state);\n' + 715 | ' if (ast) {\n' + 716 | ' return ast;\n' + 717 | ' } else {\n' + 718 | ' throw new simplepeg.TextParseError(\'Failed to parse text: \\n\\n\' + rd.get_last_error(this.state))\n' + 719 | ' }\n' + 720 | '};\n' + 721 | 'module.exports = {\n' + 722 | ' SPEG: SPEG\n' + 723 | '};\n\n' + node.children[3].join('\n'); 724 | 725 | return module_source; 726 | }; 727 | 728 | SPEG_actions.prototype.parsing_body = function(node) { 729 | node.children = node.children.map(function(child){ 730 | return child.children[0]; 731 | }); 732 | return node.children; 733 | }; 734 | 735 | SPEG_actions.prototype.parsing_rule = function(node) { 736 | var rule = node.children[4]; 737 | var ruleName = node.children[0].match; 738 | if (!this.firstRule) { 739 | this.firstRule = ruleName; 740 | } 741 | return 'function ' + ruleName + '() {\n' + 742 | 'return function(state) {\n' + 743 | 'var start = state.position;\n' + 744 | 'var ast = (' + rule + ')(state);\n' + 745 | 'if (ast) {\n' + 746 | 'ast.rule = "' + ruleName + '";\n' + 747 | 'if (!state.succesfullRules) {\n' + 748 | 'state.succesfullRules = [];\n' + 749 | '}\n' + 750 | 'state.succesfullRules.push({\n' + 751 | 'rule: ast.rule,\n' + 752 | 'match: ast.match,\n' + 753 | 'start_position: ast.start_position,\n' + 754 | 'end_position: ast.end_position\n' + 755 | '});\n' + 756 | '} else {\n' + 757 | 'if (!state.failedRules) {\n' + 758 | 'state.failedRules = [];\n' + 759 | '}\n' + 760 | 'state.failedRules.push({\n' + 761 | 'rule: "' + ruleName + '",\n' + 762 | 'start_position: start\n' + 763 | '});\n' + 764 | '}\n' + 765 | 'return ast;\n' + 766 | '}\n' + 767 | '}'; 768 | }; 769 | 770 | SPEG_actions.prototype.parsing_expression = function(node) { 771 | return node.children[0]; 772 | }; 773 | 774 | SPEG_actions.prototype.parsing_sequence = function(node) { 775 | var head = [node.children[0].children[0]]; 776 | var tail = node.children[1].children.map(function(child){ 777 | return child.children[1].children[0]; 778 | }); 779 | return 'rd.sequence([' + head.concat(tail) + '])'; 780 | }; 781 | 782 | SPEG_actions.prototype.parsing_ordered_choice = function(node) { 783 | var head = [node.children[0]]; 784 | var tail = node.children[1].children.map(function(child){ 785 | return child.children[3]; 786 | }); 787 | return 'rd.ordered_choice([' + head.concat(tail) + '])'; 788 | }; 789 | 790 | SPEG_actions.prototype.parsing_sub_expression = function(node) { 791 | var parser = node.children[1].children[0]; 792 | var tags = node.children[0].children.map(function(tag_node){ 793 | return tag_node.children[0].match; 794 | }); 795 | return 'function(state) {\n' + 796 | 'var ast = ' + parser + '.call(this, state);\n' + 797 | 'var tags = [' + tags + '];\n' + 798 | 'if (ast) {\n' + 799 | ' if (tags.length > 0) {\n' + 800 | ' if (ast.tags) {\n' + 801 | ' ast.tags = tags.concat(ast.tags);\n' + 802 | ' } else {\n' + 803 | ' ast.tags = tags;\n' + 804 | ' }\n' + 805 | ' }\n' + 806 | '} else {\n' + 807 | ' if (!state.failedTags) {\n' + 808 | ' state.failedTags = [];\n' + 809 | ' }\n' + 810 | ' state.failedTags.push.apply(state.failedTags, tags);\n' + 811 | '}\n' + 812 | 'return ast;\n' + 813 | '}' 814 | }; 815 | 816 | SPEG_actions.prototype.parsing_group = function(node) { 817 | return node.children[2]; 818 | }; 819 | 820 | SPEG_actions.prototype.parsing_atomic_expression = function(node) { 821 | return node.children[0]; 822 | }; 823 | 824 | SPEG_actions.prototype.parsing_not_predicate = function(node) { 825 | return 'rd.not_predicate(' + node.children[1].children[0] + ')'; 826 | }; 827 | 828 | SPEG_actions.prototype.parsing_and_predicate = function(node) { 829 | return 'rd.and_predicate(' + node.children[1].children[0] + ')'; 830 | }; 831 | 832 | SPEG_actions.prototype.parsing_zero_or_more = function(node) { 833 | return 'rd.zero_or_more(' + node.children[0].children[0] + ')'; 834 | }; 835 | 836 | SPEG_actions.prototype.parsing_one_or_more = function(node) { 837 | return 'rd.one_or_more(' + node.children[0].children[0] + ')'; 838 | }; 839 | 840 | SPEG_actions.prototype.parsing_optional = function(node) { 841 | return 'rd.optional(' + node.children[0].children[0] + ')'; 842 | }; 843 | 844 | SPEG_actions.prototype.parsing_string = function(node) { 845 | return 'rd.string("' + node.children[1].match + '")'; 846 | }; 847 | 848 | SPEG_actions.prototype.parsing_regex_char = function(node) { 849 | return 'rd.regex_char(/' + node.children[0].match + '/)'; 850 | }; 851 | 852 | SPEG_actions.prototype.parsing_rule_call = function(node) { 853 | return 'rd.rec(' + node.match + ')'; 854 | }; 855 | 856 | SPEG_actions.prototype.parsing_end_of_file = function() { 857 | return 'rd.end_of_file()'; 858 | }; 859 | 860 | module.exports = { 861 | SPEG_module_visitor: SPEG_module_visitor 862 | }; 863 | 864 | 865 | /***/ }), 866 | /* 4 */ 867 | /***/ (function(module, exports, __webpack_require__) { 868 | 869 | var rd = __webpack_require__(0); 870 | 871 | function peg() { 872 | return rd.action('peg', rd.sequence([ 873 | rd.zero_or_more(_()), 874 | parsing_header(), 875 | rd.one_or_more(_()), 876 | parsing_body(), 877 | rd.end_of_file() 878 | ])); 879 | } 880 | 881 | function parsing_header() { 882 | return rd.action('noop',rd.sequence([ 883 | rd.string('GRAMMAR'), 884 | rd.one_or_more(_()), 885 | rd.one_or_more(parsing_rule_name()) 886 | ])); 887 | } 888 | 889 | function parsing_body() { 890 | return rd.action('parsing_body', rd.one_or_more(rd.ordered_choice([ 891 | parsing_rule(), 892 | rd.one_or_more(_()) 893 | ]))); 894 | } 895 | 896 | function parsing_rule() { 897 | return rd.action('parsing_rule', rd.sequence([ 898 | parsing_rule_name(), 899 | rd.zero_or_more(_()), 900 | rd.string('->'), 901 | rd.zero_or_more(_()), 902 | parsing_expression(), 903 | rd.zero_or_more(_()), 904 | rd.string(';'), 905 | rd.zero_or_more(_()) 906 | ])); 907 | } 908 | 909 | function parsing_rule_name() { 910 | return rd.action('noop', rd.sequence([ 911 | rd.regex_char('[a-zA-Z_]'), 912 | rd.zero_or_more(rd.regex_char('[a-zA-Z0-9_]')) 913 | ])); 914 | } 915 | 916 | function parsing_expression() { 917 | return rd.action('parsing_expression', rd.ordered_choice([ 918 | parsing_sequence(), 919 | parsing_ordered_choice(), 920 | parsing_sub_expression() 921 | ])); 922 | } 923 | 924 | function parsing_sequence() { 925 | return rd.action('parsing_sequence', rd.sequence([ 926 | rd.ordered_choice([ 927 | parsing_ordered_choice(), 928 | parsing_sub_expression() 929 | ]), 930 | rd.one_or_more(rd.sequence([ 931 | rd.one_or_more(_()), 932 | rd.ordered_choice([ 933 | parsing_ordered_choice(), 934 | parsing_sub_expression() 935 | ]) 936 | ])) 937 | ])); 938 | } 939 | 940 | function parsing_ordered_choice() { 941 | return rd.action('parsing_ordered_choice', rd.sequence([ 942 | parsing_sub_expression(), 943 | rd.one_or_more(rd.sequence([ 944 | rd.zero_or_more(_()), 945 | rd.string('\/'), 946 | rd.zero_or_more(_()), 947 | parsing_sub_expression() 948 | ])) 949 | ])); 950 | } 951 | 952 | function parsing_sub_expression() { 953 | return rd.action('parsing_sub_expression', rd.sequence([ 954 | rd.zero_or_more(rd.sequence([ 955 | tag(), 956 | rd.string(':') 957 | ])), 958 | rd.ordered_choice([ 959 | parsing_not_predicate(), 960 | parsing_and_predicate(), 961 | parsing_optional(), 962 | parsing_one_or_more(), 963 | parsing_zero_or_more(), 964 | parsing_group(), 965 | parsing_atomic_expression() 966 | ]) 967 | ])); 968 | } 969 | 970 | function tag() { 971 | return rd.action('noop', rd.sequence([ 972 | rd.regex_char('[a-zA-Z_]'), 973 | rd.zero_or_more(rd.regex_char('[a-zA-Z0-9_]')) 974 | ])); 975 | } 976 | 977 | function parsing_group() { 978 | return rd.action('parsing_group', rd.sequence([ 979 | rd.string('('), 980 | rd.zero_or_more(_()), 981 | rd.rec(parsing_expression), 982 | rd.zero_or_more(_()), 983 | rd.string(')') 984 | ])); 985 | } 986 | 987 | function parsing_atomic_expression() { 988 | return rd.action('parsing_atomic_expression', rd.ordered_choice([ 989 | parsing_string(), 990 | parsing_regex_char(), 991 | parsing_eof(), 992 | parsing_rule_call() 993 | ])); 994 | } 995 | 996 | function parsing_not_predicate() { 997 | return rd.action('parsing_not_predicate', rd.sequence([ 998 | rd.string('!'), 999 | rd.ordered_choice([ 1000 | parsing_group(), 1001 | parsing_atomic_expression() 1002 | ]) 1003 | ])); 1004 | } 1005 | 1006 | function parsing_and_predicate() { 1007 | return rd.action('parsing_and_predicate', rd.sequence([ 1008 | rd.string('&'), 1009 | rd.ordered_choice([ 1010 | parsing_group(), 1011 | parsing_atomic_expression() 1012 | ]) 1013 | ])); 1014 | } 1015 | 1016 | function parsing_zero_or_more() { 1017 | return rd.action('parsing_zero_or_more', rd.sequence([ 1018 | rd.ordered_choice([ 1019 | parsing_group(), 1020 | parsing_atomic_expression() 1021 | ]), 1022 | rd.string('*') 1023 | ])); 1024 | } 1025 | 1026 | function parsing_one_or_more() { 1027 | return rd.action('parsing_one_or_more', rd.sequence([ 1028 | rd.ordered_choice([ 1029 | parsing_group(), 1030 | parsing_atomic_expression() 1031 | ]), 1032 | rd.string('+') 1033 | ])); 1034 | } 1035 | 1036 | function parsing_optional() { 1037 | return rd.action('parsing_optional', rd.sequence([ 1038 | rd.ordered_choice([ 1039 | parsing_group(), 1040 | parsing_atomic_expression() 1041 | ]), 1042 | rd.string('?') 1043 | ])); 1044 | } 1045 | 1046 | function parsing_rule_call() { 1047 | return rd.action('parsing_rule_call', parsing_rule_name()) 1048 | } 1049 | 1050 | function parsing_string() { 1051 | return rd.action('parsing_string', rd.sequence([ 1052 | rd.string('"'), 1053 | rd.one_or_more(rd.ordered_choice([ 1054 | rd.string('\\\\'), 1055 | rd.string('\\"'), 1056 | rd.regex_char('[^"]') 1057 | ])), 1058 | rd.string('"') 1059 | ])); 1060 | } 1061 | 1062 | function parsing_regex_char() { 1063 | return rd.action('parsing_regex_char', rd.ordered_choice([ 1064 | rd.sequence([ 1065 | rd.string('['), 1066 | rd.optional(rd.string('^')), 1067 | rd.one_or_more(rd.ordered_choice([ 1068 | rd.string('\\]'), 1069 | rd.string('\\['), 1070 | rd.regex_char('[^\\]]') 1071 | ])), 1072 | rd.string(']') 1073 | ]), 1074 | rd.string('.') 1075 | ])); 1076 | } 1077 | 1078 | function parsing_eof(){ 1079 | return rd.action('parsing_end_of_file', rd.string("EOF")) 1080 | } 1081 | 1082 | function _() { 1083 | return rd.action('noop', rd.regex_char('[\\s]')) 1084 | } 1085 | 1086 | function SPEG_parser() { 1087 | this.parser = peg(); 1088 | } 1089 | 1090 | SPEG_parser.prototype.parse = function(text) { 1091 | this.state = { 1092 | text: text, 1093 | position: 0 1094 | }; 1095 | var ast = this.parser(this.state); 1096 | return ast; 1097 | }; 1098 | 1099 | SPEG_parser.prototype.getLastExpectations = function() { 1100 | return this.state.lastExpectations 1101 | }; 1102 | 1103 | SPEG_parser.prototype.get_last_error = function() { 1104 | return rd.get_last_error(this.state); 1105 | }; 1106 | 1107 | module.exports = SPEG_parser; 1108 | 1109 | 1110 | /***/ }), 1111 | /* 5 */ 1112 | /***/ (function(module, exports) { 1113 | 1114 | function GrammarParseError() { 1115 | var temp = Error.apply(this, arguments); 1116 | temp.name = this.name = 'GrammarParseError'; 1117 | this.stack = temp.stack; 1118 | this.message = temp.message; 1119 | } 1120 | GrammarParseError.prototype = Object.create(Error.prototype, { 1121 | constructor: { 1122 | value: GrammarParseError, 1123 | writable: true, 1124 | configurable: true 1125 | } 1126 | }); 1127 | 1128 | function TextParseError() { 1129 | var temp = Error.apply(this, arguments); 1130 | temp.name = this.name = 'TextParseError'; 1131 | this.stack = temp.stack; 1132 | this.message = temp.message; 1133 | } 1134 | TextParseError.prototype = Object.create(Error.prototype, { 1135 | constructor: { 1136 | value: TextParseError, 1137 | writable: true, 1138 | configurable: true 1139 | } 1140 | }); 1141 | 1142 | 1143 | module.exports = { 1144 | GrammarParseError: GrammarParseError, 1145 | TextParseError: TextParseError 1146 | } 1147 | 1148 | 1149 | /***/ }) 1150 | /******/ ]); 1151 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplepeg", 3 | "version": "1.2.0", 4 | "description": "JavaScript version ( Browser and Node.js ) of SimplePEG", 5 | "main": "src/speg.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --timeout 120000 -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", 11 | "test": "mocha test --timeout 120000", 12 | "prebuild": "npm run lint; npm test", 13 | "lint": "eslint src/", 14 | "build": "webpack --config webpack.config.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/SimplePEG/JavaScript.git" 19 | }, 20 | "keywords": [ 21 | "PEG", 22 | "parser", 23 | "grammar" 24 | ], 25 | "author": "Oleksii Okhrymenko (aka aiboy)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/SimplePEG/JavaScript/issues" 29 | }, 30 | "homepage": "https://github.com/SimplePEG/JavaScript#readme", 31 | "devDependencies": { 32 | "chai": "^4.1.0", 33 | "coveralls": "^3.0.0", 34 | "eslint": "^6.0.0", 35 | "istanbul": "^0.4.4", 36 | "mocha": "^5.0.0", 37 | "mocha-lcov-reporter": "^1.2.0", 38 | "recursive-readdir-sync": "^1.0.6", 39 | "webpack": "^4.0.0", 40 | "mockery": "^2.1.0" 41 | }, 42 | "dependencies": { 43 | "@bundle-analyzer/webpack-plugin": "^0.4.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/exceptions.js: -------------------------------------------------------------------------------- 1 | function GrammarParseError() { 2 | var temp = Error.apply(this, arguments); 3 | temp.name = this.name = 'GrammarParseError'; 4 | this.stack = temp.stack; 5 | this.message = temp.message; 6 | } 7 | GrammarParseError.prototype = Object.create(Error.prototype, { 8 | constructor: { 9 | value: GrammarParseError, 10 | writable: true, 11 | configurable: true 12 | } 13 | }); 14 | 15 | function TextParseError() { 16 | var temp = Error.apply(this, arguments); 17 | temp.name = this.name = 'TextParseError'; 18 | this.stack = temp.stack; 19 | this.message = temp.message; 20 | } 21 | TextParseError.prototype = Object.create(Error.prototype, { 22 | constructor: { 23 | value: TextParseError, 24 | writable: true, 25 | configurable: true 26 | } 27 | }); 28 | 29 | 30 | module.exports = { 31 | GrammarParseError: GrammarParseError, 32 | TextParseError: TextParseError 33 | } 34 | -------------------------------------------------------------------------------- /src/rd_parser.js: -------------------------------------------------------------------------------- 1 | function get_last_error(state) { 2 | if (state.lastExpectations.length < 1) { 3 | return false; 4 | } 5 | var lines = state.text.split('\n'); 6 | var last_exp_position = state.lastExpectations.reduce(function(a, b){ 7 | return Math.max(a, b.position); 8 | }, state.position); 9 | var dedupedExpectations = state.lastExpectations.filter(function(expectation) { 10 | return expectation.position === last_exp_position; 11 | }).filter(function(expectation, index, array) { 12 | for (var i = 0; i < array.length; i++) { 13 | if (array[i].rule === expectation.rule && i !== index) { 14 | return false; 15 | } 16 | } 17 | return true; 18 | }); 19 | 20 | var last_position = 0; 21 | var line_of_error = ''; 22 | var error_line_number; 23 | var position_of_error = 0; 24 | 25 | for (var i = 0; i < lines.length; i++) { 26 | var line_lenght = lines[i].length + 1; 27 | 28 | if (last_exp_position >= last_position && 29 | last_exp_position < (last_position + line_lenght)) { 30 | line_of_error = lines[i]; 31 | position_of_error = last_exp_position - last_position; 32 | error_line_number = i + 1; 33 | break; 34 | } 35 | 36 | last_position += line_lenght; 37 | } 38 | 39 | var str_error_ln = '' + error_line_number; 40 | var error_ln_length = str_error_ln.length; 41 | var unexpected_char = 'EOF'; 42 | if (last_exp_position < state.text.length) { 43 | unexpected_char = state.text[last_exp_position]; 44 | } 45 | var unexpected = 'Unexpected "' + unexpected_char + '"'; 46 | var expected_rules = dedupedExpectations.map(function(exp){ return exp.rule }); 47 | var expected = ' expected (' + expected_rules.join(' or ') + ')'; 48 | var pointer = (new Array(position_of_error + 3 + error_ln_length)).join('-') + '^'; 49 | var extra = line_of_error + '\n' + pointer; 50 | return unexpected + expected + '\n' + str_error_ln + ': ' + extra; 51 | } 52 | 53 | function string(rule) { 54 | return function(state) { 55 | state.lastExpectations = []; 56 | if (state.text.substr(state.position, rule.length) === rule) { 57 | var ast = { 58 | type: 'string', 59 | match: rule, 60 | start_position: state.position, 61 | end_position: state.position += rule.length 62 | }; 63 | return ast; 64 | } else { 65 | state.lastExpectations = [{ 66 | type: 'string', 67 | rule: rule, 68 | position: state.position 69 | }]; 70 | return false; 71 | } 72 | } 73 | } 74 | 75 | function regex_char(rule) { 76 | return function(state) { 77 | state.lastExpectations = []; 78 | var match = state.text.substr(state.position).match(rule); 79 | if (match && match.index === 0) { 80 | var ast = { 81 | type: 'regex_char', 82 | match: match[0], 83 | start_position: state.position, 84 | end_position: state.position += match[0].length 85 | }; 86 | return ast; 87 | } else { 88 | state.lastExpectations = [{ 89 | type: 'regex_char', 90 | rule: rule, 91 | position: state.position 92 | }]; 93 | return false; 94 | } 95 | } 96 | } 97 | 98 | function sequence(parsers) { 99 | return function(state) { 100 | var asts = []; 101 | var expectations = []; 102 | var start_position = state.position; 103 | for (var i = 0; i < parsers.length; i++) { 104 | var ast = parsers[i](state); 105 | expectations = expectations.concat(state.lastExpectations); 106 | if (ast) { 107 | asts.push(ast); 108 | } else { 109 | state.lastExpectations = expectations; 110 | return false; 111 | } 112 | } 113 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 114 | state.lastExpectations = expectations; 115 | return { 116 | type: 'sequence', 117 | match: match, 118 | children: asts, 119 | start_position: start_position, 120 | end_position: state.position 121 | }; 122 | } 123 | } 124 | 125 | function ordered_choice(parsers) { 126 | return function(state) { 127 | var expectations = []; 128 | var initialState = { 129 | text: state.text, 130 | position: state.position 131 | }; 132 | for (var i = 0; i < parsers.length; i++) { 133 | var ast = parsers[i](state); 134 | if (ast) { 135 | return { 136 | type: 'ordered_choice', 137 | match: ast.match, 138 | children: [ast], 139 | start_position: initialState.position, 140 | end_position: state.position 141 | }; 142 | } else { 143 | state.text = initialState.text; 144 | state.position = initialState.position; 145 | expectations = expectations.concat(state.lastExpectations); 146 | } 147 | } 148 | state.lastExpectations = expectations; 149 | return false; 150 | } 151 | } 152 | 153 | function zero_or_more(parser) { 154 | return function(state) { 155 | var ast = true; 156 | var asts = []; 157 | var start_position = state.position; 158 | while (ast) { 159 | var state_position = state.position; 160 | ast = parser(state); 161 | if (ast) { 162 | asts.push(ast); 163 | } else { 164 | state.position = state_position; 165 | } 166 | } 167 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 168 | return { 169 | type: 'zero_or_more', 170 | match: asts.length > 0 ? match : null, 171 | children: asts, 172 | start_position: start_position, 173 | end_position: state.position 174 | } 175 | } 176 | } 177 | 178 | function one_or_more(parser) { 179 | return function(state) { 180 | var ast = true; 181 | var asts = []; 182 | var start_position = state.position; 183 | while (ast) { 184 | var state_position = state.position; 185 | ast = parser(state); 186 | if (ast) { 187 | asts.push(ast); 188 | } else { 189 | state.position = state_position; 190 | } 191 | } 192 | if (asts.length > 0) { 193 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 194 | return { 195 | type: 'one_or_more', 196 | match: match, 197 | children: asts, 198 | start_position: start_position, 199 | end_position: state.position 200 | } 201 | } else { 202 | return false; 203 | } 204 | } 205 | } 206 | 207 | function optional(parser) { 208 | return function(state) { 209 | var start_position = state.position; 210 | var ast = parser(state); 211 | var asts = []; 212 | if (ast) { 213 | asts.push(ast); 214 | } else { 215 | state.position = start_position; 216 | } 217 | var match = asts.reduce(function(r, n){ return r + (n.match || '') }, ''); 218 | return { 219 | type: 'optional', 220 | match: asts.length > 0 ? match : null, 221 | children: asts.length > 0 ? asts: null, 222 | start_position: start_position, 223 | end_position: state.position 224 | } 225 | } 226 | } 227 | 228 | function and_predicate(parser) { 229 | return function(state) { 230 | var currentState = { 231 | text: state.text, 232 | position: state.position 233 | }; 234 | var ast = parser(state); 235 | if (ast) { 236 | state.text = currentState.text; 237 | state.position = currentState.position; 238 | return { 239 | type: 'and_predicate', 240 | match: null, 241 | children: [ast], 242 | start_position: state.position, 243 | end_position: state.position 244 | } 245 | } else { 246 | return false; 247 | } 248 | } 249 | } 250 | 251 | function not_predicate(parser) { 252 | return function(state) { 253 | var currentState = { 254 | text: state.text, 255 | position: state.position 256 | }; 257 | var ast = parser(state); 258 | if (ast) { 259 | state.text = currentState.text; 260 | state.position = currentState.position; 261 | state.lastExpectations = [{ 262 | type: 'not_predicate', 263 | children: [ast], 264 | position: state.position 265 | }]; 266 | return false; 267 | } else { 268 | state.lastExpectations = []; 269 | return { 270 | type: 'not_predicate', 271 | match: null, 272 | children: [], 273 | start_position: state.position, 274 | end_position: state.position 275 | }; 276 | } 277 | } 278 | } 279 | 280 | function end_of_file() { 281 | return function(state) { 282 | if (state.text.length === state.position) { 283 | state.lastExpectations = []; 284 | return { 285 | type: 'end_of_file', 286 | match: null, 287 | children: [], 288 | start_position: state.position, 289 | end_position: state.position 290 | } 291 | } else { 292 | state.lastExpectations = [{ 293 | type: 'end_of_file', 294 | rule: 'EOF', 295 | position: state.position 296 | }]; 297 | return false; 298 | } 299 | } 300 | } 301 | 302 | function rec(callback) { 303 | return function() { 304 | return callback().apply(this, arguments) 305 | } 306 | } 307 | 308 | function action(name, func) { 309 | return function(){ 310 | var ast = func.apply(this, arguments); 311 | if (ast) { 312 | ast.action = name; 313 | } 314 | return ast; 315 | } 316 | } 317 | 318 | function call_rule_by_name(name) { 319 | return function(state){ 320 | var rule = state.rules.filter(function(r){ 321 | return r.name === name; 322 | })[0]; 323 | if (!rule) { 324 | throw new Error('failed to find rule by name - ' + name); 325 | } 326 | var ast = rule.parser(state); 327 | return ast; 328 | } 329 | } 330 | 331 | 332 | module.exports = { 333 | get_last_error: get_last_error, 334 | string: string, 335 | regex_char: regex_char, 336 | sequence: sequence, 337 | ordered_choice: ordered_choice, 338 | zero_or_more: zero_or_more, 339 | one_or_more: one_or_more, 340 | optional: optional, 341 | and_predicate: and_predicate, 342 | not_predicate: not_predicate, 343 | end_of_file: end_of_file, 344 | rec: rec, 345 | action: action, 346 | call_rule_by_name: call_rule_by_name 347 | }; 348 | -------------------------------------------------------------------------------- /src/speg.js: -------------------------------------------------------------------------------- 1 | var SPEG_actions_visitor = require('./speg_visitor').SPEG_actions_visitor; 2 | var SPEG_module_visitor = require('./speg_module_visitor').SPEG_module_visitor; 3 | var SPEG_parser = require('./speg_parser'); 4 | var rd = require('./rd_parser'); 5 | var ex = require('./exceptions'); 6 | 7 | function SPEG() { 8 | this.parser = new SPEG_parser(); 9 | this.visitor = new SPEG_actions_visitor(); 10 | this.speg_parser = null; 11 | this.state = null; 12 | } 13 | 14 | SPEG.prototype.parse_grammar = function(grammar) { 15 | this.speg_parser = null; 16 | var speg_ast = this.parser.parse(grammar); 17 | if (speg_ast) { 18 | this.speg_parser = this.visitor.visit(speg_ast); 19 | } else { 20 | throw new ex.GrammarParseError('Failed to parse grammar: \n\n' + this.parser.get_last_error()); 21 | } 22 | }; 23 | 24 | SPEG.prototype.parse_text = function(text) { 25 | if (this.speg_parser) { 26 | var rules = this.speg_parser.children; 27 | var first_rule = rules[0]; 28 | var first_rule_parser = first_rule.parser; 29 | this.state = { text: text, position: 0, rules: rules }; 30 | var ast = first_rule_parser(this.state); 31 | if (ast) { 32 | return ast; 33 | } else { 34 | throw new ex.TextParseError('Failed to parse text: \n\n' + rd.get_last_error(this.state)) 35 | } 36 | } else { 37 | throw Error('You need grammar to parse text. Call parseGrammar first'); 38 | } 39 | }; 40 | 41 | SPEG.prototype.parse = function(grammar, text) { 42 | var speg_ast = this.parser.parse(grammar); 43 | if (speg_ast) { 44 | var generated_parser = this.visitor.visit(speg_ast); 45 | var rules = generated_parser.children; 46 | var first_rule = rules[0]; 47 | var first_rule_parser = first_rule.parser; 48 | this.state = { text: text, position: 0, rules: rules }; 49 | var ast = first_rule_parser(this.state); 50 | if (ast) { 51 | return ast; 52 | } else { 53 | throw new ex.TextParseError('Failed to parse text: \n\n' + rd.get_last_error(this.state)) 54 | } 55 | } else { 56 | throw new ex.GrammarParseError('Failed to parse grammar: \n\n' + this.parser.get_last_error()) 57 | } 58 | }; 59 | 60 | SPEG.prototype.parse_module = function(grammar) { 61 | this.module_visitor = new SPEG_module_visitor(); 62 | var speg_ast = this.parser.parse(grammar); 63 | if (speg_ast) { 64 | return this.module_visitor.visit(speg_ast); 65 | } else { 66 | throw new ex.GrammarParseError('Failed to parse grammar: \n\n' + this.parser.get_last_error()); 67 | } 68 | } 69 | 70 | module.exports = { 71 | SPEG: SPEG, 72 | rd: rd, 73 | TextParseError: ex.TextParseError, 74 | GrammarParseError: ex.GrammarParseError 75 | }; 76 | -------------------------------------------------------------------------------- /src/speg_module_visitor.js: -------------------------------------------------------------------------------- 1 | var rd = require('./rd_parser'); 2 | 3 | function SPEG_module_visitor() { 4 | this.actions = new SPEG_actions(); 5 | } 6 | SPEG_module_visitor.prototype.visit = function(node) { 7 | this.firstRule = null; 8 | if (node.children) { 9 | node.children = node.children.map(function(child){ 10 | return this.visit(child); 11 | }, this); 12 | } 13 | if (this.actions && node.action) { 14 | return this.actions[node.action](node); 15 | } 16 | return node; 17 | }; 18 | 19 | function SPEG_actions() {} 20 | 21 | SPEG_actions.prototype.noop = function(node) { 22 | return node; 23 | }; 24 | 25 | SPEG_actions.prototype.peg = function(node) { 26 | var module_source = 27 | 'var simplepeg = require(\'simplepeg\');\n' + 28 | 'var rd = simplepeg.rd;\n' + 29 | 'function SPEG() {\n' + 30 | ' this.state = null;\n' + 31 | '}\n' + 32 | 'SPEG.prototype.parse_text = function(text) {\n' + 33 | ' this.state = { text: text, position: 0 };\n' + 34 | ' var ast = ' + this.firstRule + '()(this.state);\n' + 35 | ' if (ast) {\n' + 36 | ' return ast;\n' + 37 | ' } else {\n' + 38 | ' throw new simplepeg.TextParseError(\'Failed to parse text: \\n\\n\' + rd.get_last_error(this.state))\n' + 39 | ' }\n' + 40 | '};\n' + 41 | 'module.exports = {\n' + 42 | ' SPEG: SPEG\n' + 43 | '};\n\n' + node.children[3].join('\n'); 44 | 45 | return module_source; 46 | }; 47 | 48 | SPEG_actions.prototype.parsing_body = function(node) { 49 | node.children = node.children.map(function(child){ 50 | return child.children[0]; 51 | }); 52 | return node.children; 53 | }; 54 | 55 | SPEG_actions.prototype.parsing_rule = function(node) { 56 | var rule = node.children[4]; 57 | var ruleName = node.children[0].match; 58 | if (!this.firstRule) { 59 | this.firstRule = ruleName; 60 | } 61 | return 'function ' + ruleName + '() {\n' + 62 | 'return function(state) {\n' + 63 | 'var start = state.position;\n' + 64 | 'var ast = (' + rule + ')(state);\n' + 65 | 'if (ast) {\n' + 66 | 'ast.rule = "' + ruleName + '";\n' + 67 | 'if (!state.succesfullRules) {\n' + 68 | 'state.succesfullRules = [];\n' + 69 | '}\n' + 70 | 'state.succesfullRules.push({\n' + 71 | 'rule: ast.rule,\n' + 72 | 'match: ast.match,\n' + 73 | 'start_position: ast.start_position,\n' + 74 | 'end_position: ast.end_position\n' + 75 | '});\n' + 76 | '} else {\n' + 77 | 'if (!state.failedRules) {\n' + 78 | 'state.failedRules = [];\n' + 79 | '}\n' + 80 | 'state.failedRules.push({\n' + 81 | 'rule: "' + ruleName + '",\n' + 82 | 'start_position: start\n' + 83 | '});\n' + 84 | '}\n' + 85 | 'return ast;\n' + 86 | '}\n' + 87 | '}'; 88 | }; 89 | 90 | SPEG_actions.prototype.parsing_expression = function(node) { 91 | return node.children[0]; 92 | }; 93 | 94 | SPEG_actions.prototype.parsing_sequence = function(node) { 95 | var head = [node.children[0].children[0]]; 96 | var tail = node.children[1].children.map(function(child){ 97 | return child.children[1].children[0]; 98 | }); 99 | return 'rd.sequence([' + head.concat(tail) + '])'; 100 | }; 101 | 102 | SPEG_actions.prototype.parsing_ordered_choice = function(node) { 103 | var head = [node.children[0]]; 104 | var tail = node.children[1].children.map(function(child){ 105 | return child.children[3]; 106 | }); 107 | return 'rd.ordered_choice([' + head.concat(tail) + '])'; 108 | }; 109 | 110 | SPEG_actions.prototype.parsing_sub_expression = function(node) { 111 | var parser = node.children[1].children[0]; 112 | var tags = node.children[0].children.map(function(tag_node){ 113 | return tag_node.children[0].match; 114 | }); 115 | return 'function(state) {\n' + 116 | 'var ast = ' + parser + '.call(this, state);\n' + 117 | 'var tags = [' + tags + '];\n' + 118 | 'if (ast) {\n' + 119 | ' if (tags.length > 0) {\n' + 120 | ' if (ast.tags) {\n' + 121 | ' ast.tags = tags.concat(ast.tags);\n' + 122 | ' } else {\n' + 123 | ' ast.tags = tags;\n' + 124 | ' }\n' + 125 | ' }\n' + 126 | '} else {\n' + 127 | ' if (!state.failedTags) {\n' + 128 | ' state.failedTags = [];\n' + 129 | ' }\n' + 130 | ' state.failedTags.push.apply(state.failedTags, tags);\n' + 131 | '}\n' + 132 | 'return ast;\n' + 133 | '}' 134 | }; 135 | 136 | SPEG_actions.prototype.parsing_group = function(node) { 137 | return node.children[2]; 138 | }; 139 | 140 | SPEG_actions.prototype.parsing_atomic_expression = function(node) { 141 | return node.children[0]; 142 | }; 143 | 144 | SPEG_actions.prototype.parsing_not_predicate = function(node) { 145 | return 'rd.not_predicate(' + node.children[1].children[0] + ')'; 146 | }; 147 | 148 | SPEG_actions.prototype.parsing_and_predicate = function(node) { 149 | return 'rd.and_predicate(' + node.children[1].children[0] + ')'; 150 | }; 151 | 152 | SPEG_actions.prototype.parsing_zero_or_more = function(node) { 153 | return 'rd.zero_or_more(' + node.children[0].children[0] + ')'; 154 | }; 155 | 156 | SPEG_actions.prototype.parsing_one_or_more = function(node) { 157 | return 'rd.one_or_more(' + node.children[0].children[0] + ')'; 158 | }; 159 | 160 | SPEG_actions.prototype.parsing_optional = function(node) { 161 | return 'rd.optional(' + node.children[0].children[0] + ')'; 162 | }; 163 | 164 | SPEG_actions.prototype.parsing_string = function(node) { 165 | return 'rd.string("' + node.children[1].match + '")'; 166 | }; 167 | 168 | SPEG_actions.prototype.parsing_regex_char = function(node) { 169 | return 'rd.regex_char(/' + node.children[0].match + '/)'; 170 | }; 171 | 172 | SPEG_actions.prototype.parsing_rule_call = function(node) { 173 | return 'rd.rec(' + node.match + ')'; 174 | }; 175 | 176 | SPEG_actions.prototype.parsing_end_of_file = function() { 177 | return 'rd.end_of_file()'; 178 | }; 179 | 180 | module.exports = { 181 | SPEG_module_visitor: SPEG_module_visitor 182 | }; 183 | -------------------------------------------------------------------------------- /src/speg_parser.js: -------------------------------------------------------------------------------- 1 | var rd = require('./rd_parser'); 2 | 3 | function peg() { 4 | return rd.action('peg', rd.sequence([ 5 | rd.zero_or_more(_()), 6 | parsing_header(), 7 | rd.one_or_more(_()), 8 | parsing_body(), 9 | rd.end_of_file() 10 | ])); 11 | } 12 | 13 | function parsing_header() { 14 | return rd.action('noop',rd.sequence([ 15 | rd.string('GRAMMAR'), 16 | rd.one_or_more(_()), 17 | rd.one_or_more(parsing_rule_name()) 18 | ])); 19 | } 20 | 21 | function parsing_body() { 22 | return rd.action('parsing_body', rd.one_or_more(rd.ordered_choice([ 23 | parsing_rule(), 24 | rd.one_or_more(_()) 25 | ]))); 26 | } 27 | 28 | function parsing_rule() { 29 | return rd.action('parsing_rule', rd.sequence([ 30 | parsing_rule_name(), 31 | rd.zero_or_more(_()), 32 | rd.string('->'), 33 | rd.zero_or_more(_()), 34 | parsing_expression(), 35 | rd.zero_or_more(_()), 36 | rd.string(';'), 37 | rd.zero_or_more(_()) 38 | ])); 39 | } 40 | 41 | function parsing_rule_name() { 42 | return rd.action('noop', rd.sequence([ 43 | rd.regex_char('[a-zA-Z_]'), 44 | rd.zero_or_more(rd.regex_char('[a-zA-Z0-9_]')) 45 | ])); 46 | } 47 | 48 | function parsing_expression() { 49 | return rd.action('parsing_expression', rd.ordered_choice([ 50 | parsing_sequence(), 51 | parsing_ordered_choice(), 52 | parsing_sub_expression() 53 | ])); 54 | } 55 | 56 | function parsing_sequence() { 57 | return rd.action('parsing_sequence', rd.sequence([ 58 | rd.ordered_choice([ 59 | parsing_ordered_choice(), 60 | parsing_sub_expression() 61 | ]), 62 | rd.one_or_more(rd.sequence([ 63 | rd.one_or_more(_()), 64 | rd.ordered_choice([ 65 | parsing_ordered_choice(), 66 | parsing_sub_expression() 67 | ]) 68 | ])) 69 | ])); 70 | } 71 | 72 | function parsing_ordered_choice() { 73 | return rd.action('parsing_ordered_choice', rd.sequence([ 74 | parsing_sub_expression(), 75 | rd.one_or_more(rd.sequence([ 76 | rd.zero_or_more(_()), 77 | rd.string('\/'), 78 | rd.zero_or_more(_()), 79 | parsing_sub_expression() 80 | ])) 81 | ])); 82 | } 83 | 84 | function parsing_sub_expression() { 85 | return rd.action('parsing_sub_expression', rd.sequence([ 86 | rd.zero_or_more(rd.sequence([ 87 | tag(), 88 | rd.string(':') 89 | ])), 90 | rd.ordered_choice([ 91 | parsing_not_predicate(), 92 | parsing_and_predicate(), 93 | parsing_optional(), 94 | parsing_one_or_more(), 95 | parsing_zero_or_more(), 96 | parsing_group(), 97 | parsing_atomic_expression() 98 | ]) 99 | ])); 100 | } 101 | 102 | function tag() { 103 | return rd.action('noop', rd.sequence([ 104 | rd.regex_char('[a-zA-Z_]'), 105 | rd.zero_or_more(rd.regex_char('[a-zA-Z0-9_]')) 106 | ])); 107 | } 108 | 109 | function parsing_group() { 110 | return rd.action('parsing_group', rd.sequence([ 111 | rd.string('('), 112 | rd.zero_or_more(_()), 113 | rd.rec(parsing_expression), 114 | rd.zero_or_more(_()), 115 | rd.string(')') 116 | ])); 117 | } 118 | 119 | function parsing_atomic_expression() { 120 | return rd.action('parsing_atomic_expression', rd.ordered_choice([ 121 | parsing_string(), 122 | parsing_regex_char(), 123 | parsing_eof(), 124 | parsing_rule_call() 125 | ])); 126 | } 127 | 128 | function parsing_not_predicate() { 129 | return rd.action('parsing_not_predicate', rd.sequence([ 130 | rd.string('!'), 131 | rd.ordered_choice([ 132 | parsing_group(), 133 | parsing_atomic_expression() 134 | ]) 135 | ])); 136 | } 137 | 138 | function parsing_and_predicate() { 139 | return rd.action('parsing_and_predicate', rd.sequence([ 140 | rd.string('&'), 141 | rd.ordered_choice([ 142 | parsing_group(), 143 | parsing_atomic_expression() 144 | ]) 145 | ])); 146 | } 147 | 148 | function parsing_zero_or_more() { 149 | return rd.action('parsing_zero_or_more', rd.sequence([ 150 | rd.ordered_choice([ 151 | parsing_group(), 152 | parsing_atomic_expression() 153 | ]), 154 | rd.string('*') 155 | ])); 156 | } 157 | 158 | function parsing_one_or_more() { 159 | return rd.action('parsing_one_or_more', rd.sequence([ 160 | rd.ordered_choice([ 161 | parsing_group(), 162 | parsing_atomic_expression() 163 | ]), 164 | rd.string('+') 165 | ])); 166 | } 167 | 168 | function parsing_optional() { 169 | return rd.action('parsing_optional', rd.sequence([ 170 | rd.ordered_choice([ 171 | parsing_group(), 172 | parsing_atomic_expression() 173 | ]), 174 | rd.string('?') 175 | ])); 176 | } 177 | 178 | function parsing_rule_call() { 179 | return rd.action('parsing_rule_call', parsing_rule_name()) 180 | } 181 | 182 | function parsing_string() { 183 | return rd.action('parsing_string', rd.sequence([ 184 | rd.string('"'), 185 | rd.one_or_more(rd.ordered_choice([ 186 | rd.string('\\\\'), 187 | rd.string('\\"'), 188 | rd.regex_char('[^"]') 189 | ])), 190 | rd.string('"') 191 | ])); 192 | } 193 | 194 | function parsing_regex_char() { 195 | return rd.action('parsing_regex_char', rd.ordered_choice([ 196 | rd.sequence([ 197 | rd.string('['), 198 | rd.optional(rd.string('^')), 199 | rd.one_or_more(rd.ordered_choice([ 200 | rd.string('\\]'), 201 | rd.string('\\['), 202 | rd.regex_char('[^\\]]') 203 | ])), 204 | rd.string(']') 205 | ]), 206 | rd.string('.') 207 | ])); 208 | } 209 | 210 | function parsing_eof(){ 211 | return rd.action('parsing_end_of_file', rd.string("EOF")) 212 | } 213 | 214 | function _() { 215 | return rd.action('noop', rd.regex_char('[\\s]')) 216 | } 217 | 218 | function SPEG_parser() { 219 | this.parser = peg(); 220 | } 221 | 222 | SPEG_parser.prototype.parse = function(text) { 223 | this.state = { 224 | text: text, 225 | position: 0 226 | }; 227 | var ast = this.parser(this.state); 228 | return ast; 229 | }; 230 | 231 | SPEG_parser.prototype.getLastExpectations = function() { 232 | return this.state.lastExpectations 233 | }; 234 | 235 | SPEG_parser.prototype.get_last_error = function() { 236 | return rd.get_last_error(this.state); 237 | }; 238 | 239 | module.exports = SPEG_parser; 240 | -------------------------------------------------------------------------------- /src/speg_visitor.js: -------------------------------------------------------------------------------- 1 | var rd = require('./rd_parser'); 2 | 3 | function SPEG_actions_visitor() { 4 | this.actions = new SPEG_actions(); 5 | } 6 | SPEG_actions_visitor.prototype.visit = function(node) { 7 | if (node.children) { 8 | node.children = node.children.map(function(child){ 9 | return this.visit(child); 10 | }, this); 11 | } 12 | if (this.actions && node.action) { 13 | return this.actions[node.action](node); 14 | } 15 | return node; 16 | }; 17 | 18 | function SPEG_actions() {} 19 | 20 | SPEG_actions.prototype.noop = function(node) { 21 | return node; 22 | }; 23 | 24 | SPEG_actions.prototype.peg = function(node) { 25 | return node.children[3]; 26 | }; 27 | 28 | SPEG_actions.prototype.parsing_body = function(node) { 29 | node.children = node.children.map(function(child){ 30 | return child.children[0]; 31 | }); 32 | return node; 33 | }; 34 | 35 | SPEG_actions.prototype.parsing_rule = function(node) { 36 | var rule = node.children[4]; 37 | var ruleName = node.children[0].match; 38 | return { 39 | name: ruleName, 40 | parser: function(state) { 41 | var start = state.position; 42 | var ast = rule(state); 43 | if (ast) { 44 | ast.rule = ruleName; 45 | if (!state.succesfullRules) { 46 | state.succesfullRules = []; 47 | } 48 | state.succesfullRules.push({ 49 | rule: ast.rule, 50 | match: ast.match, 51 | start_position: ast.start_position, 52 | end_position: ast.end_position 53 | }); 54 | } else { 55 | if (!state.failedRules) { 56 | state.failedRules = []; 57 | } 58 | state.failedRules.push({ 59 | rule: ruleName, 60 | start_position: start 61 | }); 62 | } 63 | return ast; 64 | } 65 | } 66 | }; 67 | 68 | SPEG_actions.prototype.parsing_expression = function(node) { 69 | return node.children[0]; 70 | }; 71 | 72 | SPEG_actions.prototype.parsing_sequence = function(node) { 73 | var head = [node.children[0].children[0]]; 74 | var tail = node.children[1].children.map(function(child){ 75 | return child.children[1].children[0]; 76 | }); 77 | return rd.sequence(head.concat(tail)); 78 | }; 79 | 80 | SPEG_actions.prototype.parsing_ordered_choice = function(node) { 81 | var head = [node.children[0]]; 82 | var tail = node.children[1].children.map(function(child){ 83 | return child.children[3]; 84 | }); 85 | return rd.ordered_choice(head.concat(tail)); 86 | }; 87 | 88 | SPEG_actions.prototype.parsing_sub_expression = function(node) { 89 | return function(state) { 90 | var result = node.children[1].children[0].call(this, state); 91 | var tags = node.children[0].children.map(function(tag_node){ 92 | return tag_node.children[0].match; 93 | }); 94 | if (result) { 95 | if (tags.length > 0) { 96 | if (result.tags) { 97 | result.tags = tags.concat(result.tags); 98 | } else { 99 | result.tags = tags; 100 | } 101 | } 102 | } else { 103 | if (!state.failedTags) { 104 | state.failedTags = []; 105 | } 106 | state.failedTags.push.apply(state.failedTags, tags); 107 | } 108 | return result; 109 | } 110 | }; 111 | 112 | SPEG_actions.prototype.parsing_group = function(node) { 113 | return node.children[2]; 114 | }; 115 | 116 | SPEG_actions.prototype.parsing_atomic_expression = function(node) { 117 | return node.children[0]; 118 | }; 119 | 120 | SPEG_actions.prototype.parsing_not_predicate = function(node) { 121 | return rd.not_predicate(node.children[1].children[0]); 122 | }; 123 | 124 | SPEG_actions.prototype.parsing_and_predicate = function(node) { 125 | return rd.and_predicate(node.children[1].children[0]); 126 | }; 127 | 128 | SPEG_actions.prototype.parsing_zero_or_more = function(node) { 129 | return rd.zero_or_more(node.children[0].children[0]); 130 | }; 131 | 132 | SPEG_actions.prototype.parsing_one_or_more = function(node) { 133 | return rd.one_or_more(node.children[0].children[0]); 134 | }; 135 | 136 | SPEG_actions.prototype.parsing_optional = function(node) { 137 | return rd.optional(node.children[0].children[0]); 138 | }; 139 | 140 | SPEG_actions.prototype.parsing_string = function(node) { 141 | return rd.string(node.children[1].match 142 | .replace(/\\\\/g, '\\') 143 | .replace(/\\"/g, '"') 144 | ); 145 | }; 146 | 147 | SPEG_actions.prototype.parsing_regex_char = function(node) { 148 | return rd.regex_char(node.children[0].match); 149 | }; 150 | 151 | SPEG_actions.prototype.parsing_rule_call = function(node) { 152 | return rd.call_rule_by_name(node.match); 153 | }; 154 | 155 | SPEG_actions.prototype.parsing_end_of_file = function() { 156 | return rd.end_of_file(); 157 | }; 158 | 159 | module.exports = { 160 | SPEG_actions_visitor: SPEG_actions_visitor 161 | }; 162 | -------------------------------------------------------------------------------- /test/exceptions.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var expect = require('chai').expect; 3 | var simplepeg = require('./../src/speg'); 4 | var ex = require('./../src/exceptions'); 5 | 6 | describe('exceptions - ', function() { 7 | it('should throw correct error when grammar is wrong',function() { 8 | expect(function() { 9 | var parser = new simplepeg.SPEG(); 10 | parser.parse_grammar('!!!'); 11 | }).to.throw(ex.GrammarParseError); 12 | }); 13 | it('should throw correct error when text is wrong',function() { 14 | expect(function() { 15 | var parser = new simplepeg.SPEG(); 16 | parser.parse_grammar('GRAMMAR test a -> "A";'); 17 | parser.parse_text('B'); 18 | }).to.throw(ex.TextParseError); 19 | }); 20 | it('should throw when text called before grammar',function() { 21 | expect(function() { 22 | var parser = new simplepeg.SPEG(); 23 | parser.parse_text('B'); 24 | }).to.throw(Error); 25 | }); 26 | describe('should (speg.parse) throw correct error when grammar is wrong',function() { 27 | it(' - wrong beggining', function() { 28 | expect(function() { 29 | var parser = new simplepeg.SPEG(); 30 | parser.parse('!!!', 'B'); 31 | }).to.throw(ex.GrammarParseError); 32 | }); 33 | it(' - forgot semicolon for simple rule', function() { 34 | expect(function() { 35 | try { 36 | var parser = new simplepeg.SPEG(); 37 | parser.parse('GRAMMAR t a->b', 'B'); 38 | } catch (e) { 39 | expect(e.message).to.equal( 40 | 'Failed to parse grammar: ' + 41 | '\n\nUnexpected "EOF" expected (: or [\\s] or ;)\n' + 42 | '1: GRAMMAR t a->b\n' + 43 | '-----------------^' 44 | ); 45 | throw e; 46 | } 47 | }).to.throw(ex.GrammarParseError); 48 | }); 49 | it(' - forgot semicolon for sequence rule', function() { 50 | expect(function() { 51 | try { 52 | var parser = new simplepeg.SPEG(); 53 | parser.parse('GRAMMAR t a->b c', 'B'); 54 | } catch (e) { 55 | expect(e.message).to.equal( 56 | 'Failed to parse grammar: ' + 57 | '\n\nUnexpected "EOF" expected (;)\n' + 58 | '1: GRAMMAR t a->b c\n' + 59 | '-------------------^' 60 | ); 61 | throw e; 62 | } 63 | }).to.throw(ex.GrammarParseError); 64 | }); 65 | it(' - forgot semicolon for ordered choice rule', function() { 66 | expect(function() { 67 | try { 68 | var parser = new simplepeg.SPEG(); 69 | parser.parse('GRAMMAR t a->b/c', 'B'); 70 | } catch (e) { 71 | expect(e.message).to.equal( 72 | 'Failed to parse grammar: ' + 73 | '\n\nUnexpected "EOF" expected (/ or ;)\n' + 74 | '1: GRAMMAR t a->b/c\n' + 75 | '-------------------^' 76 | ); 77 | throw e; 78 | } 79 | }).to.throw(ex.GrammarParseError); 80 | }); 81 | it(' - forgot semicolon for second rule', function() { 82 | expect(function() { 83 | try { 84 | var parser = new simplepeg.SPEG(); 85 | parser.parse('GRAMMAR t a->b;b->c', 'B'); 86 | } catch (e) { 87 | expect(e.message).to.equal( 88 | 'Failed to parse grammar: ' + 89 | '\n\nUnexpected "EOF" expected (: or [\\s] or ;)\n' + 90 | '1: GRAMMAR t a->b;b->c\n' + 91 | '----------------------^' 92 | ); 93 | throw e; 94 | } 95 | }).to.throw(ex.GrammarParseError); 96 | }); 97 | it(' - forgot semicolon for third rule', function() { 98 | expect(function() { 99 | try { 100 | var parser = new simplepeg.SPEG(); 101 | parser.parse('GRAMMAR t a->b;b->c;c->d', 'B'); 102 | } catch (e) { 103 | expect(e.message).to.equal( 104 | 'Failed to parse grammar: ' + 105 | '\n\nUnexpected "EOF" expected (: or [\\s] or ;)\n' + 106 | '1: GRAMMAR t a->b;b->c;c->d\n' + 107 | '---------------------------^' 108 | ); 109 | throw e; 110 | } 111 | }).to.throw(ex.GrammarParseError); 112 | }); 113 | }); 114 | it('should (speg.parse) throw correct error when text is wrong',function() { 115 | expect(function() { 116 | var parser = new simplepeg.SPEG(); 117 | parser.parse('GRAMMAR test a -> "A";', 'B'); 118 | }).to.throw(ex.TextParseError); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/rd_parser.combo.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var rd = require('./../src/rd_parser'); 3 | 4 | describe('rd - combo - ', function() { 5 | it('should successfully combine "string" methods with "sequence"',function() { 6 | // arrange 7 | var text = 'testsomething'; 8 | var string_to_match = 'testsomething'; 9 | var start_position = 0; 10 | var children_to_match = [{ 11 | "end_position": 4, 12 | "match": "test", 13 | "start_position": 0, 14 | "type": "string" 15 | }, { 16 | "end_position": 13, 17 | "match": "something", 18 | "start_position": 4, 19 | "type": "string" 20 | }]; 21 | // act 22 | var parser = rd.sequence([ 23 | rd.string('test'), 24 | rd.string('something') 25 | ]); 26 | var ast = parser({ 27 | text: text, 28 | position: start_position 29 | }); 30 | // assert 31 | should.exist(ast); 32 | // we need to assert without children 33 | ast.should.deep.equal({ 34 | match: string_to_match, 35 | children: children_to_match, 36 | start_position: start_position, 37 | end_position: string_to_match.length, 38 | type: "sequence" 39 | }); 40 | }); 41 | it('should successfully parse simple math expression',function() { 42 | // arrange 43 | var text = '1 + 2'; 44 | var string_to_match = '1 + 2'; 45 | var start_position = 0; 46 | var children_to_match = []; 47 | // act 48 | function space() { 49 | return rd.regex_char('[\\s]') 50 | } 51 | function multiplicative() { 52 | return rd.string('*') 53 | } 54 | function additive() { 55 | return rd.string('+') 56 | } 57 | function factor() { 58 | return rd.ordered_choice([ 59 | rd.sequence([ 60 | rd.string('('), 61 | rd.rec(exp), 62 | rd.string(')') 63 | ]), 64 | rd.regex_char('[0-9]') 65 | ]) 66 | } 67 | function term(){ 68 | return rd.sequence([ 69 | factor(), 70 | rd.zero_or_more(rd.sequence([ 71 | space(), 72 | multiplicative(), 73 | space(), 74 | factor() 75 | ])) 76 | ]) 77 | } 78 | function exp() { 79 | return rd.sequence([ 80 | term(), 81 | rd.zero_or_more(rd.sequence([ 82 | space(), 83 | additive(), 84 | space(), 85 | term() 86 | ])) 87 | ]) 88 | } 89 | function math() { 90 | return rd.sequence([ 91 | exp(), 92 | rd.end_of_file() 93 | ]) 94 | } 95 | var parser = math(); 96 | var ast = parser({ 97 | text: text, 98 | position: start_position 99 | }); 100 | // assert 101 | should.exist(ast); 102 | // we need to assert without children 103 | ast.match.should.equal(string_to_match); 104 | }); 105 | it('should fail and return to position if ordered_choice failed', function() { 106 | // arrange 107 | var text = 'AB'; 108 | var start_position = 0; 109 | var state = { 110 | text: text, 111 | position: start_position 112 | }; 113 | // act 114 | var parser = rd.ordered_choice([ 115 | rd.sequence([ 116 | rd.string('A'), 117 | rd.string('A') 118 | ]), 119 | rd.string('B') 120 | ]); 121 | var ast = parser(state); 122 | // assert 123 | should.exist(ast); 124 | ast.should.be.a('boolean'); 125 | state.position.should.equal(0); 126 | }); 127 | it('should successfully combine "regex_char" methods with "sequence"',function() { 128 | // arrange 129 | var text = '1D'; 130 | var string_to_match = '1D'; 131 | var start_position = 0; 132 | var children_to_match = [{ 133 | "end_position": 1, 134 | "match": "1", 135 | "start_position": 0, 136 | "type": "regex_char" 137 | }, { 138 | "end_position": 2, 139 | "match": "D", 140 | "start_position": 1, 141 | "type": "regex_char" 142 | }]; 143 | // act 144 | var parser = rd.sequence([ 145 | rd.regex_char('[0-9]'), 146 | rd.regex_char('[A-Z]') 147 | ]); 148 | var ast = parser({ 149 | text: text, 150 | position: start_position 151 | }); 152 | // assert 153 | should.exist(ast); 154 | // we need to assert without children 155 | ast.should.deep.equal({ 156 | match: string_to_match, 157 | children: children_to_match, 158 | start_position: start_position, 159 | end_position: string_to_match.length, 160 | type: "sequence" 161 | }); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /test/rd_parser.peg.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var rd = require('./../src/rd_parser'); 3 | 4 | describe('rd - peg methods - ', function() { 5 | it('should export PEG methods', function () { 6 | rd.string.should.be.a('function'); 7 | rd.regex_char.should.be.a('function'); 8 | rd.sequence.should.be.a('function'); 9 | rd.ordered_choice.should.be.a('function'); 10 | rd.zero_or_more.should.be.a('function'); 11 | rd.one_or_more.should.be.a('function'); 12 | rd.optional.should.be.a('function'); 13 | rd.and_predicate.should.be.a('function'); 14 | rd.not_predicate.should.be.a('function'); 15 | }); 16 | it('should implement "string" method', function() { 17 | // arrange 18 | var text = 'test'; 19 | var string_to_match = 'test'; 20 | var start_position = 0; 21 | var rule = 'test'; 22 | // act 23 | var parser = rd.string(rule); 24 | var state = { 25 | text: text, 26 | position: start_position, 27 | } 28 | var ast = parser(state); 29 | // assert 30 | should.exist(ast); 31 | // we need to assert without children 32 | state.lastExpectations.should.deep.equal([]); 33 | ast.should.deep.equal({ 34 | match: string_to_match, 35 | start_position: start_position, 36 | end_position: string_to_match.length, 37 | type: "string" 38 | }); 39 | }); 40 | it('should correctly handle wrong text for "string" method', function() { 41 | // arrange 42 | var text = 'asda'; 43 | var string_to_match = 'test'; 44 | var start_position = 0; 45 | var rule = 'test'; 46 | // act 47 | var parser = rd.string(rule); 48 | var state = { 49 | text: text, 50 | position: start_position 51 | } 52 | var ast = parser(state); 53 | // assert 54 | should.exist(ast); 55 | ast.should.be.a('boolean'); 56 | // we need to assert without children 57 | state.lastExpectations.should.deep.equal([{ 58 | rule: rule, 59 | position: start_position, 60 | type: "string" 61 | }]); 62 | }); 63 | it('should implement "regex_char" method', function() { 64 | // arrange 65 | var text = '8'; 66 | var string_to_match = '8'; 67 | var rule = '[0-9]'; 68 | var start_position = 0; 69 | // act 70 | var parser = rd.regex_char(rule); 71 | var state = { 72 | text: text, 73 | position: start_position 74 | }; 75 | var ast = parser(state); 76 | // assert 77 | should.exist(ast); 78 | // we need to assert without children 79 | state.lastExpectations.should.deep.equal([]); 80 | ast.should.deep.equal({ 81 | match: string_to_match, 82 | start_position: start_position, 83 | end_position: string_to_match.length, 84 | type: "regex_char" 85 | }); 86 | }); 87 | it('should correctly handle wrong text for "regex_char" method', function() { 88 | // arrange 89 | var text = 'asda'; 90 | var string_to_match = '8'; 91 | var start_position = 0; 92 | var rule = '[0-9]'; 93 | // act 94 | var parser = rd.regex_char(rule); 95 | var state = { 96 | text: text, 97 | position: start_position 98 | }; 99 | var ast = parser(state); 100 | // assert 101 | should.exist(ast); 102 | ast.should.be.a('boolean'); 103 | // we need to assert without children 104 | state.lastExpectations.should.deep.equal([{ 105 | rule: rule, 106 | position: start_position, 107 | type: "regex_char" 108 | }]); 109 | }); 110 | it('should implement "sequence" method for string/regex', function() { 111 | // arrange 112 | var text = 'test2'; 113 | var string_to_match = 'test2'; 114 | var children_to_match = [ 115 | { 116 | match: 'test', 117 | start_position: 0, 118 | end_position: 4, 119 | type: 'string' 120 | }, 121 | { 122 | match: '2', 123 | start_position: 4, 124 | end_position: 5, 125 | type: 'regex_char' 126 | } 127 | ]; 128 | var parsing_expressions = [ 129 | rd.string('test'), 130 | rd.regex_char('[0-9]') 131 | ]; 132 | var start_position = 0; 133 | var end_position = text.length; 134 | // act 135 | var parser = rd.sequence(parsing_expressions); 136 | var state = { 137 | text: text, 138 | position: start_position 139 | }; 140 | var ast = parser(state); 141 | // assert 142 | should.exist(ast); 143 | // we need to assert without children 144 | state.lastExpectations.should.deep.equal([]); 145 | ast.should.deep.equal({ 146 | match: string_to_match, 147 | children: children_to_match, 148 | start_position: start_position, 149 | end_position: end_position, 150 | type: "sequence" 151 | }); 152 | }); 153 | it('should implement "sequence" method for regex/string', function() { 154 | // arrange 155 | var text = '2test'; 156 | var string_to_match = '2test'; 157 | var children_to_match = [ 158 | { 159 | match: '2', 160 | start_position: 0, 161 | end_position: 1, 162 | type: 'regex_char' 163 | }, 164 | { 165 | match: 'test', 166 | start_position: 1, 167 | end_position: 5, 168 | type: 'string' 169 | } 170 | ]; 171 | var parsing_expressions = [ 172 | rd.regex_char('[0-9]'), 173 | rd.string('test') 174 | ]; 175 | var start_position = 0; 176 | var end_position = text.length; 177 | // act 178 | var parser = rd.sequence(parsing_expressions); 179 | var state = { 180 | text: text, 181 | position: start_position 182 | }; 183 | var ast = parser(state); 184 | // assert 185 | should.exist(ast); 186 | // we need to assert without children 187 | state.lastExpectations.should.deep.equal([]); 188 | ast.should.deep.equal({ 189 | match: string_to_match, 190 | children: children_to_match, 191 | start_position: start_position, 192 | end_position: end_position, 193 | type: "sequence" 194 | }); 195 | }); 196 | it('should correctly handle wrong text for "sequence" method for string/regex', function() { 197 | // arrange 198 | var text = 'testwrong'; 199 | var string_to_match = 'test2'; 200 | var children_to_match = [ 201 | { 202 | match: 'test', 203 | start_position: 0, 204 | end_position: 4, 205 | type: 'string' 206 | }, 207 | { 208 | match: '2', 209 | start_position: 4, 210 | end_position: 5, 211 | type: 'regex_char' 212 | } 213 | ]; 214 | var parsing_expressions = [ 215 | rd.string('test'), 216 | rd.regex_char('[0-9]') 217 | ]; 218 | var start_position = 0; 219 | var end_position = text.length; 220 | // act 221 | var parser = rd.sequence(parsing_expressions); 222 | var state = { 223 | text: text, 224 | position: start_position 225 | }; 226 | var ast = parser(state); 227 | // assert 228 | should.exist(ast); 229 | ast.should.be.a('boolean'); 230 | // we need to assert without children 231 | state.lastExpectations.should.deep.equal([{ 232 | rule: '[0-9]', 233 | position: 4, 234 | type: "regex_char" 235 | }]); 236 | }); 237 | it('should correctly handle wrong text for "sequence" method for regex/string', function() { 238 | // arrange 239 | var text = 'wrongtest'; 240 | var string_to_match = '2test'; 241 | var children_to_match = [ 242 | { 243 | match: '2', 244 | start_position: 0, 245 | end_position: 1, 246 | type: 'regex_char' 247 | }, 248 | { 249 | match: 'test', 250 | start_position: 1, 251 | end_position: 5, 252 | type: 'string' 253 | } 254 | ]; 255 | var parsing_expressions = [ 256 | rd.regex_char('[0-9]'), 257 | rd.string('test') 258 | ]; 259 | var start_position = 0; 260 | var end_position = text.length; 261 | // act 262 | var parser = rd.sequence(parsing_expressions); 263 | var state = { 264 | text: text, 265 | position: start_position 266 | }; 267 | var ast = parser(state); 268 | // assert 269 | should.exist(ast); 270 | ast.should.be.a('boolean'); 271 | // we need to assert without children 272 | state.lastExpectations.should.deep.equal([{ 273 | rule: '[0-9]', 274 | position: start_position, 275 | type: "regex_char" 276 | }]); 277 | }); 278 | it('should implement "ordered_choice" method for string', function() { 279 | // arrange 280 | var text = '2'; 281 | var string_to_match = '2'; 282 | var children_to_match = [ 283 | { 284 | match: '2', 285 | start_position: 0, 286 | end_position: 1, 287 | type: 'regex_char' 288 | } 289 | ]; 290 | var parsing_expressions = [ 291 | rd.string('test'), 292 | rd.regex_char('[0-9]') 293 | ]; 294 | var start_position = 0; 295 | var end_position = text.length; 296 | // act 297 | var parser = rd.ordered_choice(parsing_expressions); 298 | var state = { 299 | text: text, 300 | position: start_position 301 | }; 302 | var ast = parser(state); 303 | // assert 304 | should.exist(ast); 305 | // we need to assert without children 306 | state.lastExpectations.should.deep.equal([]); 307 | ast.should.deep.equal({ 308 | match: string_to_match, 309 | children: children_to_match, 310 | start_position: start_position, 311 | end_position: start_position + end_position, 312 | type: "ordered_choice" 313 | }); 314 | }); 315 | it('should correctly handle wrong text for "ordered_choice" method for string', function() { 316 | // arrange 317 | var text = 'zzzwrong'; 318 | var string_to_match = '2'; 319 | var children_to_match = [ 320 | { 321 | match: 'test', 322 | start_position: 0, 323 | end_position: 4, 324 | type: 'string' 325 | }, 326 | { 327 | match: '2', 328 | start_position: 4, 329 | end_position: 5, 330 | type: 'regex_char' 331 | } 332 | ]; 333 | var parsing_expressions = [ 334 | rd.string('test'), 335 | rd.regex_char('[0-9]') 336 | ]; 337 | var start_position = 0; 338 | var end_position = text.length; 339 | // act 340 | var parser = rd.ordered_choice(parsing_expressions); 341 | var state = { 342 | text: text, 343 | position: start_position 344 | }; 345 | var ast = parser(state); 346 | // assert 347 | should.exist(ast); 348 | ast.should.be.a('boolean'); 349 | // we need to assert without children 350 | state.lastExpectations.should.deep.equal([{ 351 | rule: 'test', 352 | position: 0, 353 | type: 'string' 354 | },{ 355 | rule: '[0-9]', 356 | position: 0, 357 | type: 'regex_char' 358 | }]); 359 | }); 360 | it('should implement "zero_or_more" method for string', function() { 361 | // arrange 362 | var text = '22'; 363 | var string_to_match = '22'; 364 | var children_to_match = [ 365 | { 366 | match: '2', 367 | start_position: 0, 368 | end_position: 1, 369 | type: 'regex_char' 370 | }, 371 | { 372 | match: '2', 373 | start_position: 1, 374 | end_position: 2, 375 | type: 'regex_char' 376 | } 377 | ]; 378 | var parsing_expression = rd.regex_char('[0-9]'); 379 | var start_position = 0; 380 | var end_position = text.length; 381 | // act 382 | var parser = rd.zero_or_more(parsing_expression); 383 | var state = { 384 | text: text, 385 | position: start_position 386 | }; 387 | var ast = parser(state); 388 | // assert 389 | should.exist(ast); 390 | // we need to assert without children 391 | state.lastExpectations.should.deep.equal([ 392 | { 393 | "position": 2, 394 | "rule": "[0-9]", 395 | "type": "regex_char" 396 | } 397 | ]); 398 | ast.should.deep.equal({ 399 | match: string_to_match, 400 | children: children_to_match, 401 | start_position: start_position, 402 | end_position: end_position, 403 | type: "zero_or_more" 404 | }); 405 | }); 406 | it('should correctly handle wrong text for "zero_or_more" method for string', function() { 407 | // arrange 408 | var text = 'zzzwrong'; 409 | var string_to_match = null; 410 | var children_to_match = []; 411 | var parsing_expression = rd.regex_char('[0-9]'); 412 | var start_position = 0; 413 | var end_position = 0; 414 | // act 415 | var parser = rd.zero_or_more(parsing_expression); 416 | var state = { 417 | text: text, 418 | position: start_position 419 | }; 420 | var ast = parser(state); 421 | // assert 422 | should.exist(ast); 423 | // we need to assert without children 424 | ast.should.deep.equal({ 425 | match: string_to_match, 426 | children: children_to_match, 427 | start_position: start_position, 428 | end_position: end_position, 429 | type: "zero_or_more" 430 | }); 431 | }); 432 | it('should correctly handle wrong text for second "zero_or_more" method for string', function() { 433 | // arrange 434 | var text = '0f'; 435 | var string_to_match = null; 436 | var children_to_match = []; 437 | var parsing_expression = rd.regex_char('[0-9]'); 438 | var start_position = 0; 439 | var end_position = 0; 440 | // act 441 | var parser = rd.sequence([ 442 | rd.zero_or_more(parsing_expression), 443 | rd.end_of_file() 444 | ]); 445 | var state = { 446 | text: text, 447 | position: start_position 448 | }; 449 | var ast = parser(state); 450 | // assert 451 | should.exist(ast); 452 | ast.should.be.a('boolean'); 453 | // we need to assert without children 454 | state.lastExpectations.should.deep.equal([{ 455 | rule: '[0-9]', 456 | position: 1, 457 | type: 'regex_char' 458 | },{ 459 | rule: 'EOF', 460 | position: 1, 461 | type: 'end_of_file' 462 | }]); 463 | }); 464 | it('should implement "one_or_more" method for string', function() { 465 | // arrange 466 | var text = '22'; 467 | var string_to_match = '22'; 468 | var children_to_match = [ 469 | { 470 | match: '2', 471 | start_position: 0, 472 | end_position: 1, 473 | type: 'regex_char' 474 | }, 475 | { 476 | match: '2', 477 | start_position: 1, 478 | end_position: 2, 479 | type: 'regex_char' 480 | } 481 | ]; 482 | var parsing_expression = rd.regex_char('[0-9]'); 483 | var start_position = 0; 484 | var end_position = text.length; 485 | // act 486 | var parser = rd.one_or_more(parsing_expression); 487 | var state = { 488 | text: text, 489 | position: start_position 490 | }; 491 | var ast = parser(state); 492 | // assert 493 | should.exist(ast); 494 | // we need to assert without children 495 | state.lastExpectations.should.deep.equal([ 496 | { 497 | "position": 2, 498 | "rule": "[0-9]", 499 | "type": "regex_char" 500 | } 501 | ]); 502 | ast.should.deep.equal({ 503 | match: string_to_match, 504 | children: children_to_match, 505 | start_position: start_position, 506 | end_position: end_position, 507 | type: "one_or_more" 508 | }); 509 | }); 510 | it('should correctly handle wrong text for "one_or_more" method for string', function() { 511 | // arrange 512 | var text = 'zzzwrong'; 513 | var string_to_match = null; 514 | var children_to_match = []; 515 | var parsing_expression = rd.regex_char('[0-9]'); 516 | var start_position = 0; 517 | var end_position = 0; 518 | // act 519 | var parser = rd.one_or_more(parsing_expression); 520 | var state = { 521 | text: text, 522 | position: start_position 523 | }; 524 | var ast = parser(state); 525 | // assert 526 | should.exist(ast); 527 | ast.should.be.a('boolean'); 528 | // we need to assert without children 529 | state.lastExpectations.should.deep.equal([{ 530 | rule: '[0-9]', 531 | position: 0, 532 | type: 'regex_char' 533 | }]); 534 | }); 535 | it('should correctly handle wrong text for second "one_or_more" method for string', function() { 536 | // arrange 537 | var text = '0f'; 538 | var string_to_match = null; 539 | var children_to_match = []; 540 | var parsing_expression = rd.regex_char('[0-9]'); 541 | var start_position = 0; 542 | var end_position = 0; 543 | // act 544 | var parser = rd.sequence([ 545 | rd.one_or_more(parsing_expression), 546 | rd.end_of_file() 547 | ]); 548 | var state = { 549 | text: text, 550 | position: start_position 551 | }; 552 | var ast = parser(state); 553 | // assert 554 | should.exist(ast); 555 | ast.should.be.a('boolean'); 556 | // we need to assert without children 557 | state.lastExpectations.should.deep.equal([{ 558 | rule: '[0-9]', 559 | position: 1, 560 | type: 'regex_char' 561 | },{ 562 | rule: 'EOF', 563 | position: 1, 564 | type: 'end_of_file' 565 | }]); 566 | }); 567 | it('should implement "optional" method for string', function() { 568 | // arrange 569 | var text = '22'; 570 | var string_to_match = '2'; 571 | var children_to_match = [ 572 | { 573 | match: '2', 574 | start_position: 0, 575 | end_position: 1, 576 | type: 'regex_char' 577 | } 578 | ]; 579 | var parsing_expression = rd.regex_char('[0-9]'); 580 | var start_position = 0; 581 | var end_position = 1; 582 | // act 583 | var parser = rd.optional(parsing_expression); 584 | var state = { 585 | text: text, 586 | position: start_position 587 | }; 588 | var ast = parser(state); 589 | // assert 590 | should.exist(ast); 591 | // we need to assert without children 592 | state.lastExpectations.should.deep.equal([]); 593 | ast.should.deep.equal({ 594 | match: string_to_match, 595 | children: children_to_match, 596 | start_position: start_position, 597 | end_position: end_position, 598 | type: "optional" 599 | }); 600 | }); 601 | it('should correctly handle wrong text for "optional" method for string', function() { 602 | // arrange 603 | var text = 'zzzwrong'; 604 | var string_to_match = null; 605 | var children_to_match = null; 606 | var parsing_expression = rd.regex_char('[0-9]'); 607 | var start_position = 0; 608 | var end_position = 0; 609 | // act 610 | var parser = rd.optional(parsing_expression); 611 | var state = { 612 | text: text, 613 | position: start_position 614 | }; 615 | var ast = parser(state); 616 | // assert 617 | should.exist(ast); 618 | // we need to assert without children 619 | ast.should.deep.equal({ 620 | match: string_to_match, 621 | children: children_to_match, 622 | start_position: start_position, 623 | end_position: end_position, 624 | type: "optional" 625 | }); 626 | }); 627 | it('should implement "and_predicate" method for string', function() { 628 | // arrange 629 | var text = '22'; 630 | var string_to_match = null; 631 | var children_to_match = [ 632 | { 633 | match: '2', 634 | start_position: 0, 635 | end_position: 1, 636 | type: 'regex_char' 637 | } 638 | ]; 639 | var parsing_expression = rd.regex_char('[0-9]'); 640 | var start_position = 0; 641 | var end_position = 0; 642 | // act 643 | var parser = rd.and_predicate(parsing_expression); 644 | var state = { 645 | text: text, 646 | position: start_position 647 | }; 648 | var ast = parser(state); 649 | // assert 650 | should.exist(ast); 651 | // we need to assert without children 652 | state.lastExpectations.should.deep.equal([]); 653 | ast.should.deep.equal({ 654 | match: string_to_match, 655 | children: children_to_match, 656 | start_position: start_position, 657 | end_position: end_position, 658 | type: "and_predicate" 659 | }); 660 | }); 661 | it('should correctly handle wrong text for "and_predicate" method for string', function() { 662 | // arrange 663 | var text = 'zzzwrong'; 664 | var string_to_match = null; 665 | var children_to_match = []; 666 | var parsing_expression = rd.regex_char('[0-9]'); 667 | var start_position = 0; 668 | var end_position = 0; 669 | // act 670 | var parser = rd.and_predicate(parsing_expression); 671 | var state = { 672 | text: text, 673 | position: start_position 674 | }; 675 | var ast = parser(state); 676 | // assert 677 | should.exist(ast); 678 | ast.should.be.a('boolean'); 679 | // we need to assert without children 680 | state.lastExpectations.should.deep.equal([{ 681 | rule: '[0-9]', 682 | position: 0, 683 | type: 'regex_char' 684 | }]); 685 | }); 686 | it('should implement "not_predicate" method for string', function() { 687 | // arrange 688 | var text = 'z22'; 689 | var string_to_match = null; 690 | var children_to_match = []; 691 | var parsing_expression = rd.regex_char('[0-9]'); 692 | var start_position = 0; 693 | var end_position = 0; 694 | // act 695 | var parser = rd.not_predicate(parsing_expression); 696 | var state = { 697 | text: text, 698 | position: start_position 699 | }; 700 | var ast = parser(state); 701 | // assert 702 | should.exist(ast); 703 | // we need to assert without children 704 | state.lastExpectations.should.deep.equal([]); 705 | ast.should.deep.equal({ 706 | match: string_to_match, 707 | children: children_to_match, 708 | start_position: start_position, 709 | end_position: end_position, 710 | type: "not_predicate" 711 | }); 712 | }); 713 | it('should correctly handle wrong text for "not_predicate" method for string', function() { 714 | // arrange 715 | var text = '222'; 716 | var string_to_match = null; 717 | var children_to_match = []; 718 | var parsing_expression = rd.regex_char('[0-9]'); 719 | var start_position = 0; 720 | var end_position = 0; 721 | // act 722 | var parser = rd.not_predicate(parsing_expression); 723 | var state = { 724 | text: text, 725 | position: start_position 726 | }; 727 | var ast = parser(state); 728 | // assert 729 | should.exist(ast); 730 | ast.should.be.a('boolean'); 731 | // we need to assert without children 732 | state.lastExpectations.should.deep.equal([{ 733 | type: 'not_predicate', 734 | position: 0, 735 | children: [{ 736 | end_position: 1, 737 | match: "2", 738 | start_position: 0, 739 | type: 'regex_char' 740 | }] 741 | }]); 742 | }); 743 | }); 744 | -------------------------------------------------------------------------------- /test/rd_parser.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var rd_parser = require('./../src/rd_parser'); 3 | 4 | describe('rd_parser - ', function() { 5 | it('should exist', function () { 6 | should.exist(rd_parser); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/speg.fixtures.js: -------------------------------------------------------------------------------- 1 | var SPEG = require('./../src/speg').SPEG; 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var recursiveReadSync = require('recursive-readdir-sync'); 5 | var valid_files = recursiveReadSync('./test/speg_fixtures'); 6 | 7 | describe('speg - fixtures - ', function() { 8 | valid_files = valid_files.filter(function(filename) { 9 | return /\.peg$/.test(filename); 10 | }) 11 | for (var i = 0, len = valid_files.length; i < len; i++) { 12 | it('should parse - ' + valid_files[i], (function (filename) { 13 | return function () { 14 | var name = path.basename(filename); 15 | var grammar = fs.readFileSync(filename, "utf8"); 16 | var text = fs.readFileSync(path.join('./test/speg_fixtures/', name + '.txt'), "utf8"); 17 | var result = fs.readFileSync(path.join('./test/speg_fixtures/', name + '.json'), "utf8"); 18 | var speg = new SPEG(); 19 | var ast = speg.parse(grammar, text); 20 | ast.should.deep.equal(JSON.parse(result)); 21 | } 22 | }(valid_files[i]))); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /test/speg.invalid.fixtures.js: -------------------------------------------------------------------------------- 1 | var SPEG = require('./../src/speg').SPEG; 2 | var fs = require('fs'); 3 | var expect = require('chai').expect; 4 | var path = require('path'); 5 | var recursiveReadSync = require('recursive-readdir-sync'); 6 | var valid_files = recursiveReadSync('./test/speg_invalid_fixtures'); 7 | 8 | describe('speg - invalid fixtures - ', function() { 9 | valid_files = valid_files.filter(function(filename) { 10 | return /\.peg$/.test(filename); 11 | }) 12 | for (var i = 0, len = valid_files.length; i < len; i++) { 13 | it('should parse - ' + valid_files[i], (function (filename) { 14 | return function () { 15 | var name = path.basename(filename); 16 | var grammar = fs.readFileSync(filename, "utf8"); 17 | var text = fs.readFileSync(path.join('./test/speg_invalid_fixtures/', name + '.txt'), "utf8"); 18 | var result = fs.readFileSync(path.join('./test/speg_invalid_fixtures/', name + '.error'), "utf8"); 19 | var speg = new SPEG(); 20 | expect(function() { 21 | var ast = speg.parse(grammar, text); 22 | }).to.throw(Error); 23 | try { 24 | var ast = speg.parse(grammar, text); 25 | } catch (e) { 26 | e.message.should.equal(result); 27 | } 28 | } 29 | }(valid_files[i]))); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /test/speg_fixtures/escape.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "\\"; -------------------------------------------------------------------------------- /test/speg_fixtures/escape.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "start_position": 0, 4 | "match": "\\", 5 | "rule": "a", 6 | "end_position": 1 7 | } -------------------------------------------------------------------------------- /test/speg_fixtures/escape.peg.txt: -------------------------------------------------------------------------------- 1 | \ -------------------------------------------------------------------------------- /test/speg_fixtures/pegjs_example.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR Math_Expression_Grammar 2 | 3 | Math -> Expression EOF; 4 | 5 | Expression -> Term (_ ("+" / "-") _ Term)*; 6 | 7 | Term -> Factor (_ ("*" / "/") _ Factor)*; 8 | 9 | Factor -> ("(" _ Expression _ ")") / Integer; 10 | 11 | Integer -> [0-9]+; 12 | 13 | _ -> [ \t\n\r]*; -------------------------------------------------------------------------------- /test/speg_fixtures/pegjs_example.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "children": [ 3 | { 4 | "children": [ 5 | { 6 | "children": [ 7 | { 8 | "children": [ 9 | { 10 | "children": [ 11 | { 12 | "end_position": 1, 13 | "match": "2", 14 | "start_position": 0, 15 | "type": "regex_char" 16 | } 17 | ], 18 | "end_position": 1, 19 | "match": "2", 20 | "rule": "Integer", 21 | "start_position": 0, 22 | "type": "one_or_more" 23 | } 24 | ], 25 | "end_position": 1, 26 | "match": "2", 27 | "rule": "Factor", 28 | "start_position": 0, 29 | "type": "ordered_choice" 30 | }, 31 | { 32 | "children": [ 33 | { 34 | "children": [ 35 | { 36 | "children": [ 37 | { 38 | "end_position": 2, 39 | "match": " ", 40 | "start_position": 1, 41 | "type": "regex_char" 42 | } 43 | ], 44 | "end_position": 2, 45 | "match": " ", 46 | "rule": "_", 47 | "start_position": 1, 48 | "type": "zero_or_more" 49 | }, 50 | { 51 | "children": [ 52 | { 53 | "end_position": 3, 54 | "match": "*", 55 | "start_position": 2, 56 | "type": "string" 57 | } 58 | ], 59 | "end_position": 3, 60 | "match": "*", 61 | "start_position": 2, 62 | "type": "ordered_choice" 63 | }, 64 | { 65 | "children": [ 66 | { 67 | "end_position": 4, 68 | "match": " ", 69 | "start_position": 3, 70 | "type": "regex_char" 71 | } 72 | ], 73 | "end_position": 4, 74 | "match": " ", 75 | "rule": "_", 76 | "start_position": 3, 77 | "type": "zero_or_more" 78 | }, 79 | { 80 | "children": [ 81 | { 82 | "children": [ 83 | { 84 | "end_position": 5, 85 | "match": "(", 86 | "start_position": 4, 87 | "type": "string" 88 | }, 89 | { 90 | "children": [], 91 | "end_position": 5, 92 | "match": null, 93 | "rule": "_", 94 | "start_position": 5, 95 | "type": "zero_or_more" 96 | }, 97 | { 98 | "children": [ 99 | { 100 | "children": [ 101 | { 102 | "children": [ 103 | { 104 | "children": [ 105 | { 106 | "end_position": 6, 107 | "match": "3", 108 | "start_position": 5, 109 | "type": "regex_char" 110 | } 111 | ], 112 | "end_position": 6, 113 | "match": "3", 114 | "rule": "Integer", 115 | "start_position": 5, 116 | "type": "one_or_more" 117 | } 118 | ], 119 | "end_position": 6, 120 | "match": "3", 121 | "rule": "Factor", 122 | "start_position": 5, 123 | "type": "ordered_choice" 124 | }, 125 | { 126 | "children": [], 127 | "end_position": 6, 128 | "match": null, 129 | "start_position": 6, 130 | "type": "zero_or_more" 131 | } 132 | ], 133 | "end_position": 6, 134 | "match": "3", 135 | "rule": "Term", 136 | "start_position": 5, 137 | "type": "sequence" 138 | }, 139 | { 140 | "children": [ 141 | { 142 | "children": [ 143 | { 144 | "children": [ 145 | { 146 | "end_position": 7, 147 | "match": " ", 148 | "start_position": 6, 149 | "type": "regex_char" 150 | } 151 | ], 152 | "end_position": 7, 153 | "match": " ", 154 | "rule": "_", 155 | "start_position": 6, 156 | "type": "zero_or_more" 157 | }, 158 | { 159 | "children": [ 160 | { 161 | "end_position": 8, 162 | "match": "+", 163 | "start_position": 7, 164 | "type": "string" 165 | } 166 | ], 167 | "end_position": 8, 168 | "match": "+", 169 | "start_position": 7, 170 | "type": "ordered_choice" 171 | }, 172 | { 173 | "children": [ 174 | { 175 | "end_position": 9, 176 | "match": " ", 177 | "start_position": 8, 178 | "type": "regex_char" 179 | } 180 | ], 181 | "end_position": 9, 182 | "match": " ", 183 | "rule": "_", 184 | "start_position": 8, 185 | "type": "zero_or_more" 186 | }, 187 | { 188 | "children": [ 189 | { 190 | "children": [ 191 | { 192 | "children": [ 193 | { 194 | "end_position": 10, 195 | "match": "4", 196 | "start_position": 9, 197 | "type": "regex_char" 198 | } 199 | ], 200 | "end_position": 10, 201 | "match": "4", 202 | "rule": "Integer", 203 | "start_position": 9, 204 | "type": "one_or_more" 205 | } 206 | ], 207 | "end_position": 10, 208 | "match": "4", 209 | "rule": "Factor", 210 | "start_position": 9, 211 | "type": "ordered_choice" 212 | }, 213 | { 214 | "children": [], 215 | "end_position": 10, 216 | "match": null, 217 | "start_position": 10, 218 | "type": "zero_or_more" 219 | } 220 | ], 221 | "end_position": 10, 222 | "match": "4", 223 | "rule": "Term", 224 | "start_position": 9, 225 | "type": "sequence" 226 | } 227 | ], 228 | "end_position": 10, 229 | "match": " + 4", 230 | "start_position": 6, 231 | "type": "sequence" 232 | } 233 | ], 234 | "end_position": 10, 235 | "match": " + 4", 236 | "start_position": 6, 237 | "type": "zero_or_more" 238 | } 239 | ], 240 | "end_position": 10, 241 | "rule": "Expression", 242 | "match": "3 + 4", 243 | "start_position": 5, 244 | "type": "sequence" 245 | }, 246 | { 247 | "children": [], 248 | "end_position": 10, 249 | "rule": "_", 250 | "match": null, 251 | "start_position": 10, 252 | "type": "zero_or_more" 253 | }, 254 | { 255 | "end_position": 11, 256 | "match": ")", 257 | "start_position": 10, 258 | "type": "string" 259 | } 260 | ], 261 | "end_position": 11, 262 | "match": "(3 + 4)", 263 | "start_position": 4, 264 | "type": "sequence" 265 | } 266 | ], 267 | "end_position": 11, 268 | "rule": "Factor", 269 | "match": "(3 + 4)", 270 | "start_position": 4, 271 | "type": "ordered_choice" 272 | } 273 | ], 274 | "end_position": 11, 275 | "match": " * (3 + 4)", 276 | "start_position": 1, 277 | "type": "sequence" 278 | } 279 | ], 280 | "end_position": 11, 281 | "match": " * (3 + 4)", 282 | "start_position": 1, 283 | "type": "zero_or_more" 284 | } 285 | ], 286 | "end_position": 11, 287 | "match": "2 * (3 + 4)", 288 | "rule": "Term", 289 | "start_position": 0, 290 | "type": "sequence" 291 | }, 292 | { 293 | "children": [], 294 | "end_position": 11, 295 | "match": null, 296 | "start_position": 11, 297 | "type": "zero_or_more" 298 | } 299 | ], 300 | "end_position": 11, 301 | "match": "2 * (3 + 4)", 302 | "start_position": 0, 303 | "rule": "Expression", 304 | "type": "sequence" 305 | }, 306 | { 307 | "children": [], 308 | "end_position": 11, 309 | "match": null, 310 | "start_position": 11, 311 | "type": "end_of_file" 312 | } 313 | ], 314 | "end_position": 11, 315 | "rule": "Math", 316 | "match": "2 * (3 + 4)", 317 | "start_position": 0, 318 | "type": "sequence" 319 | } -------------------------------------------------------------------------------- /test/speg_fixtures/pegjs_example.peg.txt: -------------------------------------------------------------------------------- 1 | 2 * (3 + 4) -------------------------------------------------------------------------------- /test/speg_fixtures/simple_and_predicate.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> &"A"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_and_predicate.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "and_predicate", 3 | "start_position": 0, 4 | "rule": "a", 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "A", 10 | "end_position": 1 11 | } 12 | ], 13 | "match": null, 14 | "end_position": 0 15 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_and_predicate.peg.txt: -------------------------------------------------------------------------------- 1 | AA -------------------------------------------------------------------------------- /test/speg_fixtures/simple_eof.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A" EOF; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_eof.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "sequence", 3 | "start_position": 0, 4 | "rule": "a", 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "A", 10 | "end_position": 1 11 | }, 12 | { 13 | "type": "end_of_file", 14 | "start_position": 1, 15 | "children": [], 16 | "match": null, 17 | "end_position": 1 18 | } 19 | ], 20 | "match": "A", 21 | "end_position": 1 22 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_eof.peg.txt: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /test/speg_fixtures/simple_not_predicate.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> !"A"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_not_predicate.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "not_predicate", 3 | "start_position": 0, 4 | "children": [], 5 | "rule": "a", 6 | "match": null, 7 | "end_position": 0 8 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_not_predicate.peg.txt: -------------------------------------------------------------------------------- 1 | BB -------------------------------------------------------------------------------- /test/speg_fixtures/simple_one_or_more.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A"+; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_one_or_more.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "one_or_more", 3 | "start_position": 0, 4 | "rule": "a", 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "A", 10 | "end_position": 1 11 | }, 12 | { 13 | "type": "string", 14 | "start_position": 1, 15 | "match": "A", 16 | "end_position": 2 17 | } 18 | ], 19 | "match": "AA", 20 | "end_position": 2 21 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_one_or_more.peg.txt: -------------------------------------------------------------------------------- 1 | AA -------------------------------------------------------------------------------- /test/speg_fixtures/simple_optional.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A"?; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_optional.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "optional", 3 | "start_position": 0, 4 | "rule": "a", 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "A", 10 | "end_position": 1 11 | } 12 | ], 13 | "match": "A", 14 | "end_position": 1 15 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_optional.peg.txt: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /test/speg_fixtures/simple_ordered_choice.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A" / "B"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_ordered_choice.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ordered_choice", 3 | "start_position": 0, 4 | "rule": "a", 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "B", 10 | "end_position": 1 11 | } 12 | ], 13 | "match": "B", 14 | "end_position": 1 15 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_ordered_choice.peg.txt: -------------------------------------------------------------------------------- 1 | B -------------------------------------------------------------------------------- /test/speg_fixtures/simple_regex.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> [0-9]; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_regex.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "regex_char", 3 | "start_position": 0, 4 | "match": "7", 5 | "rule": "a", 6 | "end_position": 1 7 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_regex.peg.txt: -------------------------------------------------------------------------------- 1 | 7 -------------------------------------------------------------------------------- /test/speg_fixtures/simple_rule_call.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> b; 4 | b -> "A"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_rule_call.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "start_position": 0, 4 | "match": "A", 5 | 6 | "rule": "a", 7 | "end_position": 1 8 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_rule_call.peg.txt: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /test/speg_fixtures/simple_rule_call_underscore.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> _; 4 | _ -> "A"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_rule_call_underscore.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "start_position": 0, 4 | "match": "A", 5 | "rule": "a", 6 | "end_position": 1 7 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_rule_call_underscore.peg.txt: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /test/speg_fixtures/simple_sequence.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A" "B" "C"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_sequence.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "sequence", 3 | "rule": "a", 4 | "start_position": 0, 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "A", 10 | "end_position": 1 11 | }, 12 | { 13 | "type": "string", 14 | "start_position": 1, 15 | "match": "B", 16 | "end_position": 2 17 | }, 18 | { 19 | "type": "string", 20 | "start_position": 2, 21 | "match": "C", 22 | "end_position": 3 23 | } 24 | ], 25 | "match": "ABC", 26 | "end_position": 3 27 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_sequence.peg.txt: -------------------------------------------------------------------------------- 1 | ABC -------------------------------------------------------------------------------- /test/speg_fixtures/simple_string.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A"; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_string.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "rule": "a", 4 | "start_position": 0, 5 | "match": "A", 6 | "end_position": 1 7 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_string.peg.txt: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /test/speg_fixtures/simple_zero_or_more.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "A"*; -------------------------------------------------------------------------------- /test/speg_fixtures/simple_zero_or_more.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "zero_or_more", 3 | "start_position": 0, 4 | "rule": "a", 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "A", 10 | "end_position": 1 11 | }, 12 | { 13 | "type": "string", 14 | "start_position": 1, 15 | "match": "A", 16 | "end_position": 2 17 | } 18 | ], 19 | "match": "AA", 20 | "end_position": 2 21 | } -------------------------------------------------------------------------------- /test/speg_fixtures/simple_zero_or_more.peg.txt: -------------------------------------------------------------------------------- 1 | AA -------------------------------------------------------------------------------- /test/speg_fixtures/stringstring.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> "\"" "A"; -------------------------------------------------------------------------------- /test/speg_fixtures/stringstring.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "sequence", 3 | "rule": "a", 4 | "start_position": 0, 5 | "children": [ 6 | { 7 | "type": "string", 8 | "start_position": 0, 9 | "match": "\"", 10 | "end_position": 1 11 | }, 12 | { 13 | "type": "string", 14 | "start_position": 1, 15 | "match": "A", 16 | "end_position": 2 17 | } 18 | ], 19 | "match": "\"A", 20 | "end_position": 2 21 | } -------------------------------------------------------------------------------- /test/speg_fixtures/stringstring.peg.txt: -------------------------------------------------------------------------------- 1 | "A -------------------------------------------------------------------------------- /test/speg_fixtures/tag.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "start_position": 0, 4 | "match": "A", 5 | "rule": "a", 6 | "tags": [ 7 | "tag" 8 | ], 9 | "end_position": 1 10 | } -------------------------------------------------------------------------------- /test/speg_fixtures/tag.peg.txt: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /test/speg_fixtures/tag.peg1: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> tag:"A"; -------------------------------------------------------------------------------- /test/speg_fixtures/tags.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "sequence", 3 | "rule": "a", 4 | "start_position": 0, 5 | "match": "AA", 6 | "children": [ 7 | { 8 | "end_position": 1, 9 | "rule": "b", 10 | "match": "A", 11 | "start_position": 0, 12 | "tags": [ 13 | "tag1", 14 | "tag2", 15 | "last" 16 | ], 17 | "type": "string" 18 | }, 19 | { 20 | "end_position": 2, 21 | "match": "A", 22 | "rule": "c", 23 | "start_position": 1, 24 | "tags": [ 25 | "ctag", 26 | "last" 27 | ], 28 | "type": "string" 29 | } 30 | ], 31 | "end_position": 2 32 | } -------------------------------------------------------------------------------- /test/speg_fixtures/tags.peg.txt: -------------------------------------------------------------------------------- 1 | AA -------------------------------------------------------------------------------- /test/speg_fixtures/tags.peg1: -------------------------------------------------------------------------------- 1 | GRAMMAR test 2 | 3 | a -> tag1:tag2:b ctag:c; 4 | b -> c; 5 | c -> last:"A"; -------------------------------------------------------------------------------- /test/speg_fixtures/url.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | url -> scheme "://" host pathname search hash? EOF; 4 | scheme -> "http" "s"?; 5 | host -> hostname port?; 6 | hostname -> segment ("." segment)*; 7 | segment -> [a-z0-9-]+; 8 | port -> ":" [0-9]+; 9 | pathname -> "/" [^ ?]*; 10 | search -> ("?" [^ #]*)?; 11 | hash -> "#" [^ ]*; 12 | -------------------------------------------------------------------------------- /test/speg_fixtures/url.peg.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "sequence", 3 | "start_position": 0, 4 | "children": [ 5 | { 6 | "type": "sequence", 7 | "start_position": 0, 8 | "children": [ 9 | { 10 | "type": "string", 11 | "start_position": 0, 12 | "match": "http", 13 | "end_position": 4 14 | }, 15 | { 16 | "type": "optional", 17 | "start_position": 4, 18 | "children": null, 19 | "match": null, 20 | "end_position": 4 21 | } 22 | ], 23 | "match": "http", 24 | "rule": "scheme", 25 | "end_position": 4 26 | }, 27 | { 28 | "type": "string", 29 | "start_position": 4, 30 | "match": "://", 31 | "end_position": 7 32 | }, 33 | { 34 | "type": "sequence", 35 | "start_position": 7, 36 | "rule": "host", 37 | "children": [ 38 | { 39 | "type": "sequence", 40 | "start_position": 7, 41 | "rule": "hostname", 42 | "children": [ 43 | { 44 | "type": "one_or_more", 45 | "start_position": 7, 46 | "rule": "segment", 47 | "children": [ 48 | { 49 | "type": "regex_char", 50 | "start_position": 7, 51 | "match": "i", 52 | "end_position": 8 53 | }, 54 | { 55 | "type": "regex_char", 56 | "start_position": 8, 57 | "match": "p", 58 | "end_position": 9 59 | }, 60 | { 61 | "type": "regex_char", 62 | "start_position": 9, 63 | "match": "o", 64 | "end_position": 10 65 | }, 66 | { 67 | "type": "regex_char", 68 | "start_position": 10, 69 | "match": "n", 70 | "end_position": 11 71 | }, 72 | { 73 | "type": "regex_char", 74 | "start_position": 11, 75 | "match": "w", 76 | "end_position": 12 77 | }, 78 | { 79 | "type": "regex_char", 80 | "start_position": 12, 81 | "match": "e", 82 | "end_position": 13 83 | }, 84 | { 85 | "type": "regex_char", 86 | "start_position": 13, 87 | "match": "b", 88 | "end_position": 14 89 | } 90 | ], 91 | "match": "iponweb", 92 | "end_position": 14 93 | }, 94 | { 95 | "type": "zero_or_more", 96 | "start_position": 14, 97 | "children": [ 98 | { 99 | "type": "sequence", 100 | "start_position": 14, 101 | "children": [ 102 | { 103 | "type": "string", 104 | "start_position": 14, 105 | "match": ".", 106 | "end_position": 15 107 | }, 108 | { 109 | "type": "one_or_more", 110 | "rule": "segment", 111 | "start_position": 15, 112 | "children": [ 113 | { 114 | "type": "regex_char", 115 | "start_position": 15, 116 | "match": "n", 117 | "end_position": 16 118 | }, 119 | { 120 | "type": "regex_char", 121 | "start_position": 16, 122 | "match": "e", 123 | "end_position": 17 124 | }, 125 | { 126 | "type": "regex_char", 127 | "start_position": 17, 128 | "match": "t", 129 | "end_position": 18 130 | } 131 | ], 132 | "match": "net", 133 | "end_position": 18 134 | } 135 | ], 136 | "match": ".net", 137 | "end_position": 18 138 | } 139 | ], 140 | "match": ".net", 141 | "end_position": 18 142 | } 143 | ], 144 | "match": "iponweb.net", 145 | "end_position": 18 146 | }, 147 | { 148 | "type": "optional", 149 | "start_position": 18, 150 | "children": null, 151 | "match": null, 152 | "end_position": 18 153 | } 154 | ], 155 | "match": "iponweb.net", 156 | "end_position": 18 157 | }, 158 | { 159 | "type": "sequence", 160 | "rule": "pathname", 161 | "start_position": 18, 162 | "children": [ 163 | { 164 | "type": "string", 165 | "start_position": 18, 166 | "match": "/", 167 | "end_position": 19 168 | }, 169 | { 170 | "type": "zero_or_more", 171 | "start_position": 19, 172 | "children": [ 173 | { 174 | "type": "regex_char", 175 | "start_position": 19, 176 | "match": "s", 177 | "end_position": 20 178 | }, 179 | { 180 | "type": "regex_char", 181 | "start_position": 20, 182 | "match": "o", 183 | "end_position": 21 184 | }, 185 | { 186 | "type": "regex_char", 187 | "start_position": 21, 188 | "match": "m", 189 | "end_position": 22 190 | }, 191 | { 192 | "type": "regex_char", 193 | "start_position": 22, 194 | "match": "e", 195 | "end_position": 23 196 | }, 197 | { 198 | "type": "regex_char", 199 | "start_position": 23, 200 | "match": "p", 201 | "end_position": 24 202 | }, 203 | { 204 | "type": "regex_char", 205 | "start_position": 24, 206 | "match": "a", 207 | "end_position": 25 208 | }, 209 | { 210 | "type": "regex_char", 211 | "start_position": 25, 212 | "match": "g", 213 | "end_position": 26 214 | }, 215 | { 216 | "type": "regex_char", 217 | "start_position": 26, 218 | "match": "e", 219 | "end_position": 27 220 | }, 221 | { 222 | "type": "regex_char", 223 | "start_position": 27, 224 | "match": "#", 225 | "end_position": 28 226 | }, 227 | { 228 | "type": "regex_char", 229 | "start_position": 28, 230 | "match": "h", 231 | "end_position": 29 232 | }, 233 | { 234 | "type": "regex_char", 235 | "start_position": 29, 236 | "match": "a", 237 | "end_position": 30 238 | }, 239 | { 240 | "type": "regex_char", 241 | "start_position": 30, 242 | "match": "s", 243 | "end_position": 31 244 | }, 245 | { 246 | "type": "regex_char", 247 | "start_position": 31, 248 | "match": "h", 249 | "end_position": 32 250 | } 251 | ], 252 | "match": "somepage#hash", 253 | "end_position": 32 254 | } 255 | ], 256 | "match": "/somepage#hash", 257 | "end_position": 32 258 | }, 259 | { 260 | "type": "optional", 261 | "rule": "search", 262 | "start_position": 32, 263 | "children": null, 264 | "match": null, 265 | "end_position": 32 266 | }, 267 | { 268 | "type": "optional", 269 | "start_position": 32, 270 | "children": null, 271 | "match": null, 272 | "end_position": 32 273 | }, 274 | { 275 | "type": "end_of_file", 276 | "start_position": 32, 277 | "children": [], 278 | "match": null, 279 | "end_position": 32 280 | } 281 | ], 282 | "match": "http://iponweb.net/somepage#hash", 283 | "rule": "url", 284 | "end_position": 32 285 | } -------------------------------------------------------------------------------- /test/speg_fixtures/url.peg.txt: -------------------------------------------------------------------------------- 1 | http://iponweb.net/somepage#hash -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/double_space_with_wrong_end.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin" [0-9] "end"; 4 | other -> "A" / [0-9]; 5 | other_next -> "A" / test / "sdasda"; 6 | 7 | next_seq -> other test 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/empty_and_predicate.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> &"begin" [0-9] "end"; 4 | other -> "A" / &; 5 | other_next -> "A" / & / "sdasda"; 6 | 7 | next_seq -> & &; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/empty_one_or_more.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin"+ [0-9] "end"; 4 | other -> "A" / +; 5 | other_next -> "A" / + / "sdasda"; 6 | 7 | next_seq -> + test+; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/empty_optional.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin"? [0-9] "end"; 4 | other -> "A" / ?; 5 | other_next -> "A" / ? / "sdasda"; 6 | 7 | next_seq -> ? test?; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/empty_zero_or_more.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin"* [0-9] "end"; 4 | other -> "A" / *; 5 | other_next -> "A" / test* / "sdasda"; 6 | 7 | next_seq -> * test*; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/grammar_numbers_at_start.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR 123numbers 2 | 3 | test -> "test"; -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/rule_numbers_at_start.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR numbers_at_start 2 | 3 | a -> 1b b2 b3 b999; 4 | 1b -> "test"; 5 | b2 -> "test"; 6 | b3 -> "test"; 7 | b999 -> "test"; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/simple_regex_char_unescaped.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "asda"; 4 | lowercase -> [a-z]]; 5 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/invalid/simple_string_no_space.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR urltest -> "asda";newtest -> "sda"; 2 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/and_predicate.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> &"begin" [0-9] "end"; 4 | other -> "A" / &[0-9]; 5 | other_next -> "A" / &test / "sdasda"; 6 | 7 | next_seq -> &other &test; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/grammar_camel_case.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR CamelCase 2 | 3 | test -> "test"; -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/grammar_lower_numbers.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR numbers123 2 | 3 | test -> "test"; -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/grammar_lower_undescore.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR lower_case_underscore 2 | 3 | test -> "test"; -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/not_predicate.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> !"begin" [0-9] "end"; 4 | other -> "A" / ![0-9]; 5 | other_next -> "A" / !test / "sdasda"; 6 | 7 | next_seq -> !other !test; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/one_or_more.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin"+ [0-9] "end"; 4 | other -> "A" / [0-9]+; 5 | other_next -> "A" / test+ / "sdasda"; 6 | 7 | next_seq -> other+ test+; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/optional.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin"? [0-9] "end"; 4 | other -> "A" / [0-9]?; 5 | other_next -> "A" / test? / "sdasda"; 6 | 7 | next_seq -> other? test?; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/ordered_choice.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin" [0-9] "end"; 4 | other -> "A" / [0-9]; 5 | other_next -> "A" / [0-9] / "sdasda"; 6 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/rule_call.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin" [0-9] "end"; 4 | other -> "A" / [0-9]; 5 | other_next -> "A" / test / "sdasda"; 6 | 7 | next_seq -> other test; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/rule_call_numbers.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | a -> b1 b2 b3 b999; 4 | b1 -> "test"; 5 | b2 -> "test"; 6 | b3 -> "test"; 7 | b999 -> "test"; 8 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/rule_call_underscrore.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | a -> _; 4 | _ -> "test"; 5 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/sequence.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin" [0-9] "end"; 4 | other -> "A"; 5 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/simple_regex_char.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "asda"; 4 | lowercase -> [a-z]; 5 | uppercase -> [A-Z]; 6 | numbers -> [0-9]; 7 | combo -> [a-zA-Z0-9_\\]]; 8 | 9 | not_lowercase -> [^a-z]; 10 | not_uppercase -> [^A-Z]; 11 | not_numbers -> [^0-9]; 12 | not_combo -> [^a-zA-Z0-9_]; 13 | 14 | dot -> .; 15 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/simple_string.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "asda"; 4 | newtest -> "sda"; 5 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/simple_string_no_space.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url test ->"asda";newtest-> "sda"; 2 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/simple_string_space_before_grammar.peg: -------------------------------------------------------------------------------- 1 | 2 | 3 | GRAMMAR url test ->"asda";newtest-> "sda"; 4 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/single_char_rule_call.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url test -> a; -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/tag.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR tag 2 | 3 | tag -> tag:"tag"; 4 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/tags_url.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | url -> scheme "://" host pathname search hash?; 4 | scheme -> "http" "s"?; 5 | host -> hostname port?; 6 | hostname -> segment ("." segment)*; 7 | segment -> [a-z0-9-]+; 8 | port -> ":" [0-9]+; 9 | pathname -> "/" [^ ?]*; 10 | search -> ("?" [^ #]*)?; 11 | hash -> "#" [^ ]*; 12 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/url.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | url -> scheme "://" host pathname search hash?; 4 | scheme -> "http" "s"?; 5 | host -> hostname port?; 6 | hostname -> segment ("." segment)*; 7 | segment -> [a-z0-9-]+; 8 | port -> ":" [0-9]+; 9 | pathname -> "/" [^ ?]*; 10 | search -> ("?" [^ #]*)?; 11 | hash -> "#" [^ ]*; 12 | -------------------------------------------------------------------------------- /test/speg_grammar_fixtures/valid/zero_or_more.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR url 2 | 3 | test -> "begin"* [0-9] "end"; 4 | other -> "A" / [0-9]*; 5 | other_next -> "A" / test* / "sdasda"; 6 | 7 | next_seq -> other* test*; 8 | -------------------------------------------------------------------------------- /test/speg_invalid_fixtures/optional-negation.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR optional_genation 2 | 3 | exp -> negation? term EOF; 4 | negation -> "NOT" " "; 5 | term -> "1"; -------------------------------------------------------------------------------- /test/speg_invalid_fixtures/optional-negation.peg.error: -------------------------------------------------------------------------------- 1 | Failed to parse text: 2 | 3 | Unexpected "1" expected ( ) 4 | 1: NOT1 5 | ------^ -------------------------------------------------------------------------------- /test/speg_invalid_fixtures/optional-negation.peg.txt: -------------------------------------------------------------------------------- 1 | NOT1 -------------------------------------------------------------------------------- /test/speg_invalid_fixtures/sequence.peg: -------------------------------------------------------------------------------- 1 | GRAMMAR sequence 2 | 3 | Test -> Expression EOF; 4 | Expression -> Term ("+" Term)*; 5 | Term -> Group / "1"; 6 | Group -> "(" Expression ")"; -------------------------------------------------------------------------------- /test/speg_invalid_fixtures/sequence.peg.error: -------------------------------------------------------------------------------- 1 | Failed to parse text: 2 | 3 | Unexpected "#" expected (( or 1) 4 | 1: (1+# 5 | ------^ -------------------------------------------------------------------------------- /test/speg_invalid_fixtures/sequence.peg.txt: -------------------------------------------------------------------------------- 1 | (1+# -------------------------------------------------------------------------------- /test/speg_module.fixtures.js: -------------------------------------------------------------------------------- 1 | var simplepeg = require('./../src/speg') 2 | var SPEG = simplepeg.SPEG; 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var mockery = require('mockery'); 6 | var recursiveReadSync = require('recursive-readdir-sync'); 7 | var valid_files = recursiveReadSync('./test/speg_fixtures'); 8 | 9 | describe('speg module - fixtures - ', function() { 10 | beforeEach(function(){ 11 | mockery.enable({ warnOnReplace: false, useCleanCache: true }); 12 | mockery.registerAllowable('./../parser'); 13 | mockery.registerMock('simplepeg', simplepeg); 14 | }); 15 | afterEach(function() { 16 | mockery.deregisterMock('simplepeg'); 17 | mockery.disable(); 18 | }); 19 | valid_files = valid_files.filter(function(filename) { 20 | return /\.peg$/.test(filename); 21 | }) 22 | for (var i = 0, len = valid_files.length; i < len; i++) { 23 | it('should parse - ' + valid_files[i], (function (filename) { 24 | return function () { 25 | var name = path.basename(filename); 26 | var grammar = fs.readFileSync(filename, "utf8"); 27 | var text = fs.readFileSync(path.join('./test/speg_fixtures/', name + '.txt'), "utf8"); 28 | var result = fs.readFileSync(path.join('./test/speg_fixtures/', name + '.json'), "utf8"); 29 | var speg = new SPEG(); 30 | var module_source = speg.parse_module(grammar); 31 | fs.writeFileSync('./parser.js', module_source); 32 | delete require.cache[path.resolve('./parser.js')]; 33 | var parser = new (require('./../parser').SPEG); 34 | fs.unlinkSync('./parser.js'); 35 | var ast = parser.parse_text(text); 36 | ast.should.deep.equal(JSON.parse(result)); 37 | } 38 | }(valid_files[i]))); 39 | } 40 | }); -------------------------------------------------------------------------------- /test/speg_parser.fixtures.js: -------------------------------------------------------------------------------- 1 | var SPEG_parser = require('./../src/speg_parser'); 2 | var fs = require('fs'); 3 | var recursiveReadSync = require('recursive-readdir-sync'); 4 | var valid_files = recursiveReadSync('./test/speg_grammar_fixtures/valid'); 5 | var invalid_files = recursiveReadSync('./test/speg_grammar_fixtures/invalid'); 6 | 7 | describe('speg_parser - fixtures - ', function() { 8 | for (var i = 0, len = valid_files.length; i < len; i++) { 9 | it('should parse - ' + valid_files[i], (function (filename) { 10 | return function () { 11 | var input = fs.readFileSync(filename, "utf8"); 12 | var speg = new SPEG_parser(); 13 | var ast = speg.parse(input); 14 | 15 | if (!ast) { 16 | console.log(JSON.stringify(speg.getLastExpectations())); 17 | console.log(speg.get_last_error()); 18 | } 19 | ast.should.be.an('object'); 20 | } 21 | }(valid_files[i]))); 22 | } 23 | for (var i = 0, len = invalid_files.length; i < len; i++) { 24 | it('should fail to parse - ' + invalid_files[i], (function (filename) { 25 | return function () { 26 | var input = fs.readFileSync(filename, "utf8"); 27 | var speg = new SPEG_parser(); 28 | var ast = speg.parse(input); 29 | ast.should.equal(false); 30 | } 31 | }(invalid_files[i]))); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const BundleAnalyzerPlugin = require('@bundle-analyzer/webpack-plugin') 2 | 3 | 4 | module.exports = { 5 | context: __dirname + "/src", 6 | entry: "./speg.js", 7 | output: { 8 | path: __dirname + "/dist", 9 | filename: "speg.js", 10 | library: "simplepeg", 11 | libraryTarget: 'umd', 12 | umdNamedDefine: true 13 | }, 14 | plugins: [new BundleAnalyzerPlugin({ token: 'bd01d591e9a74b92b1bfb2ca8dfc8c416e6fa1d0' })], 15 | }; --------------------------------------------------------------------------------