├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── package.json ├── readme.md ├── src ├── compile.js ├── grammer.pegjs └── index.js └── test ├── expressions.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | pids 10 | logs 11 | results 12 | npm-debug.log 13 | node_modules 14 | /index.js 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .travis.yml 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Forbes Lindesay 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regexp", 3 | "version": "1.0.0", 4 | "description": "Regex Parser", 5 | "keywords": [], 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "uglify-js": "~2.3.6", 9 | "pegjs": "https://github.com/dmajda/pegjs/archive/791034fad92c9cd7a9d1c71187df03441bbfd521.tar.gz", 10 | "bash-color": "0.0.3" 11 | }, 12 | "scripts": { 13 | "test": "node test/index.js", 14 | "prepublish": "npm test" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ForbesLindesay/regexp.git" 19 | }, 20 | "author": "ForbesLindesay", 21 | "license": "MIT" 22 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # regexp 2 | 3 | Regex parser based on descriptions in http://www.javascriptkit.com/javatutors/redev2.shtml 4 | 5 | [![Build Status](https://img.shields.io/travis/ForbesLindesay/regexp/master.svg)](https://travis-ci.org/ForbesLindesay/regexp) 6 | [![Dependency Status](https://img.shields.io/david/ForbesLindesay/regexp.svg)](https://david-dm.org/ForbesLindesay/regexp) 7 | [![NPM version](https://img.shields.io/npm/v/regexp.svg)](https://www.npmjs.com/package/regexp) 8 | 9 | ## Installation 10 | 11 | npm install regexp 12 | 13 | ## Usage 14 | 15 | ```js 16 | var regexp = require('regexp') 17 | var res = regexp('[a-z]+') 18 | assert.deepEqual(res, { type: 'match', 19 | offset: 0, 20 | text: '[a-z]+', 21 | body: 22 | [ { type: 'quantified', 23 | offset: 0, 24 | text: '[a-z]+', 25 | body: 26 | { type: 'charset', 27 | offset: 0, 28 | text: '[a-z]', 29 | invert: false, 30 | body: 31 | [ { type: 'range', 32 | offset: 1, 33 | text: 'a-z', 34 | start: 'a', 35 | end: 'z' } ] }, 36 | quantifier: 37 | { type: 'quantifier', 38 | offset: 5, 39 | text: '+', 40 | min: 1, 41 | max: Infinity, 42 | greedy: true } } ] }) 43 | ``` 44 | 45 | ## Contributing 46 | 47 | To run tests: 48 | 49 | ```console 50 | $ npm install 51 | $ npm test 52 | ``` 53 | 54 | This will also automatically compile `index.js`. 55 | 56 | The key source files are `src/grammer.pegjs` which is compiled using [pegjs](http://pegjs.majda.cz/) and `src/index.js` which is a CommonJS module with a special additional pseudo `import` statement. 57 | 58 | ## License 59 | 60 | MIT -------------------------------------------------------------------------------- /src/compile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var peg = require('pegjs') 5 | var uglify = require('uglify-js') 6 | 7 | var index = fs.readFileSync(__dirname + '/index.js', 'utf8') 8 | var parser = peg.buildParser(fs.readFileSync(__dirname + '/grammer.pegjs', 'utf8'), {trackLineAndColumn: true, output: 'source'}) 9 | 10 | var result = '// This file is automatically generated from the contents of `/src` using `/src/compile.js`\n\n' + index.replace("import './grammer.pegjs'", parser) 11 | 12 | var minify = true 13 | if (minify) { 14 | result = uglify.minify(result, { 15 | fromString: true, 16 | output: { ascii_only: true, indent_level: 2, beautify: true } 17 | }).code 18 | } 19 | Function('exports,module,require', result)(exports, module, require) 20 | 21 | // If we're being called from the command line, output the file 22 | //if (require.main && require.main.id === module.id) { 23 | fs.writeFileSync(__dirname + '/../index.js', result, 'utf8') 24 | //} -------------------------------------------------------------------------------- /src/grammer.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | Token.offset = offset 3 | Token.text = text 4 | } 5 | 6 | regexp = match:match alternate:("|" regexp)? { return alternate ? new Alternate(match, alternate[1]) : match } 7 | 8 | match = start:start? (!quantifier) match:(quantified / submatch)* end:end? { return new Match([start].concat(match).concat([end])) } 9 | submatch = subexp / charset / terminal 10 | 11 | start = "^" { return new Token('start') } 12 | end = "$" { return new Token('end') } 13 | 14 | quantified = submatch:submatch quantifier:quantifier { return new Quantified(submatch, quantifier)} 15 | quantifier "Quantifier" = quantity:quantifierSpec notgreedy:greedyFlag? { if (notgreedy) { quantity.greedy = false } return quantity } 16 | 17 | quantifierSpec = quantifierSpecFull / quantifierSpecAtLeast / quantifierSpecExact / quantifierRequired / quantifierAny / quantifierOptional 18 | quantifierSpecFull = "{" min:integer "," max:integer "}" { return new Quantifier(min, max)} 19 | quantifierSpecAtLeast = "{" min:integer ",}" { return new Quantifier(min, Infinity)} 20 | quantifierSpecExact = "{" value:integer "}" { return new Quantifier(value, value)} 21 | quantifierRequired = "+" { return new Quantifier(1, Infinity)} 22 | quantifierAny = "*" { return new Quantifier(0, Infinity)} 23 | quantifierOptional = "?" { return new Quantifier(0, 1)} 24 | greedyFlag = "?" 25 | 26 | 27 | integer = num:([0-9]+) { return +num.join('') } 28 | 29 | 30 | subexp = "(" body:(positiveLookahead / negativeLookahead / groupNoCapture / groupCapture) ")" { return body} 31 | groupCapture = regexp:regexp { return new CaptureGroup(regexp) } 32 | groupNoCapture = "?:" regexp:regexp { return new Group('non-capture-group', regexp) } 33 | positiveLookahead = "?=" regexp:regexp { return new Group('positive-lookahead', regexp) } 34 | negativeLookahead = "?!" regexp:regexp { return new Group('negative-lookahead', regexp) } 35 | 36 | 37 | charset "CharacterSet" = "[" invert:"^"? body:(charsetRange / charsetTerminal)* "]" { return new CharSet(!!invert, body) } 38 | charsetRange "CharacterRange" = start:charsetTerminal "-" end:charsetTerminal { return new CharacterRange(start, end) } 39 | charsetTerminal "Character" = charsetEscapedCharacter / charsetLiteral 40 | charsetLiteral = value:[^\\\]] { return new Literal(value) } 41 | charsetEscapedCharacter = backspaceCharacter / controlCharacter / digitCharacter / non_digitCharacter / formFeedCharacter / lineFeedCharacter / carriageReturnCharacter / whiteSpaceCharacter / nonWhiteSpaceCharacter / tabCharacter / verticalTabCharacter / wordCharacter / nonWordCharacter / octalCharacter / hexCharacter / unicodeCharacter / nullCharacter / otherEscaped 42 | 43 | terminal = anyCharacter / escapedCharacter / literal 44 | 45 | anyCharacter = "." { return new Token('any-character') } 46 | 47 | literal "Literal" = value:[^|\\/.\[\(\)\?\+\*\$\^] { return new Literal(value) } 48 | 49 | escapedCharacter = word_boundaryCharacter / nonWord_boundaryCharacter / controlCharacter / digitCharacter / non_digitCharacter / formFeedCharacter / lineFeedCharacter / carriageReturnCharacter / whiteSpaceCharacter / nonWhiteSpaceCharacter / tabCharacter / verticalTabCharacter / wordCharacter / nonWordCharacter / backReference / octalCharacter / hexCharacter / unicodeCharacter / nullCharacter / otherEscaped 50 | 51 | backspaceCharacter = "\\b" { return new Token('backspace') } 52 | word_boundaryCharacter = "\\b" { return new Token('word-boundary') } 53 | nonWord_boundaryCharacter = "\\B" { return new Token('non-word-boundary') } 54 | digitCharacter = "\\d" { return new Token('digit') } 55 | non_digitCharacter = "\\D" { return new Token('non-digit') } 56 | formFeedCharacter = "\\f" { return new Token('form-feed') } 57 | lineFeedCharacter = "\\n" { return new Token('line-feed') } 58 | carriageReturnCharacter = "\\r" { return new Token('carriage-return') } 59 | whiteSpaceCharacter = "\\s" { return new Token('white-space') } 60 | nonWhiteSpaceCharacter = "\\S" { return new Token('non-white-space') } 61 | tabCharacter = "\\t" { return new Token('tab') } 62 | verticalTabCharacter = "\\v" { return new Token('vertical-tab') } 63 | wordCharacter = "\\w" { return new Token('word') } 64 | nonWordCharacter = "\\W" { return new Token('non-word') } 65 | 66 | controlCharacter = "\\c" code:. { return new ControlCharacter(code) } 67 | backReference = "\\" code:[1-9] { return new BackReference(code) } 68 | octalCharacter = "\\0" code:([0-7]+) { return new Octal(code.join('')) } 69 | hexCharacter = "\\x" code:([0-9a-fA-F]+) { return new Hex(code.join('')) } 70 | unicodeCharacter = "\\u" code:([0-9a-fA-F]+) { return new Unicode(code.join('')) } 71 | 72 | nullCharacter = "\\0" { return new Token('null-character') } 73 | otherEscaped = "\\" value:. { return new Literal(value) } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var parser = import './grammer.pegjs' 2 | 3 | var index = 1 4 | var cgs = {} 5 | exports = (module.exports = parse) 6 | 7 | function parse(str) { 8 | if (typeof str != 'string') { 9 | var ex = new TypeError('The regexp to parse must be represented as a string.') 10 | throw ex 11 | } 12 | //capture group index 13 | index = 1 14 | cgs = {} 15 | return parser.parse(str) 16 | } 17 | 18 | exports.Token = Token 19 | function Token(type, obj) { 20 | this.type = type 21 | this.offset = Token.offset() 22 | this.text = Token.text() 23 | } 24 | 25 | exports.Alternate = Alternate 26 | function Alternate(left, right) { 27 | Token.call(this, 'alternate') 28 | this.left = left 29 | this.right = right 30 | } 31 | Alternate.prototype = Object.create(Token.prototype) 32 | Alternate.prototype.constructor = Alternate 33 | 34 | exports.Match = Match 35 | function Match(body) { 36 | Token.call(this, 'match') 37 | this.body = body.filter(Boolean) 38 | } 39 | Match.prototype = Object.create(Token.prototype) 40 | Match.prototype.constructor = Match 41 | 42 | exports.Group = Group 43 | function Group(type, body) { 44 | Token.call(this, type) 45 | this.body = body 46 | } 47 | Group.prototype = Object.create(Token.prototype) 48 | Group.prototype.constructor = Group 49 | 50 | exports.CaptureGroup = CaptureGroup 51 | function CaptureGroup(body) { 52 | Group.call(this, 'capture-group') 53 | 54 | // a bug means this gets called multiple times so memoize based on the offset 55 | this.index = cgs[this.offset] || (cgs[this.offset] = index++) 56 | this.body = body 57 | } 58 | CaptureGroup.prototype = Object.create(Group.prototype) 59 | CaptureGroup.prototype.constructor = CaptureGroup 60 | 61 | exports.Quantified = Quantified 62 | function Quantified(body, quantifier) { 63 | Token.call(this, 'quantified') 64 | this.body = body 65 | this.quantifier = quantifier 66 | } 67 | Quantified.prototype = Object.create(Token.prototype) 68 | Quantified.prototype.constructor = Quantified 69 | 70 | exports.Quantifier = Quantifier 71 | function Quantifier(min, max) { 72 | Token.call(this, 'quantifier') 73 | this.min = min 74 | this.max = max 75 | this.greedy = true //initial setting 76 | } 77 | Quantifier.prototype = Object.create(Token.prototype) 78 | Quantifier.prototype.constructor = Quantifier 79 | 80 | exports.CharSet = CharSet 81 | function CharSet(invert, body) { 82 | Token.call(this, 'charset') 83 | this.invert = invert 84 | this.body = body 85 | } 86 | CharSet.prototype = Object.create(Token.prototype) 87 | CharSet.prototype.constructor = CharSet 88 | 89 | exports.CharacterRange = CharacterRange 90 | function CharacterRange(start, end) { 91 | Token.call(this, 'range') 92 | this.start = start 93 | this.end = end 94 | } 95 | CharacterRange.prototype = Object.create(Token.prototype) 96 | CharacterRange.prototype.constructor = CharacterRange 97 | 98 | 99 | exports.Literal = Literal 100 | function Literal(body) { 101 | Token.call(this, 'literal') 102 | this.body = body 103 | this.escaped = this.body != this.text 104 | } 105 | Literal.prototype = Object.create(Token.prototype) 106 | Literal.prototype.constructor = Literal 107 | 108 | exports.Unicode = Unicode 109 | function Unicode(code) { 110 | Token.call(this, 'unicode') 111 | this.code = code.toUpperCase() 112 | } 113 | Unicode.prototype = Object.create(Token.prototype) 114 | Unicode.prototype.constructor = Unicode 115 | 116 | exports.Hex = Hex 117 | function Hex(code) { 118 | Token.call(this, 'hex') 119 | this.code = code.toUpperCase() 120 | } 121 | Hex.prototype = Object.create(Token.prototype) 122 | Hex.prototype.constructor = Hex 123 | 124 | exports.Octal = Octal 125 | function Octal(code) { 126 | Token.call(this, 'octal') 127 | this.code = code.toUpperCase() 128 | } 129 | Octal.prototype = Object.create(Token.prototype) 130 | Octal.prototype.constructor = Octal 131 | 132 | exports.BackReference = BackReference 133 | function BackReference(code) { 134 | Token.call(this, 'back-reference') 135 | this.code = code.toUpperCase() 136 | } 137 | BackReference.prototype = Object.create(Token.prototype) 138 | BackReference.prototype.constructor = BackReference 139 | 140 | exports.ControlCharacter = ControlCharacter 141 | function ControlCharacter(code) { 142 | Token.call(this, 'control-character') 143 | this.code = code.toUpperCase() 144 | } 145 | ControlCharacter.prototype = Object.create(Token.prototype) 146 | ControlCharacter.prototype.constructor = ControlCharacter 147 | -------------------------------------------------------------------------------- /test/expressions.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | [/[a-z]+/, { 3 | type: 'match', 4 | offset: 0, 5 | text: '[a-z]+', 6 | body: [ 7 | { 8 | type: 'quantified', 9 | offset: 0, 10 | text: '[a-z]+', 11 | body: { 12 | type: 'charset', 13 | offset: 0, 14 | text: '[a-z]', 15 | invert: false, 16 | body: [{ 17 | type: 'range', 18 | offset: 1, 19 | text: 'a-z', 20 | start: { 21 | type: 'literal', 22 | offset: 1, 23 | text: 'a', 24 | body: 'a', 25 | escaped: false 26 | }, 27 | end: { 28 | type: 'literal', 29 | offset: 3, 30 | text: 'z', 31 | body: 'z', 32 | escaped: false 33 | } 34 | }] 35 | }, 36 | quantifier: { 37 | type: 'quantifier', 38 | offset: 5, 39 | text: '+', 40 | min: 1, 41 | max: Infinity, 42 | greedy: true 43 | } 44 | } 45 | ] 46 | }], 47 | [/a|bc*/, { 48 | type: 'alternate', 49 | offset: 0, 50 | text: 'a|bc*', 51 | left: { 52 | type: 'match', 53 | offset: 0, 54 | text: 'a', 55 | body:[{ 56 | type: 'literal', 57 | offset: 0, 58 | text: 'a', 59 | body: 'a', 60 | escaped: false 61 | }] 62 | }, 63 | right: { 64 | type: 'match', 65 | offset: 2, 66 | text: 'bc*', 67 | body: [ 68 | { 69 | type: 'literal', 70 | offset: 2, 71 | text: 'b', 72 | body: 'b', 73 | escaped: false 74 | }, 75 | { 76 | type: 'quantified', 77 | offset: 3, 78 | text: 'c*', 79 | body: { 80 | type: 'literal', 81 | offset: 3, 82 | text: 'c', 83 | body: 'c', 84 | escaped: false 85 | }, 86 | quantifier: { 87 | type: 'quantifier', 88 | offset: 4, 89 | text: '*', 90 | min: 0, 91 | max: Infinity, 92 | greedy: true 93 | } 94 | } 95 | ] 96 | } 97 | }], 98 | [/\n\b[\-]/, { 99 | type: 'match', 100 | offset: 0, 101 | text: '\\n\\b[\\-]', 102 | body: [ 103 | { 104 | type: 'line-feed', 105 | offset: 0, 106 | text: '\\n' 107 | }, 108 | { 109 | type: 'word-boundary', 110 | offset: 2, 111 | text: '\\b' 112 | }, 113 | { 114 | type: 'charset', 115 | offset: 4, 116 | text: '[\\-]', 117 | invert: false, 118 | body: [{ 119 | type: 'literal', 120 | offset: 5, 121 | text: '\\-', 122 | body: '-', 123 | escaped: true 124 | }] 125 | } 126 | ] 127 | }], 128 | [/\u1Af/, { 129 | type: 'match', 130 | offset: 0, 131 | text: '\\u1Af', 132 | body: [{ 133 | type: 'unicode', 134 | offset: 0, 135 | text: '\\u1Af', 136 | code: '1AF' 137 | }] 138 | }], 139 | [/again and again/,{ type: 'match', 140 | offset: 0, 141 | text: 'again and again', 142 | body: 143 | [ { type: 'literal', 144 | offset: 0, 145 | text: 'a', 146 | body: 'a', 147 | escaped: false }, 148 | { type: 'literal', 149 | offset: 1, 150 | text: 'g', 151 | body: 'g', 152 | escaped: false }, 153 | { type: 'literal', 154 | offset: 2, 155 | text: 'a', 156 | body: 'a', 157 | escaped: false }, 158 | { type: 'literal', 159 | offset: 3, 160 | text: 'i', 161 | body: 'i', 162 | escaped: false }, 163 | { type: 'literal', 164 | offset: 4, 165 | text: 'n', 166 | body: 'n', 167 | escaped: false }, 168 | { type: 'literal', 169 | offset: 5, 170 | text: ' ', 171 | body: ' ', 172 | escaped: false }, 173 | { type: 'literal', 174 | offset: 6, 175 | text: 'a', 176 | body: 'a', 177 | escaped: false }, 178 | { type: 'literal', 179 | offset: 7, 180 | text: 'n', 181 | body: 'n', 182 | escaped: false }, 183 | { type: 'literal', 184 | offset: 8, 185 | text: 'd', 186 | body: 'd', 187 | escaped: false }, 188 | { type: 'literal', 189 | offset: 9, 190 | text: ' ', 191 | body: ' ', 192 | escaped: false }, 193 | { type: 'literal', 194 | offset: 10, 195 | text: 'a', 196 | body: 'a', 197 | escaped: false }, 198 | { type: 'literal', 199 | offset: 11, 200 | text: 'g', 201 | body: 'g', 202 | escaped: false }, 203 | { type: 'literal', 204 | offset: 12, 205 | text: 'a', 206 | body: 'a', 207 | escaped: false }, 208 | { type: 'literal', 209 | offset: 13, 210 | text: 'i', 211 | body: 'i', 212 | escaped: false }, 213 | { type: 'literal', 214 | offset: 14, 215 | text: 'n', 216 | body: 'n', 217 | escaped: false } ] }], 218 | [/(a)(b)(?:c)(d)/, { type: 'match', 219 | offset: 0, 220 | text: '(a)(b)(?:c)(d)', 221 | body: 222 | [ { type: 'capture-group', 223 | offset: 1, 224 | text: 'a', 225 | body: 226 | { type: 'match', 227 | offset: 1, 228 | text: 'a', 229 | body: 230 | [ { type: 'literal', 231 | offset: 1, 232 | text: 'a', 233 | body: 'a', 234 | escaped: false } ] }, 235 | index: 1 }, 236 | { type: 'capture-group', 237 | offset: 4, 238 | text: 'b', 239 | body: 240 | { type: 'match', 241 | offset: 4, 242 | text: 'b', 243 | body: 244 | [ { type: 'literal', 245 | offset: 4, 246 | text: 'b', 247 | body: 'b', 248 | escaped: false } ] }, 249 | index: 2 }, 250 | { type: 'non-capture-group', 251 | offset: 7, 252 | text: '?:c', 253 | body: 254 | { type: 'match', 255 | offset: 9, 256 | text: 'c', 257 | body: 258 | [ { type: 'literal', 259 | offset: 9, 260 | text: 'c', 261 | body: 'c', 262 | escaped: false } ] } }, 263 | { type: 'capture-group', 264 | offset: 12, 265 | text: 'd', 266 | body: 267 | { type: 'match', 268 | offset: 12, 269 | text: 'd', 270 | body: 271 | [ { type: 'literal', 272 | offset: 12, 273 | text: 'd', 274 | body: 'd', 275 | escaped: false } ] }, 276 | index: 3 } ] }], 277 | [/\cm/, { 278 | type: 'match', 279 | offset: 0, 280 | text: '\\cm', 281 | body: [{ 282 | type: 'control-character', 283 | offset: 0, 284 | text: '\\cm', 285 | code: 'M' 286 | }] 287 | }] 288 | ] -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var assert = require('assert') 3 | 4 | var color = require('bash-color') 5 | 6 | var regex 7 | try { 8 | regex = require('../src/compile.js') 9 | } catch (ex) { 10 | if (ex.code != 'MODULE_NOT_FOUND') throw ex 11 | else regex = require('../') 12 | } 13 | var expressions = require('./expressions.js') 14 | 15 | var all = process.argv[2] === '--all' || process.argv[2] === '-a' 16 | var failed = false 17 | expressions.forEach(function (expression) { 18 | console.log() 19 | 20 | var exp = /^\/(.*)\/$/.exec(expression[0].toString())[1] 21 | var parsed = regex(exp) 22 | try { 23 | assert.deepEqual(parsed, expression[1]) 24 | console.log(color.green(expression[0])) 25 | if (all) { 26 | console.log() 27 | inspect(parsed, ' ') 28 | } 29 | } catch (ex) { 30 | console.log(color.red(expression[0])) 31 | console.log() 32 | inspect(parsed, ' ') 33 | failed = true 34 | } 35 | }) 36 | 37 | if (failed) { 38 | throw new Error('At least one test failed, see above.') 39 | } 40 | 41 | function inspect(obj, indent) { 42 | var options = { 43 | showHidden: false, 44 | depth: 30, 45 | colors: true 46 | } 47 | if (util.inspect.length === 4) { 48 | console.log(util.inspect(obj, options.showHidden, options.depth, options.colors).replace(/^/gm, indent || '')) 49 | } else { 50 | console.log(util.inspect(obj, options).replace(/^/gm, indent || '')) 51 | } 52 | } --------------------------------------------------------------------------------