├── .travis.yml ├── .gitattributes ├── test ├── index.html └── tests.js ├── bower.json ├── .gitignore ├── .npmignore ├── package.json ├── LICENSE ├── re-build.min.js ├── doc └── reference.md ├── re-build.min.map ├── readme.md └── re-build.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.3" 4 | - "5.12" 5 | - "4.4" 6 | - "0.12" 7 | - "0.10" 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RE-Build tests 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re-build", 3 | "main": "re-build.js", 4 | "homepage": "https://github.com/MaxArt2501/re-build", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/MaxArt2501/re-build.git" 8 | }, 9 | "authors": [ 10 | "Massimo Artizzu " 11 | ], 12 | "description": "Building regular expressions with natural language", 13 | "moduleType": [ 14 | "amd", 15 | "globals", 16 | "node" 17 | ], 18 | "keywords": [ 19 | "regex", 20 | "regular-expression", 21 | "regexp", 22 | "composition", 23 | "builder" 24 | ], 25 | "license": "MIT", 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "test", 31 | "tests" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # Windows image file caches 4 | Thumbs.db 5 | ehthumbs.db 6 | 7 | # Folder config file 8 | Desktop.ini 9 | 10 | # Recycle Bin used on file shares 11 | $RECYCLE.BIN/ 12 | 13 | # Windows Installer files 14 | *.cab 15 | *.msi 16 | *.msm 17 | *.msp 18 | 19 | # Windows shortcuts 20 | *.lnk 21 | 22 | # ========================= 23 | # Operating System Files 24 | # ========================= 25 | 26 | # OSX 27 | # ========================= 28 | 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear on external disk 37 | .Spotlight-V100 38 | .Trashes 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .gitattributes 3 | *.min.* 4 | bower.json 5 | 6 | # Windows image file caches 7 | Thumbs.db 8 | ehthumbs.db 9 | 10 | # Folder config file 11 | Desktop.ini 12 | 13 | # Recycle Bin used on file shares 14 | $RECYCLE.BIN/ 15 | 16 | # Windows Installer files 17 | *.cab 18 | *.msi 19 | *.msm 20 | *.msp 21 | 22 | # Windows shortcuts 23 | *.lnk 24 | 25 | # ========================= 26 | # Operating System Files 27 | # ========================= 28 | 29 | # OSX 30 | # ========================= 31 | 32 | .DS_Store 33 | .AppleDouble 34 | .LSOverride 35 | 36 | # Thumbnails 37 | ._* 38 | 39 | # Files that might appear on external disk 40 | .Spotlight-V100 41 | .Trashes 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re-build", 3 | "version": "1.0.0", 4 | "description": "Building regular expressions with natural language", 5 | "main": "re-build.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "node_modules/.bin/mocha" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/MaxArt2501/re-build.git" 15 | }, 16 | "keywords": [ 17 | "regex", 18 | "regular-expression", 19 | "regexp", 20 | "composition", 21 | "builder" 22 | ], 23 | "author": { 24 | "name": "Massimo Artizzu", 25 | "email": "maxart.x@gmail.com" 26 | }, 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/MaxArt2501/re-build/issues" 30 | }, 31 | "homepage": "https://github.com/MaxArt2501/re-build", 32 | "devDependencies": { 33 | "mocha": "^2.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Massimo Artizzu (MaxArt2501) 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 | -------------------------------------------------------------------------------- /re-build.min.js: -------------------------------------------------------------------------------- 1 | !function(n,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():n.RE=t()}(this,function(){"use strict";function n(n){return{not:function(){return f(l(v(c(this),{negate:!0}),this.source),n)}}}function t(n,t){if("number"==typeof t.min||"number"==typeof t.max){var e,r="number"==typeof t.min?t.min:0,i="number"==typeof t.max?t.max:1/0;e=r===i?1===r?"":"{"+r+"}":0===r?1===i?"?":i===1/0?"*":"{,"+i+"}":1===r?i===1/0?"+":"{1,"+i+"}":"{"+r+","+(i===1/0?"":i)+"}",e&&((n.length>2||2===n.length&&"\\"!==n[0])&&u(n)&&(n="(?:"+n+")"),n+=e+(t.lazy?"?":""))}return n}function e(n){var t={};for(var e in n)t[e]={value:n[e],writable:!1,configurable:!1};return t}function r(n,t){return y(n,e(t))}function i(n,t,e){return t.source=e||"",r(n,t)}function o(n){for(var t,e="",r=0;r=55296&&e<=56319){var r=n.charCodeAt(t+1);r>=56320&&r<=57343&&(e=65536+(e-55296<<10)+(r-56320))}return e},O={digit:["\\d","\\D"],alphaNumeric:["\\w","\\W"],whiteSpace:["\\s","\\S"],wordBoundary:["\\b","\\B",b+m],anyChar:[".","",m],tab:["\\t"],vTab:["\\v"],cReturn:["\\r"],newLine:["\\n"],formFeed:["\\f"],null:["\\0"],slash:["\\/"],backslash:["\\\\"],theStart:["^","",b+m],theEnd:["$","",b+m],ascii:[function(){for(var n="",t=0,e=0;t255)throw new RangeError("Invalid character code");n+="\\x"+("0"+r.toString(16)).slice(-2)}return n}],codePoint:[function(){for(var n="",t=this.unicode,e=0,r=0;e65535?2:1,r1114111)throw new RangeError("Invalid code point "+i);i>65535&&!t&&(i-=65536,n+="\\u"+(55296+(i>>10)).toString(16),i=56320+(1023&i)),n+="\\u"+(i>65535?"{"+i.toString(16)+"}":("000"+i.toString(16)).slice(-4))}return n}],control:[function(n){if(!/^[a-zA-Z]$/.test(n))throw new RangeError("Invalid control code");return"\\c"+n.toUpperCase()}],group:[function(){var n=j(arguments);return"(?:"!==n.slice(0,3)&&(n="(?:"+n+")"),n},0,m],capture:[function(){var n=j(arguments);return"(?:"===n.slice(0,3)?n="("+n.slice(3):"("!==n.charAt(0)&&(n="("+n+")"),n},0,m],reference:[function(n){if("number"!=typeof n||n!==n|0||n<0)throw new RangeError("Invalid back reference number");return"\\"+n},0,m]},E={withFlags:function(){return function(n){var t={};return"string"==typeof n?t={global:~n.indexOf("g"),ignoreCase:~n.indexOf("i"),multiline:~n.indexOf("m"),unicode:~n.indexOf("u"),sticky:~n.indexOf("y")}:"object"==typeof n&&n.forEach(function(n){t[n]=this[n]},n),f(r({},t),[C])}}};p.forEach(function(n){E[this[n]]=function(){var t={};return p.forEach(function(e){t[e]=e===n||this[e]},this),f(r({},t),[E,C])}},{global:"globally",ignoreCase:"anyCase",multiline:"fullText",unicode:"withUnicode",sticky:"stickily"});var C={matching:function(){return f(i(function(){return f(l(a(this),j(arguments)),[A])},a(this)),[I,M,n([z,M])])}},R={between:function(){return function(e,r){if(null!=e&&(isNaN(e)||Math.floor(e)!==+e||+e<0)||null!=r&&(isNaN(r)||Math.floor(r)!==+r||+r<0))throw new RangeError("Non-negative integer expected");if(null==e&&null==r)throw new RangeError("Range expected");var o=this,u=this.source,s=v(c(this),{min:e,max:r});return f(i(function(){return f(l(a(o),u+t(j(arguments),s)),[A])},s,u),[P,n([$])])}},exactly:function(){return function(n){return this.between(n,n)}},atLeast:function(){return function(n){return this.between(n,this.max)}},atMost:function(){return function(n){return this.between(this.min,n)}},anyAmountOf:function(){return this.between(0,1/0)},noneOrOne:function(){return this.between(0,1)},oneOrMore:function(){return this.between(1,1/0)}},k={lazily:function(){return f(l(v(c(this),{lazy:!0}),this.source),[R])}},A={then:function(){var t=a(this),e=this.source;return f(i(function(){return f(l(t,e+j(arguments)),[A])},t,e),[I,n([z])])},or:function(){var t=a(this),e=this.source+"|";return f(i(function(){return f(l(t,e+j(arguments)),[A])},t,e),[I,M,n([z,M])])}},I={},z={},S={},N={},P={},$={};d.keys(O).forEach(function(n){var e=O[n];"string"==typeof e[0]?(I[n]=function(){var n=this.source+t(this.negate&&e[1]||e[0],this);return f(l(a(this),n),[A])},e[1]&&(z[n]=I[n])):I[n]=function(){return function(){var n=this.source+t(e[0].apply(this,arguments),this);return f(l(a(this),n),[A])}},e[2]&b||(P[n]=I[n],e[1]&&($[n]=I[n])),e[2]&m||("string"==typeof e[0]?(S[n]=function(){var n=this.source,t=n.lastIndexOf("]");return f(l(a(this),n.slice(0,t)+(this.negate&&e[1]||e[0])+n.slice(t)),[A,F])},e[1]&&(N[n]=S[n])):S[n]=function(){return function(){var n=this.source,t=n.lastIndexOf("]");return f(l(a(this),n.slice(0,t)+e[0].apply(this,arguments)+n.slice(t)),[A,F])}})}),I.oneOf=z.oneOf=P.oneOf=$.oneOf=function(){var n=this,e=this.source;return f(i(function(){return f(l(a(n),e+t((n.negate?"[^":"[")+B(arguments)+"]",n)),[F,A])},c(this),e+t(this.negate?"[^]":"[]",this)),[S])},v(I,R,k),S.backspace=function(){var n=this.source,t=n.lastIndexOf("]");return f(l(a(this),n.slice(0,t)+"\\b"+n.slice(t)),[A,F])},S.range=function(){function n(n){if("string"==typeof n&&1===n.length)return B(n);if(h(n)&&(n=n.source,1===n.length||/^\\(?:[0btnvfr\/\\]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|c[a-zA-Z])$/.test(n)))return n;throw new RangeError("Incorrect character range")}return function(t,e){t=n(t),e=n(e);var r=this.source,i=r.lastIndexOf("]");return f(l(a(this),r.slice(0,i)+t+"-"+e+r.slice(i)),[A,F])}},v(S,n([N]));var F={and:function(){var n=a(this),t=this.source;return f(i(function(){var e=t.lastIndexOf("]");return f(l(n,t.slice(0,e)+B(arguments)+t.slice(e)),[F,A])},n,t),[S])}},M={followedBy:function(){return function(){var n=t(j(arguments),this),e=this.negate?"(?!":"(?=";return n.slice(0,3)!==e&&(n=e+n+")"),f(l(a(this),(this.source||"")+n),[A])}}};v(A,M);var j=o.bind(/[\^\$\/\.\*\+\?\|\(\)\[\]\{\}\\]/g),B=o.bind(/[\^\/\[\]\\-]/g),L={valueOf:function(){return this.regex},toString:function(){return"/"+this.source+"/"+this.flags},test:function(n){return this.regex.test(n)},exec:function(n){return this.regex.exec(n)},replace:function(n,t){return n.replace(this.regex,t)},split:function(n){return n.split(this.regex)},search:function(n){return n.search(this.regex)}};return L.toRegExp=L.valueOf,f(i(g,{global:!1,ignoreCase:!1,multiline:!1,unicode:!1,sticky:!1}),[I,E,C]),g}); 2 | //# sourceMappingURL=re-build.min.map 3 | -------------------------------------------------------------------------------- /doc/reference.md: -------------------------------------------------------------------------------- 1 | RE-Build reference 2 | ================== 3 | 4 | # `RegExp` builders 5 | 6 | The object obtained from building a regular expressions *builders*. Builders are augmented with members and methods to build the regex further, but they're basically immutable objects as every call to extend the builder returns a *new* builder instance. 7 | 8 | ## Properties 9 | 10 | All the following properties are read-only. 11 | 12 | Type | Name | Description 13 | -------:|--------------|------------- 14 | string | `regex` | The regular expression defined by the builder. It's compiled the first time the property is requested, then cached 15 | string | `source` | The source of the underlying regular expression. Used to compile it 16 | string | `flags` | A string comprising the regex' flags. It may include one or more of the letters `"g"`, `"m"`, `"i"`, `"u"` or `"y"` 17 | boolean | `global` | The regex' `global` flag 18 | boolean | `ignoreCase` | The regex' `ignoreCase` flag 19 | boolean | `multiline` | The regex' `multiline` flag 20 | boolean | `unicode` | The regex' `unicode` flag 21 | boolean | `sticky` | The regex' `sticky` flag 22 | 23 | ## Methods 24 | 25 | Returns | Name | Description 26 | --------:|------------------|------------------------- 27 | `RegExp` | `toRegExp()` | Basically, returns the `regex` property 28 | `RegExp` | `valueOf()` | See above 29 | string | `toString()` | Returns a string representation 30 | boolean | `test(string)` | Uses the underlying regex to test a string. Short for `.regex.test(...)` 31 | array | `exec(string)` | Executes the underlying regex on a string. Short for `.regex.exec(...)` 32 | string | `replace(string, string/function)` | Uses the underlying regex to perform a regex-based replacement. Short for `string.replace(regex, ...)` 33 | array | `split(string)` | Uses the underlying regex to perform a regex-based string split. Short for `string.split(regex)` 34 | number | `search(string)` | Uses the underlying regex to perform a string search. Short for `string.search(regex)` 35 | 36 | # Building a regex 37 | 38 | Regex building begins from the he `RE` object returned by the module. You can obtain a *builder* every time you use "words" like `digit`, `then` and such. Some of these words act like functions (like `atLeast` and `codePoint`), some like properties (like `digit` and `theEnd`), some work as both. 39 | 40 | In this last case, if the word is not used as a function, additional words are expected to obtain a builder: 41 | 42 | ```js 43 | var foo = RE.matching.digit.then.alphaNumeric; 44 | ``` 45 | 46 | Many words that can (or must) be used as functions accept a variable number of arguments, that can be either strings, or regular expressions, or builders, which are all appended to the source. Strings are backslash-escaped, while in the other cases the `source` property is then added *unescaped*: 47 | 48 | ```js 49 | var amount = RE.oneOrMore.digit.then(".").then.digit.then.digit, 50 | currency = /[$€£]/; 51 | 52 | var builder = RE.matching.theStart 53 | .then("Total: ", amount, currency) 54 | .then.theEnd; 55 | ``` 56 | 57 | Other words that work as functions only usually accept other types of arguments. 58 | 59 | ## Flags 60 | 61 | The flags of a builder (and its underlying regular expression) can be set using words starting from the `RE` object. After one of these words, another flag word or `matching` must follow, with the exception of `withFlags` that must be followed by `matching` only. 62 | 63 | * **`globally`** 64 | 65 | Set the `global` flag on. 66 | 67 | * **`anyCase`** 68 | 69 | Set the `ignoreCase` flag on. 70 | 71 | * **`fullText`** 72 | 73 | Set the `multiline` flag on. 74 | 75 | * **`stickily`** 76 | 77 | Set the `sticky` flag on. 78 | 79 | * **`withUnicode`** 80 | 81 | Set the `unicode` flag on. 82 | 83 | * **`withFlags(flags)`** 84 | 85 | Set multiple flags. `flags` is expected to be a string containing letters in the set `"g"`, `"m"`, `"i"` and `"y"`. 86 | 87 | ## Conjunctions 88 | 89 | Conjunctions append additional blocks to the current source. They can follow any open or set block. 90 | 91 | * **`then`** 92 | 93 | Appends a block to the current source. 94 | 95 | * **`or`** 96 | 97 | Adds an alternative block (prefixed by the pipe `|` character in regular expressions). 98 | 99 | ## Open and set blocks 100 | 101 | These words can be used in both "open" sequences or inside character sets. They can be used after conjunction words, or a quantifier, or the `matching` word, or the `RE` object itself, or the `and` word joining blocks in character sets. 102 | 103 | * **`digit` / `not.digit`** 104 | 105 | A digit character (`\d`) or its negation (`\D`). 106 | 107 | * **`alphaNumeric` / `not.alphaNumeric`** 108 | 109 | An alphanumeric character plus the undescore (`\w`) or its negation (`\W`). 110 | 111 | * **`whiteSpace` / `not.whisteSpace`** 112 | 113 | A whitespace (`\s`) or its negation (`\W`). 114 | * **`cReturn`** `\r` 115 | * **`newLine`** `\n` 116 | * **`tab`** `\t` 117 | * **`vTab`** `\v` 118 | * **`formFeed`** `\f` 119 | * **`null`** `\0` 120 | * **`slash`** `\/` 121 | * **`backslash`** `\\` 122 | * **`ascii(code)`** 123 | 124 | An ASCII escape sequence (`\xhh`). `code` must be an integer between 0 and 255. It it then converted as two hexadecimal digits in the sequence. 125 | 126 | * **`codePoint(code, ...)`** 127 | 128 | An Unicode escape sequence (`\uhhhh`, or `\u{hhhhh}` with the `unicode` flag set and with a code not from the [Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode))). `code` must be an integer between 0 and 1114111 (`0x10ffff`) or a `RangeError` will be thrown; or it can be a string, whose code points will be converted in the corresponding Unicode escape sequence. Keep in mind that code points from astral planes, when the `unicode` flag is *not* set, are encoded in the corresponding surrogate code point pairs (e.g.: `"🍰"` will become `"\ud83c\udf70"`): *it is your duty* to wrap the pairs in a group if needed or, when it's not possible (for example, in a character range) using an adequate regex structure. 129 | 130 | * **`control(letter)`** 131 | 132 | A control sequence (`\cx`). `letter` must be a string of a single letter. It is then converted to uppercase in the sequence. 133 | 134 | ## Open-only blocks 135 | 136 | These words can be used in open block sequences only (which means, not inside character sets). They can be used after conjunction words, or a quantifier, or the `matching` word, or the `RE` object itself. 137 | 138 | * **`anyChar`** 139 | 140 | The universal character (`.`). 141 | 142 | * **`theStart` / `theEnd`** 143 | 144 | The string-start and string-end boundaries (`^` and `$`, respectively). 145 | 146 | * **`wordBoundary` / `not.wordBoundary`** 147 | 148 | A word boundary (`\b`) or its negation (`\B`). 149 | 150 | * **`oneOf` / `not.oneOf`** 151 | 152 | Appends a character set (`[...]` or `[^...]`, respectively). See the paragraph about [character sets](#character-sets). 153 | 154 | * **`group(...)`** 155 | 156 | Non-capturing group - `(?:...)`. Used as functions only. Arguments can be strings, regexes or builders. 157 | 158 | * **`capture(...)`** 159 | 160 | Capturing group - `(...)`. Used as functions only. Arguments can be strings, regexes or builders. 161 | 162 | * **`reference(number)`** 163 | 164 | Group backreference (`\number`). `number` should be a positive integer. 165 | 166 | ## Character sets 167 | 168 | Character sets are introduced by the `oneOf` word, and may include one or more blocks separated by the `and` word (e.g.: `RE.oneOf.digit.and("abcdef")`). 169 | 170 | These words can be used in character sets only: 171 | 172 | * **`range(start, end)`** 173 | 174 | Adds a character interval into the character set (`[...start-end...]`). `start` and `end` are supposed to be strings of single characters defining the boundaries of the character range; or they can be builders that define one single character, or character class usable in character ranges (which include: `ascii`, `unicode`, `control`, `newLine`, `cReturn`, `tab`, `vTab`, `formFeed`, `null`). 175 | 176 | * **`backspace`** 177 | 178 | The backspace character, `\b` (U+0008). Not to be confused with the word boundary, which can be used as an "open" block only. 179 | 180 | ## Quantifiers 181 | 182 | Quantifiers can follow conjunction words, or the `matching` word, or the `RE` object itself, and can precede any "open" block, with the exception of `wordBoundary`, `not.wordBoundary`, `theStart` and `theEnd`. 183 | 184 | They can be prefixed by `lazily` to define a lazy quantifier, instead of a greedy one. 185 | 186 | Quantifiers can be used as functions, and accept strings, regexes or builders as arguments. A convenient group wrap will be used if necessary: 187 | 188 | ```js 189 | var foo = RE.oneOrMore("a"); // /a+/ 190 | var bar = RE.oneOrMore("abc"); // /(?:abc)+/ 191 | ``` 192 | 193 | * **`anyAmountOf`** `*` 194 | * **`oneOrMore`** `+` 195 | * **`noneOrOne`** `?` 196 | * **`atLeast(n)`** 197 | 198 | `n` must be a non-negative integer. If `n` is 0, a `*` is produced; if `n` is 1, then `+` is produced; else, the quantifier is `{n,}`. 199 | 200 | * **`atMost(n)`** 201 | 202 | `n` must be a non-negative integer. If `n` is 1, then `?` is produced; else, the quantifier is `{,n}`. 203 | 204 | * **`exactly(n)`** 205 | 206 | `n` must be a non-negative integer. If `n` is 1, then no quantifier is defined; else, the quantifier is `{n}`. 207 | 208 | * **`between(n, m)`** 209 | 210 | `n` and `m` must be non-negative integers. If the the values are adequate, the produced quantifier can be one of the above; otherwise, the quantifier is `{n,m}`. 211 | 212 | 213 | ## Look-aheads 214 | 215 | * **`followedBy(...)` / `not.followedBy(...)`** 216 | 217 | Appends a look-ahead (`(?=...)` or `(?!...)`, respectively). Used as functions only. Arguments can be strings, regexes or builders. 218 | 219 | Can follow any open block, or the `matching` word, or the `RE` object itself, or the `or` conjunction. 220 | -------------------------------------------------------------------------------- /re-build.min.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["re-build.js"],"names":["root","factory","define","amd","exports","module","RE","this","negator","bundles","not","buildBuilder","createBuilder","extend","getSettings","negate","source","wrapSource","settings","min","max","quantifier","Infinity","length","hasManyBlocks","lazy","getConstMap","consts","map","name","value","writable","configurable","setConsts","dest","defineProps","initFunc","fnc","reparser","blocks","block","i","replace","RegExp","isBuilder","len","search","match","re","count","lastIndex","exec","index","object","props","settingList","sets","getFlags","flags","bundle","prop","defs","enumerable","get","getPropDefs","regex","global","ignoreCase","multiline","unicode","sticky","O","create","proto","isPrototypeOf","parseArgs","arguments","thenable","Object","assign","defineProperties","concat","NOQUANTIFY","NOSETS","getCodePointAt","codePointAt","string","code","charCodeAt","surr","names","digit","alphaNumeric","whiteSpace","wordBoundary","anyChar","tab","vTab","cReturn","newLine","formFeed","null","slash","backslash","theStart","theEnd","ascii","j","arg","RangeError","toString","slice","codePoint","control","letter","test","toUpperCase","group","capture","charAt","reference","number","flagger","withFlags","indexOf","forEach","f","matcher","flag","matching","openable","lookAheads","negable","quantifiers","between","isNaN","Math","floor","that","quantifiable","qntnegable","exactly","quantity","atLeast","atMost","anyAmountOf","noneOrOne","oneOrMore","lazinator","lazily","then","or","settable","setnegable","keys","def","apply","lastBracket","lastIndexOf","andCharSet","oneOf","parseSets","backspace","range","checkBoundary","bnd","start","end","and","followedBy","seq","bind","valueOf","subs","split","toRegExp"],"mappings":"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"} -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | RE-Build 2 | ======== 3 | 4 | Build regular expressions with natural language. 5 | 6 | ## Introduction 7 | 8 | Have you ever dealt with complex regular expressions like the following one? 9 | 10 | ```js 11 | var ipMatch = /(?:(?:1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.){3}(?:1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)\b/; 12 | ``` 13 | 14 | Using a meaningful variable name can help, writing comments helps even more, but what's always hard to understand is what the regular expression actually *does*: They're left as some sort of magic trick that it's never updated because their syntax is so obscure that even the authors themselves hardly fell like facing them again. Debugging a regular expression often means rewriting it from scratch. 15 | 16 | RE-Build's aim is to change that, converting the process of creating a regular expression to combining nice natural language expressions. The above regex would be composed as 17 | 18 | ```js 19 | var ipNumber = RE.group( 20 | RE ("1").then.digit.then.digit 21 | .or ("2").then.oneOf.range("0", "4").then.digit 22 | .or ("25").then.oneOf.range("0", "5") 23 | .or .oneOf.range("1", "9").then.digit 24 | .or .digit 25 | ), 26 | 27 | ipMatch = RE.matching.exactly(3).group( ipNumber.then(".") ) 28 | .then(ipNumber).then.wordBoundary.regex; 29 | ``` 30 | 31 | This approach is definitely more verbose, but also much clearer and less error prone. 32 | 33 | Another module for the same purpose is [VerbalExpressions](https://github.com/VerbalExpressions/JSVerbalExpressions), but it doesn't allow to build just *any* regular expression. RE-Build aims to fill that gap too. 34 | 35 | Remember, as a general rule, that RE-Build does *not* care if your environment doesn't support certain `RegExp` features (for example, the `sticky` flag or extended Unicode escaping sequences), as the corresponding source code will be generated anyway. Of course, you'll get an error trying to get a `RegExp` object out of it. 36 | 37 | ## Installation 38 | 39 | Via `npm`: 40 | 41 | ```bash 42 | npm install re-build 43 | ``` 44 | 45 | Via `bower`: 46 | 47 | ```bash 48 | bower install re-build 49 | ``` 50 | 51 | The package can be loaded as a CommonJS module (node.js, io.js), as an AMD module (RequireJS, ...) or as a standalone script: 52 | 53 | ```html 54 | 55 | ``` 56 | 57 | ## Usage 58 | 59 | For a detailed documentation, check the [reference sheet](doc/reference.md). Keep in mind that RE-Build is a tool to help building, understanding and debugging regular expressions, and does *not* prevent one to create incorrect results. 60 | 61 | ### Basics 62 | 63 | The *core* point is the `RE` object (or whatever variable name you assigned to it), together with the `matching` method: 64 | 65 | ```js 66 | var RE = require("re-build"); 67 | var builder = RE.matching("xyz"); 68 | ``` 69 | 70 | The output is *not*, however, a regular expression, but a regular expression *builder* that can be extended, or used as an extension for other builders. To get the corresponding regular expression, use the `regex` property or the `toRegExp()/valueOf()` methods. 71 | 72 | ```js 73 | var start = RE.matching.theStart.then(builder).toRegExp(); // /^xyz/ 74 | 75 | var foo = RE.matching(builder).then.oneOrMore.digit.regex; // /xyz\d+/ 76 | ``` 77 | 78 | As you can see, you can put additional matching blocks using the `then` word, which is also a function that can take arguments as blocks to add too. The arguments can be strings (which are backslash-escaped), regular expressions or RE-Build'ers, whose `source` property is added to the builder *unescaped*. 79 | 80 | The `or` word has a similar meaning, but adds an alternative block to the source: 81 | 82 | ```js 83 | var hex = RE.matching.digit 84 | .or.oneOf.range("A", "F") 85 | .regex; // /\d|[A-F]/ 86 | ``` 87 | 88 | ### Regex builders are immutable 89 | 90 | Regular expression builders are immutable objects, meaning that when extending a builder we get a new builder instance: 91 | 92 | ```js 93 | var bld1 = RE.matching.digit; 94 | var bld2 = bld1.or.oneOf.range("A", "F"); 95 | bld1 === bld2; // => false 96 | ``` 97 | 98 | ### Special classes, aliases and escaping 99 | 100 | RE-Build uses specific names to address common regex character classes: 101 | 102 | Name | Result | Notes 103 | ---------------|--------------|-------------- 104 | `digit` | `\d` | from `0` to `9` 105 | `alphaNumeric` | `\w` | digits, uppercase and lowercase letters and the underscore 106 | `whiteSpace` | `\s` | white space characters 107 | `wordBoundary` | `\b` | 108 | `anyChar` | `.` | universal matcher 109 | `theStart` | `^` | 110 | `theEnd` | `$` | 111 | `cReturn` | `\r` | carriage return 112 | `newLine` | `\n` | 113 | `tab` | `\t` | 114 | `vTab` | `\v` | vertical tab 115 | `formFeed` | `\f` | 116 | `null` | `\0` | 117 | `slash` | `\/` | 118 | `backslash` | `\\` | 119 | `backspace` | `\b` | can be used in character sets `[...]` *only* 120 | 121 | The first four names can be negated prefixing them with `not` to get the complementary meaning: 122 | 123 | * `not.digit` for `\D`; 124 | * `not.alphaNumeric` for `\W`; 125 | * `not.whiteSpace` for `\S`; 126 | * `not.wordBoundary` for `\B`. 127 | 128 | Single characters can be defined by escape sequences: 129 | 130 | Function | Result | Meaning 131 | ---------------|----------|----------- 132 | `ascii(n)` | `\xhh` | ASCII character corrisponding to `n` 133 | `codePoint(n)` | `\uhhhh` / `\u{hhhhhh}` | Unicode character corrisponding to `n` 134 | `control(a)` | `\ca` | Control sequence corrisponding to the letter `a` 135 | 136 | With the exception of `wordBoundary`, `theStart` and `theEnd`, all of the previous words can be used inside character sets (see after). 137 | 138 | ### Flags 139 | 140 | You can set the flags of the regex prefixing `matching` with one or more of the flagging options: 141 | 142 | * `globally` for a global regex; 143 | * `anyCase` for a case-insensitive regex; 144 | * `fullText` for a "multiline" regex (i.e., the dot '`.`' matches new line characters too); 145 | * `withUnicode` for a regex with extended Unicode support; 146 | * `stickily` for a "sticky" regex. 147 | 148 | Alternatively, you can set the flags with the `withFlags` method of the `RE` object. 149 | 150 | ```js 151 | // The following regexes are equivalent: /[a-f]/gi 152 | var foo = RE.globally.anyCase.matching.oneOf.range("a", "f").regex; 153 | var bar = RE.withFlags("gi").matching.oneOf.range("a", "f").regex; 154 | ``` 155 | 156 | You can't change a regex builder's flags, as builders are immutable, but you can create a copy of a builder with different flags: 157 | 158 | ```js 159 | var foo = RE.matching.oneOrMore.alphaNumeric; // /\w+/ 160 | var bar = RE.globally.matching(foo); // /\w+/g 161 | ``` 162 | 163 | If you don't need flags set, as a shortened version you can remove the `matching` word: 164 | 165 | ```js 166 | // These are equivalent: 167 | RE.matching("abc").then.digit; 168 | RE("abc").then.digit; 169 | ``` 170 | 171 | This becomes useful when defining the content of groups, character sets or look-aheads. 172 | 173 | ### Grouping 174 | 175 | Use the `group` word to define a non-capturing group, and `capture` for a capturing group: 176 | 177 | ```js 178 | var amount = RE.matching("$").then.capture( 179 | RE.oneOrMore.digit 180 | .then.noneOrOne.group(".", RE.oneOrMore.digit) 181 | ).regex; 182 | // /\$(\d+(?:\.\d+)?)/ 183 | ``` 184 | 185 | The `group` and `capture` words are function, and the resulting groups will embrace everything passed as arguments. Just like for `then` and `or`, arguments can be strings, regular expression or other RE-Build'ers. 186 | 187 | Backrefences for capturing groups are obtained using the `reference` function, passing the reference number: 188 | 189 | ```js 190 | var quote = RE.matching.capture( RE.oneOf("'\"") ) 191 | .then.anyAmountOf.alphaNumeric 192 | .then.reference(1); 193 | // /(['"])\w*\1/ 194 | ``` 195 | 196 | ### Character sets 197 | 198 | Character sets (`[...]`) are introduced by the word `oneOf`. Several characters can be included separated by the word `and`. Additionally, one can include a character interval, using the function `range` and giving the initial and final character of the interval. 199 | 200 | Exclusive character sets can be obtained prefixing `oneOf` with the word `not`. 201 | 202 | ```js 203 | var hexColor = RE.matching("#").then.exactly(6) 204 | .oneOf.digit.and.range("a", "f").and.range("A", "F"); 205 | // /#[\da-fA-F]{6}/ 206 | 207 | var hours = RE.oneOf("01").then.digit.or("2").then.oneOf.range("0", "3"); 208 | // /[01]\d|2[0-3]/ 209 | 210 | var quote = RE.matching('"').then.oneOrMore.not.oneOf('"').then('"'); 211 | // /"[^"]+"/ 212 | ``` 213 | 214 | ### Quantifiers 215 | 216 | Quantifiers can be defined prefixing the quantified block by one of these constructs: 217 | 218 | Construct | Result 219 | ----------------|--------- 220 | `anyAmountOf` | `*` 221 | `oneOrMore` | `+` 222 | `noneOrOne` | `?` 223 | `atLeast(n)` | `{n,}` 224 | `atMost(n)` | `{,n}` 225 | `exactly(n)` | `{n}` 226 | `between(n, m)` | `{n,m}` 227 | 228 | Quantification is smart enough to translate constructs in their most compact form (e.g., `.atLeast(1)` becomes `+`, `.between(0, 1)` becomes `?` and so on). 229 | 230 | Lazy quantifiers can be obtained prefixing the word `lazily` prior to the quantifier. 231 | 232 | ```js 233 | var number = RE.oneOrMore.digit; // /\d+/ 234 | 235 | var hexnumber = RE.exactly(2).oneOf.digit.and.range("a", "f"); 236 | // /[\da-f]{2}/ 237 | 238 | var macAddress = RE.anyCase.matching(hexnumber).then.exactly(5).group( 239 | RE("-").then(hexnumber) 240 | ); 241 | // /[\da-f]{2}(?:-[\da-f]{2}){5}/i 242 | 243 | var quoteAlt = RE.matching.capture(RE.oneOf("'\"")) 244 | .then.lazily.anyAmountOf.anyChar 245 | .then.reference(1); 246 | // /(['"]).*?\1/ 247 | ``` 248 | 249 | ### Look-aheads 250 | 251 | Look-aheads are introduced by the function `followedBy` (eventually prefixed by `not` for negative look-aheads). 252 | 253 | ```js 254 | var euro = RE.matching.oneOrMore.digit.followedBy("€"); 255 | // /\d+(?=€)/ 256 | 257 | var foo = RE("a").or.not.followedBy("b").then("c"); 258 | // /a|(?!b)c/ 259 | ``` 260 | 261 | ## Compatibilty 262 | 263 | * Internet Explorer 9+ 264 | * Firefox 4+ 265 | * Safari 5+ 266 | * Chrome 267 | * Opera 11.60+ 268 | * node.js 269 | 270 | Basically, every Javascript environment that supports [`Object.defineProperties`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) should be fine. 271 | 272 | ## Tests 273 | 274 | The unit tests are built on top of [mocha](http://mochajs.org/). Once the package is installed, run `npm install` from the package's root directory in order to locally install mocha, then `npm run test` to execute the tests. Open [index.html](test/index.html) with a browser to perform the tests on the client side. 275 | 276 | If mocha is installed globally, served side tests can be run with just the command `mocha` from the package's root directory. 277 | 278 | ## To do 279 | 280 | * More natural language alternatives 281 | * Plurals, articles 282 | * CLI tool to translate regexes to and from RE-Build's syntax 283 | * More examples 284 | * Consider IE8 support 285 | 286 | ## License 287 | 288 | MIT @ Massimo Artizzu 2015-2016. See [LICENSE](LICENSE). 289 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | (function(root, tests) { 2 | if (typeof define === "function" && define.amd) 3 | define(["re-build"], tests); 4 | else if (typeof exports === "object") 5 | tests(require("../re-build.js")); 6 | else tests(root.RE); 7 | })(this, function(RE) { 8 | "use strict"; 9 | 10 | function assert(value, expected) { 11 | if (value !== expected) 12 | throw new Error("Expected " + expected + ", computed " + value); 13 | } 14 | 15 | function assertBuilder(builder, expsource, expflags) { 16 | assertSource(builder, expsource); 17 | assertFlags(builder, expflags); 18 | } 19 | 20 | function assertSource(builder, expected) { 21 | var source = builder.regex.source; 22 | if (source !== expected) 23 | throw new Error("Expected source /" + expected + "/, computed /" + source + "/"); 24 | } 25 | 26 | function assertFlags(builder, expected) { 27 | var restring = builder.regex.toString(), 28 | flags = restring.substring(restring.lastIndexOf("/") + 1); 29 | if (flags !== expected) 30 | throw new Error("Expected flags \"" + expected + "\", computed \"" + flags + "\""); 31 | } 32 | 33 | describe("RE-Build'ers", function() { 34 | it("Character sequences", function() { 35 | assertSource(RE.matching("abc"), "abc"); 36 | assertSource(RE.matching("a", /b/, RE("c")), "abc"); 37 | assertSource(RE.matching("a[b]"), "a\\[b\\]"); 38 | assertSource(RE.matching("f(x) = {4.5}^\\3"), "f\\(x\\) = \\{4\\.5\\}\\^\\\\3"); 39 | }); 40 | 41 | it("Flags", function() { 42 | assertFlags(RE.matching("abc"), ""); 43 | assertFlags(RE.globally.matching("abc"), "g"); 44 | assertFlags(RE.anyCase.matching("abc"), "i"); 45 | assertFlags(RE.fullText.matching("abc"), "m"); 46 | if ("unicode" in /a/) 47 | assertFlags(RE.withUnicode.matching("abc"), "u"); 48 | if ("sticky" in /a/) 49 | assertFlags(RE.stickily.matching("abc"), "y"); 50 | assertFlags(RE.globally.anyCase.fullText.matching("abc"), "gim"); 51 | assertFlags(RE.withFlags("img").matching("abc"), "gim"); 52 | 53 | // Cloning a builder with different flags 54 | var builder = RE.matching.oneOrMore.digit, 55 | other = RE.globally.matching(builder); 56 | assertBuilder(other, builder.source, "g"); 57 | }); 58 | 59 | it("Character classes and aliases", function() { 60 | assertSource(RE.matching.digit, "\\d"); 61 | assertSource(RE.matching.alphaNumeric, "\\w"); 62 | assertSource(RE.matching.whiteSpace, "\\s"); 63 | assertSource(RE.matching.wordBoundary, "\\b"); 64 | assertSource(RE.matching.cReturn, "\\r"); 65 | assertSource(RE.matching.newLine, "\\n"); 66 | assertSource(RE.matching.tab, "\\t"); 67 | assertSource(RE.matching.vTab, "\\v"); 68 | assertSource(RE.matching.formFeed, "\\f"); 69 | assertSource(RE.matching.slash, "\\/"); 70 | assertSource(RE.matching.backslash, "\\\\"); 71 | assertSource(RE.matching.anyChar, "."); 72 | }); 73 | 74 | it("Negated character classes", function() { 75 | assertSource(RE.matching.not.digit, "\\D"); 76 | assertSource(RE.matching.not.alphaNumeric, "\\W"); 77 | assertSource(RE.matching.not.whiteSpace, "\\S"); 78 | assertSource(RE.matching.not.wordBoundary, "\\B"); 79 | }); 80 | 81 | it("Character escaping", function() { 82 | assertSource(RE.matching.control("M"), "\\cM"); 83 | assertSource(RE.matching.ascii(160), "\\xa0"); 84 | assertSource(RE.matching.ascii("ABC"), "\\x41\\x42\\x43"); 85 | assertSource(RE.matching.codePoint(0x2661), "\\u2661"); 86 | assertSource(RE.matching.codePoint("♡"), "\\u2661"); 87 | assertSource(RE.matching.codePoint(0x1f370), "\\ud83c\\udf70"); 88 | assertSource(RE.matching.codePoint("I♡🍰"), "\\u0049\\u2661\\ud83c\\udf70"); 89 | if ("unicode" in /a/) { 90 | assertSource(RE.withUnicode.matching.codePoint(0x1f370), "\\u{1f370}"); 91 | assertSource(RE.withUnicode.matching.codePoint("I♡🍰"), "\\u0049\\u2661\\u{1f370}"); 92 | } 93 | 94 | try { 95 | RE.matching.control("1"); 96 | throw new Error("Expected RangeError"); 97 | } catch (e) { 98 | assert(e.name, "RangeError"); 99 | } 100 | try { 101 | RE.matching.ascii("♡"); 102 | throw new Error("Expected RangeError"); 103 | } catch (e) { 104 | assert(e.name, "RangeError"); 105 | } 106 | try { 107 | RE.matching.codePoint(0x200000); 108 | throw new Error("Expected RangeError"); 109 | } catch (e) { 110 | assert(e.name, "RangeError"); 111 | } 112 | }); 113 | 114 | it("Concatenation of sequences and blocks", function() { 115 | assertSource(RE.matching("abc").then("de"), "abcde"); 116 | assertSource(RE.matching("abc").then.digit, "abc\\d"); 117 | assertSource(RE.matching("abc").then.not.digit, "abc\\D"); 118 | assertSource(RE.matching.digit.then.digit, "\\d\\d"); 119 | assertSource(RE.matching("ab").then("cd").then("ef"), "abcdef"); 120 | assert("backspace" in RE.matching, false); 121 | }); 122 | 123 | it("Character sets", function() { 124 | assertSource(RE.matching.oneOf("abc"), "[abc]"); 125 | assertSource(RE.matching.oneOf("a-z"), "[a\\-z]"); 126 | assertSource(RE.matching.oneOf("^[]"), "[\\^\\[\\]]"); 127 | assertSource(RE.matching.oneOf("abc").and("de"), "[abcde]"); 128 | assertSource(RE.matching.oneOf.digit.and.whiteSpace, "[\\d\\s]"); 129 | assertSource(RE.matching.oneOf.digit, "[\\d]"); 130 | assertSource(RE.matching.oneOf.ascii(240).and.codePoint(0xca0), "[\\xf0\\u0ca0]"); 131 | assertSource(RE.matching.oneOf.backspace.and.newLine.and("abc"), "[\\b\\nabc]"); 132 | assertSource(RE.matching.not.oneOf("abc"), "[^abc]"); 133 | assertSource(RE.matching.not.oneOf.not.digit, "[^\\D]"); 134 | assertSource(RE.matching.oneOf("abc").then.digit, "[abc]\\d"); 135 | assertSource(RE.matching.oneOf.not.digit.then.digit, "[\\D]\\d"); 136 | assert("anyChar" in RE.matching.oneOf, false); 137 | assert("wordBoundary" in RE.matching.oneOf, false); 138 | assert("theStart" in RE.matching.oneOf, false); 139 | }); 140 | 141 | it("Character set ranges", function() { 142 | assertSource(RE.matching.oneOf.range("a", "z"), "[a-z]"); 143 | assertSource(RE.matching.oneOf.range("a", "z").and.range("0", "9"), "[a-z0-9]"); 144 | assertSource(RE.matching.oneOf.range(RE.ascii(128), RE.ascii(255)), "[\\x80-\\xff]"); 145 | assertSource(RE.matching.oneOf.range("z", RE.codePoint(0x2001)), "[z-\\u2001]"); 146 | assertSource(RE.matching.oneOf.range(RE.null, RE.control("M")), "[\\0-\\cM]"); 147 | assertSource(RE.matching.oneOf.range(RE.tab, RE.cReturn), "[\\t-\\r]"); 148 | assertSource(RE.matching.oneOf.range(RE.newLine, RE.vTab), "[\\n-\\v]"); 149 | assertSource(RE.matching.oneOf.range(RE.slash, RE.backslash), "[\\/-\\\\]"); 150 | }); 151 | 152 | it("String boundaries", function() { 153 | assertSource(RE.matching.theStart.then.digit, "^\\d"); 154 | assertSource(RE.matching("abc").then.theEnd, "abc$"); 155 | }); 156 | 157 | it("Capturing and non-capturing groups", function() { 158 | assertSource(RE.matching.group("abc"), "(?:abc)"); 159 | assertSource(RE.matching.group(RE.digit), "(?:\\d)"); 160 | assertSource(RE.matching.group("a", /b/, RE("c")), "(?:abc)"); 161 | assertSource(RE.matching.capture("abc"), "(abc)"); 162 | assertSource(RE.matching.capture(RE.digit), "(\\d)"); 163 | assertSource(RE.matching.capture("a", /b/, RE("c")), "(abc)"); 164 | }); 165 | 166 | it("Backreferences", function() { 167 | assertSource(RE.matching.capture(RE.oneOrMore.digit).then(" - ").then.reference(1).then(" = 0"), "(\\d+) - \\1 = 0"); 168 | assertSource(RE.matching.capture(RE.oneOf("'\"")).then.oneOrMore.alphaNumeric.then.reference(1), "(['\"])\\w+\\1"); 169 | try { 170 | RE.matching.reference(-1); 171 | throw new Error("Expected RangeError"); 172 | } catch (e) { 173 | assert(e.name, "RangeError"); 174 | } 175 | }); 176 | 177 | it("Greedy quantifiers", function() { 178 | assertSource(RE.matching.noneOrOne("a"), "a?"); 179 | assertSource(RE.matching.anyAmountOf("a"), "a*"); 180 | assertSource(RE.matching.oneOrMore("a"), "a+"); 181 | assertSource(RE.matching.atLeast(0)("a"), "a*"); 182 | assertSource(RE.matching.atLeast(1)("a"), "a+"); 183 | assertSource(RE.matching.atLeast(2)("a"), "a{2,}"); 184 | assertSource(RE.matching.atMost(0)("a"), "a{0}"); 185 | assertSource(RE.matching.atMost(1)("a"), "a?"); 186 | assertSource(RE.matching.atMost(2)("a"), "a{,2}"); 187 | assertSource(RE.matching.exactly(1)("a"), "a"); 188 | assertSource(RE.matching.exactly(4)("a"), "a{4}"); 189 | assertSource(RE.matching.between(0)("a"), "a*"); 190 | assertSource(RE.matching.between(1)("a"), "a+"); 191 | assertSource(RE.matching.between(0, 1)("a"), "a?"); 192 | assertSource(RE.matching.between(null, 1)("a"), "a?"); 193 | assertSource(RE.matching.between(2, 4)("a"), "a{2,4}"); 194 | assertSource(RE.matching.between(1, 1)("a"), "a"); 195 | assertSource(RE.matching.between(3, 3)("a"), "a{3}"); 196 | assertSource(RE.matching.between(3)("a"), "a{3,}"); 197 | assertSource(RE.matching.between(null, 3)("a"), "a{,3}"); 198 | 199 | assertSource(RE.matching.oneOrMore("abc"), "(?:abc)+"); 200 | assertSource(RE.matching.oneOrMore.digit, "\\d+"); 201 | assertSource(RE.matching.oneOrMore.oneOf("abc"), "[abc]+"); 202 | assertSource(RE.matching.oneOrMore.oneOf.range("a", "z"), "[a-z]+"); 203 | assertSource(RE.matching.oneOrMore.oneOf.range("a", "z").and.digit, "[a-z\\d]+"); 204 | assertSource(RE.matching.oneOrMore.group("abc"), "(?:abc)+"); 205 | assertSource(RE.matching.oneOrMore.capture("abc"), "(abc)+"); 206 | assertSource(RE.matching.oneOrMore.capture("a)(b"), "(a\\)\\(b)+"); 207 | assertSource(RE.matching.oneOrMore(/(ab)(cd)/), "(?:(ab)(cd))+"); 208 | assertSource(RE.matching.oneOrMore(/(ab(cd))/), "(ab(cd))+"); 209 | 210 | try { 211 | RE.matching.exactly(1.5)("a"); 212 | throw new Error("Expected RangeError"); 213 | } catch (e) { 214 | assert(e.name, "RangeError"); 215 | } 216 | try { 217 | RE.matching.exactly(-1)("a"); 218 | throw new Error("Expected RangeError"); 219 | } catch (e) { 220 | assert(e.name, "RangeError"); 221 | } 222 | try { 223 | RE.matching.between()("a"); 224 | throw new Error("Expected RangeError"); 225 | } catch (e) { 226 | assert(e.name, "RangeError"); 227 | } 228 | 229 | assert("digit" in RE.matching.exactly(1), true); 230 | assert("slash" in RE.matching.exactly(1), true); 231 | assert("oneOf" in RE.matching.exactly(1), true); 232 | assert("ascii" in RE.matching.exactly(1), true); 233 | assert("group" in RE.matching.exactly(1), true); 234 | assert("wordBoundary" in RE.matching.exactly(1), false); 235 | assert("theStart" in RE.matching.exactly(1), false); 236 | }); 237 | 238 | it("Lazy quantifiers", function() { 239 | assertSource(RE.matching.lazily.noneOrOne("a"), "a??"); 240 | assertSource(RE.matching.lazily.anyAmountOf("a"), "a*?"); 241 | assertSource(RE.matching.lazily.oneOrMore("a"), "a+?"); 242 | assertSource(RE.matching.lazily.atLeast(0)("a"), "a*?"); 243 | assertSource(RE.matching.lazily.atLeast(1)("a"), "a+?"); 244 | assertSource(RE.matching.lazily.atLeast(2)("a"), "a{2,}?"); 245 | assertSource(RE.matching.lazily.atMost(1)("a"), "a??"); 246 | assertSource(RE.matching.lazily.atMost(2)("a"), "a{,2}?"); 247 | assertSource(RE.matching.lazily.exactly(4)("a"), "a{4}?"); 248 | assertSource(RE.matching.lazily.between(2, 4)("a"), "a{2,4}?"); 249 | 250 | assertSource(RE.matching.lazily.oneOrMore("abc"), "(?:abc)+?"); 251 | assertSource(RE.matching.lazily.oneOrMore.digit, "\\d+?"); 252 | assertSource(RE.matching.lazily.oneOrMore.oneOf("abc"), "[abc]+?"); 253 | assertSource(RE.matching.lazily.oneOrMore.oneOf.range("a", "z"), "[a-z]+?"); 254 | assertSource(RE.matching.lazily.oneOrMore.oneOf.range("a", "z").and.digit, "[a-z\\d]+?"); 255 | assertSource(RE.matching.lazily.oneOrMore.group("abc"), "(?:abc)+?"); 256 | assertSource(RE.matching.lazily.oneOrMore.capture("abc"), "(abc)+?"); 257 | assertSource(RE.matching.lazily.oneOrMore.capture("a)(b"), "(a\\)\\(b)+?"); 258 | assertSource(RE.matching.lazily.oneOrMore(/(ab)(cd)/), "(?:(ab)(cd))+?"); 259 | assertSource(RE.matching.lazily.oneOrMore(/(ab(cd))/), "(ab(cd))+?"); 260 | }); 261 | 262 | it("Look-aheads", function() { 263 | assertSource(RE.matching.followedBy("abc"), "(?=abc)"); 264 | assertSource(RE.matching.not.followedBy("abc"), "(?!abc)"); 265 | assertSource(RE.matching("0").or.not.followedBy("b").then.alphaNumeric, "0|(?!b)\\w"); 266 | assertSource(RE.matching.oneOrMore.alphaNumeric.followedBy(","), "\\w+(?=,)"); 267 | }); 268 | 269 | it("Complex examples", function() { 270 | // Matching time, format hh:mm:ss 271 | assertSource( 272 | RE.matching.theStart.then.group( 273 | RE.oneOf("01").then.digit 274 | .or("2").then.oneOf.range("0", "3") 275 | ).then.exactly(2).group( 276 | RE(":").then.oneOf.range("0", "5").then.digit 277 | ).then.theEnd, 278 | "^(?:[01]\\d|2[0-3])(?::[0-5]\\d){2}$" 279 | ); 280 | 281 | // Matching HTML/XML attributes (sort of) 282 | assertBuilder( 283 | RE.globally.anyCase.matching.whiteSpace 284 | .then.oneOf.range("a", "z").then.anyAmountOf.alphaNumeric 285 | .then.anyAmountOf.whiteSpace.then("=").then.anyAmountOf.whiteSpace 286 | .then.capture(RE.oneOf("'\"")) 287 | .then.lazily.anyAmountOf.anyChar.then.reference(1), 288 | "\\s[a-z]\\w*\\s*=\\s*(['\"]).*?\\1", "gi" 289 | ); 290 | 291 | // Matching CSS colors 292 | var spaces = RE.anyAmountOf(" "), 293 | comma = RE(spaces).then(",").then(spaces), 294 | upTo255 = RE.group(RE("25").then.oneOf.range("0", "5") 295 | .or("2").then.oneOf.range("0", "4").then.digit 296 | .or("1").then.digit.then.digit 297 | .or.oneOf.range("1", "9").then.noneOrOne.digit 298 | .or("0")), 299 | upTo360 = RE.group(RE("360") 300 | .or("3").then.oneOf.range("0", "5").then.digit 301 | .or.oneOf("12").then.digit.then.digit 302 | .or.oneOf.range("1", "9").then.noneOrOne.digit 303 | .or("0")), 304 | percentage = RE.group(RE("100") 305 | .or.oneOf.range("1", "9").then.noneOrOne.digit 306 | .or("0")).then("%"), 307 | opacity = RE.group(RE.noneOrOne("0").then(".").then.oneOrMore.digit 308 | .or.oneOf("01")); 309 | assertBuilder( 310 | RE.anyCase.matching // #xxxxxx or #xxx 311 | .not.wordBoundary.then("#") 312 | .then.between(1, 2).group( 313 | RE.exactly(3)(RE.oneOf.digit.and.range("a", "f")) 314 | ).then.wordBoundary 315 | .or // rgb(x, y, z) 316 | .wordBoundary.then("rgb(").then(spaces).then.exactly(2).group( 317 | RE(upTo255).then(comma) 318 | ).then(upTo255).then(spaces).then(")") 319 | .or // rgba(x, y, z, k) 320 | .wordBoundary.then("rgba(").then(spaces).then.exactly(3).group( 321 | RE(upTo255).then(comma) 322 | ).then(opacity).then(spaces).then(")") 323 | .or // hsl(x, y, z) 324 | .wordBoundary.then("hsl(").then(spaces).then(upTo360).then(comma) 325 | .then(percentage).then(comma).then(percentage) 326 | .then(spaces).then(")") 327 | .or // hsla(x, y, z, k) 328 | .wordBoundary.then("hsla(").then(spaces).then(upTo360).then(comma) 329 | .then(percentage).then(comma).then(percentage).then(comma) 330 | .then(opacity).then(spaces).then(")"), 331 | "\\B#(?:[\\da-f]{3}){1,2}\\b|\\brgb\\( *(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d?|0) *, *){2}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d?|0) *\\)|\\brgba\\( *(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d?|0) *, *){3}(?:0?\\.\\d+|[01]) *\\)|\\bhsl\\( *(?:360|3[0-5]\\d|[12]\\d\\d|[1-9]\\d?|0) *, *(?:100|[1-9]\\d?|0)% *, *(?:100|[1-9]\\d?|0)% *\\)|\\bhsla\\( *(?:360|3[0-5]\\d|[12]\\d\\d|[1-9]\\d?|0) *, *(?:100|[1-9]\\d?|0)% *, *(?:100|[1-9]\\d?|0)% *, *(?:0?\\.\\d+|[01]) *\\)", "i" 332 | ); 333 | }); 334 | }); 335 | 336 | describe("Builder prototype", function() { 337 | it("test", function() { 338 | var builder = RE.matching.oneOrMore.digit; 339 | assert(builder.test("We're living in " + new Date().getFullYear()), true); 340 | assert(builder.test("Hello, world!"), false); 341 | }); 342 | 343 | it("exec", function() { 344 | var builder = RE.matching.oneOrMore.digit, 345 | result = builder.exec("The answer is 42."); 346 | assert(result instanceof Array, true); 347 | assert(result[0].length, 2); 348 | assert(result.index, 14); 349 | }); 350 | 351 | it("replace", function() { 352 | var reverseDate = RE.matching.capture(RE.exactly(4).digit).then("-") 353 | .then.capture(RE.exactly(2).digit).then("-") 354 | .then.capture(RE.exactly(2).digit); 355 | assert(reverseDate.replace("2015-04-20", "$3/$2/$1"), "20/04/2015"); 356 | 357 | var capital = RE.globally.matching.wordBoundary.then.oneOrMore.alphaNumeric; 358 | assert(capital.replace("hello, world!", function(m) { 359 | return m.charAt(0).toUpperCase() + m.substring(1); 360 | }), "Hello, World!"); 361 | }); 362 | 363 | it("split", function() { 364 | var space = RE.oneOrMore.whiteSpace; 365 | assert(space.split("Lorem ipsum dolor sit amet").length, 5); 366 | assert(space.split("RE-Build").length, 1); 367 | }); 368 | 369 | it("search", function() { 370 | var builder = RE.matching.oneOrMore.digit; 371 | assert(builder.search("The answer is 42."), 14); 372 | assert(builder.search("Hello, world!"), -1); 373 | }); 374 | }); 375 | 376 | }); 377 | -------------------------------------------------------------------------------- /re-build.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * RE-Build - v1.0.0 3 | * by Massimo Artizzu (MaxArt2501) 4 | * 5 | * https://github.com/MaxArt2501/re-build 6 | * 7 | * Licensed under the MIT License 8 | * See LICENSE for details 9 | */ 10 | 11 | (function (root, factory) { 12 | if (typeof define === "function" && define.amd) { 13 | // AMD. Register as an anonymous module. 14 | define([], factory); 15 | } else if (typeof exports === "object") { 16 | // Node. Does not work with strict CommonJS, but 17 | // only CommonJS-like environments that support module.exports, 18 | // like Node. 19 | module.exports = factory(); 20 | } else { 21 | // Browser globals (root is window) 22 | root.RE = factory(); 23 | } 24 | })(this, function() { 25 | "use strict"; 26 | 27 | var O = Object; 28 | var extend = O.assign || function(dest) { 29 | for (var i = 1, source, prop; i < arguments.length;) { 30 | source = arguments[i++]; 31 | if (source) 32 | for (var prop in source) 33 | dest[prop] = source[prop]; 34 | } 35 | 36 | return dest; 37 | }; 38 | var defineProps = O.defineProperties; 39 | 40 | var flags = [ "global", "ignoreCase", "multiline", "unicode", "sticky" ], 41 | settingList = flags.concat([ "min", "max", "lazy", "negate" ]); 42 | 43 | var /** @const */ NOQUANTIFY = 1, 44 | /** @const */ NOSETS = 2; 45 | 46 | var getCodePointAt = "".codePointAt ? function(string, index) { 47 | return string.codePointAt(index); 48 | } : function(string, index) { 49 | var code = string.charCodeAt(index); 50 | if (code >= 0xd800 && code <= 0xdbff) { 51 | var surr = string.charCodeAt(index + 1); 52 | if (surr >= 0xdc00 && surr <= 0xdfff) 53 | code = 0x10000 + ((code - 0xd800) << 10) + (surr - 0xdc00); 54 | } 55 | 56 | return code; 57 | }; 58 | 59 | var names = { 60 | digit: ["\\d", "\\D"], 61 | alphaNumeric: [ "\\w", "\\W"], 62 | whiteSpace: ["\\s", "\\S"], 63 | wordBoundary: ["\\b", "\\B", NOQUANTIFY + NOSETS], 64 | anyChar: [".", "", NOSETS], 65 | 66 | tab: ["\\t"], 67 | vTab: ["\\v"], 68 | cReturn: ["\\r"], 69 | newLine: ["\\n"], 70 | formFeed: ["\\f"], 71 | null: ["\\0"], 72 | slash: ["\\/"], 73 | backslash: ["\\\\"], 74 | 75 | theStart: ["^", "", NOQUANTIFY + NOSETS], 76 | theEnd: ["$", "", NOQUANTIFY + NOSETS], 77 | 78 | ascii: [function() { 79 | var source = ""; 80 | for (var i = 0, j = 0; i < arguments.length; i++) { 81 | var arg = arguments[i], code; 82 | if (typeof arg === "string") { 83 | code = arg.charCodeAt(j++); 84 | if (j < arg.length) i--; 85 | else j = 0; 86 | } else code = arg|0; 87 | if (code < 0 || code > 255) 88 | throw new RangeError("Invalid character code"); 89 | 90 | source += "\\x" + ("0" + code.toString(16)).slice(-2); 91 | } 92 | 93 | return source; 94 | }], 95 | codePoint: [function() { 96 | var source = "", 97 | unicode = this.unicode; 98 | 99 | for (var i = 0, j = 0; i < arguments.length; i++) { 100 | var arg = arguments[i], code; 101 | if (typeof arg === "string") { 102 | code = unicode ? getCodePointAt(arg, j) : arg.charCodeAt(j); 103 | j += code > 0xffff ? 2 : 1; 104 | if (j < arg.length) i--; 105 | else j = 0; 106 | } else code = arg|0; 107 | 108 | if (code < 0 || code > 0x10ffff) 109 | throw new RangeError("Invalid code point " + code); 110 | if (code > 0xffff && !unicode) { 111 | // Computing surrogate code points 112 | code -= 0x10000; 113 | // First surrogate is immediately added to the source 114 | source += "\\u" + (0xd800 + (code >> 10)).toString(16); 115 | code = 0xdc00 + (code & 0x3ff); 116 | } 117 | 118 | source += "\\u" + (code > 0xffff ? "{" + code.toString(16) + "}" : ("000" + code.toString(16)).slice(-4)); 119 | } 120 | 121 | return source; 122 | }], 123 | control: [function(letter) { 124 | if (!/^[a-zA-Z]$/.test(letter)) 125 | throw new RangeError("Invalid control code"); 126 | 127 | return "\\c" + letter.toUpperCase(); 128 | }], 129 | 130 | group: [function() { 131 | var source = parseArgs(arguments); 132 | if (source.slice(0, 3) !== "(?:") 133 | source = "(?:" + source + ")"; 134 | 135 | return source; 136 | }, 0, NOSETS], 137 | capture: [function() { 138 | var source = parseArgs(arguments); 139 | if (source.slice(0, 3) === "(?:") 140 | source = "(" + source.slice(3); 141 | else if (source.charAt(0) !== "(") 142 | source = "(" + source + ")"; 143 | 144 | return source; 145 | }, 0, NOSETS], 146 | reference: [function(number) { 147 | if (typeof number !== "number" || number !== number | 0 || number < 0) 148 | throw new RangeError("Invalid back reference number"); 149 | 150 | return "\\" + number; 151 | }, 0, NOSETS] 152 | }; 153 | 154 | var flagger = { 155 | withFlags: function() { 156 | return function(flags) { 157 | var consts = {}; 158 | if (typeof flags === "string") 159 | consts = { 160 | global: ~flags.indexOf("g"), 161 | ignoreCase: ~flags.indexOf("i"), 162 | multiline: ~flags.indexOf("m"), 163 | unicode: ~flags.indexOf("u"), 164 | sticky: ~flags.indexOf("y") 165 | } 166 | else if (typeof flags === "object") 167 | flags.forEach(function(f) { consts[f] = this[f]; }, flags); 168 | 169 | return buildBuilder(setConsts({}, consts), [ matcher ]); 170 | }; 171 | } 172 | }; 173 | flags.forEach(function(flag) { 174 | flagger[this[flag]] = function() { 175 | var consts = {}; 176 | flags.forEach(function(f) { consts[f] = f === flag || this[f]; }, this); 177 | 178 | return buildBuilder(setConsts({}, consts), [ flagger, matcher ]); 179 | }; 180 | }, { 181 | global: "globally", 182 | ignoreCase: "anyCase", 183 | multiline: "fullText", 184 | unicode: "withUnicode", 185 | sticky: "stickily" 186 | }); 187 | 188 | var matcher = { 189 | matching: function() { 190 | return buildBuilder(initFunc(function() { 191 | return buildBuilder(createBuilder(getFlags(this), parseArgs(arguments)), [ thenable ]); 192 | }, getFlags(this)), [ openable, lookAheads, negator([ negable, lookAheads ]) ]); 193 | } 194 | }; 195 | 196 | var quantifiers = { 197 | between: function() { 198 | return function(min, max) { 199 | if (min != null && (isNaN(min) || Math.floor(min) !== +min || +min < 0) 200 | || max != null && (isNaN(max) || Math.floor(max) !== +max || +max < 0)) 201 | throw new RangeError("Non-negative integer expected"); 202 | 203 | if (min == null && max == null) 204 | throw new RangeError("Range expected"); 205 | 206 | var that = this, 207 | source = this.source, 208 | settings = extend(getSettings(this), { min: min, max: max }); 209 | 210 | return buildBuilder(initFunc(function() { 211 | return buildBuilder(createBuilder(getFlags(that), 212 | source + wrapSource(parseArgs(arguments), settings)), [ thenable ]); 213 | }, settings, source), [ quantifiable, negator([ qntnegable ]) ]); 214 | }; 215 | }, 216 | exactly: function() { 217 | return function(quantity) { 218 | return this.between(quantity, quantity); 219 | }; 220 | }, 221 | atLeast: function() { 222 | return function(quantity) { 223 | return this.between(quantity, this.max); 224 | }; 225 | }, 226 | atMost: function() { 227 | return function(quantity) { 228 | return this.between(this.min, quantity); 229 | }; 230 | }, 231 | anyAmountOf: function() { 232 | return this.between(0, Infinity); 233 | }, 234 | noneOrOne: function() { 235 | return this.between(0, 1); 236 | }, 237 | oneOrMore: function() { 238 | return this.between(1, Infinity); 239 | } 240 | }; 241 | 242 | var lazinator = { 243 | lazily: function() { 244 | return buildBuilder(createBuilder(extend(getSettings(this), { lazy: true }), this.source), [ quantifiers ]); 245 | } 246 | }; 247 | 248 | var thenable = { 249 | then: function() { 250 | var settings = getFlags(this), 251 | source = this.source; 252 | 253 | return buildBuilder(initFunc(function() { 254 | return buildBuilder(createBuilder(settings, 255 | source + parseArgs(arguments)), [ thenable ]); 256 | }, settings, source), [ openable, negator([ negable ]) ]); 257 | }, 258 | or: function() { 259 | var settings = getFlags(this), 260 | source = this.source + "|"; 261 | 262 | return buildBuilder(initFunc(function() { 263 | return buildBuilder(createBuilder(settings, 264 | source + parseArgs(arguments)), [ thenable ]); 265 | }, settings, source), [ openable, lookAheads, negator([ negable, lookAheads ]) ]); 266 | } 267 | }; 268 | 269 | var openable = {}, negable = {}, 270 | settable = {}, setnegable = {}, 271 | quantifiable = {}, qntnegable = {}; 272 | 273 | O.keys(names).forEach(function(name) { 274 | var def = names[name]; 275 | 276 | if (typeof def[0] === "string") { 277 | openable[name] = function() { 278 | var source = this.source + wrapSource(this.negate && def[1] || def[0], this); 279 | return buildBuilder(createBuilder(getFlags(this), source), [ thenable ]); 280 | }; 281 | if (def[1]) negable[name] = openable[name]; 282 | } else 283 | openable[name] = function() { 284 | return function() { 285 | var source = this.source + wrapSource(def[0].apply(this, arguments), this); 286 | return buildBuilder(createBuilder(getFlags(this), source), [ thenable ]); 287 | } 288 | }; 289 | if (!(def[2] & NOQUANTIFY)) { 290 | quantifiable[name] = openable[name]; 291 | if (def[1]) qntnegable[name] = openable[name]; 292 | } 293 | 294 | if (!(def[2] & NOSETS)) { 295 | if (typeof def[0] === "string") { 296 | settable[name] = function() { 297 | var source = this.source, 298 | lastBracket = source.lastIndexOf("]"); 299 | return buildBuilder(createBuilder(getFlags(this), source.slice(0, lastBracket) 300 | + (this.negate && def[1] || def[0]) + source.slice(lastBracket)), [ thenable, andCharSet ]); 301 | }; 302 | if (def[1]) setnegable[name] = settable[name]; 303 | } else 304 | settable[name] = function() { 305 | return function() { 306 | var source = this.source, 307 | lastBracket = source.lastIndexOf("]"); 308 | return buildBuilder(createBuilder(getFlags(this), source.slice(0, lastBracket) 309 | + def[0].apply(this, arguments) + source.slice(lastBracket)), [ thenable, andCharSet ]); 310 | }; 311 | }; 312 | } 313 | }); 314 | openable.oneOf = negable.oneOf = quantifiable.oneOf = qntnegable.oneOf = function() { 315 | var that = this, source = this.source; 316 | 317 | return buildBuilder(initFunc(function() { 318 | return buildBuilder(createBuilder(getFlags(that), source 319 | + wrapSource((that.negate ? "[^" : "[") + parseSets(arguments) + "]", that)), [ andCharSet, thenable ]); 320 | }, getSettings(this), source + wrapSource(this.negate ? "[^]" : "[]", this)), [ settable ]); 321 | }; 322 | extend(openable, quantifiers, lazinator); 323 | 324 | settable.backspace = function() { 325 | var source = this.source, 326 | lastBracket = source.lastIndexOf("]"); 327 | return buildBuilder(createBuilder(getFlags(this), source.slice(0, lastBracket) 328 | + "\\b" + source.slice(lastBracket)), [ thenable, andCharSet ]); 329 | }; 330 | settable.range = function() { 331 | function checkBoundary(bnd) { 332 | if (typeof bnd === "string" && bnd.length === 1) 333 | return parseSets(bnd); 334 | 335 | if (isBuilder(bnd)) { 336 | bnd = bnd.source; 337 | if (bnd.length === 1 || /^\\(?:[0btnvfr\/\\]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|c[a-zA-Z])$/.test(bnd)) 338 | return bnd; 339 | } 340 | 341 | throw new RangeError("Incorrect character range"); 342 | } 343 | return function(start, end) { 344 | start = checkBoundary(start); 345 | end = checkBoundary(end); 346 | 347 | var source = this.source, 348 | lastBracket = source.lastIndexOf("]"); 349 | return buildBuilder(createBuilder(getFlags(this), source.slice(0, lastBracket) 350 | + start + "-" + end + source.slice(lastBracket)), 351 | [ thenable, andCharSet ]); 352 | }; 353 | }; 354 | extend(settable, negator([ setnegable ])); 355 | 356 | var andCharSet = { 357 | and: function() { 358 | var flags = getFlags(this), source = this.source; 359 | 360 | return buildBuilder(initFunc(function() { 361 | var lastBracket = source.lastIndexOf("]"); 362 | return buildBuilder(createBuilder(flags, source.slice(0, lastBracket) 363 | + parseSets(arguments) + source.slice(lastBracket)), [ andCharSet, thenable ]); 364 | }, flags, source), [ settable ]); 365 | } 366 | }; 367 | 368 | var lookAheads = { 369 | followedBy: function() { 370 | return function() { 371 | var source = wrapSource(parseArgs(arguments), this), 372 | seq = this.negate ? "(?!" : "(?="; 373 | if (source.slice(0, 3) !== seq) 374 | source = seq + source + ")"; 375 | 376 | return buildBuilder(createBuilder(getFlags(this), (this.source || "") + source), [ thenable ]); 377 | }; 378 | } 379 | }; 380 | extend(thenable, lookAheads); 381 | 382 | function negator(bundles) { 383 | return { not: function() { 384 | return buildBuilder(createBuilder(extend(getSettings(this), { negate: true }), this.source), bundles); 385 | } }; 386 | } 387 | 388 | /** 389 | * Adds the eventual quantifier to a chunk of regex source, conveniently 390 | * wrapping it in a non-capturing group if it contains more than a block. 391 | * @param {string} source 392 | * @param {Object} settings Quantifying settings (min, max and lazy). 393 | * @returns {string} Quantified source 394 | */ 395 | function wrapSource(source, settings) { 396 | if (typeof settings.min === "number" || typeof settings.max === "number") { 397 | var quantifier, 398 | min = typeof settings.min === "number" ? settings.min : 0, 399 | max = typeof settings.max === "number" ? settings.max : Infinity; 400 | 401 | if (min === max) 402 | quantifier = min === 1 ? "" : "{" + min + "}"; 403 | else if (min === 0) 404 | quantifier = max === 1 ? "?" 405 | : max === Infinity ? "*" 406 | : "{," + max + "}"; 407 | else if (min === 1) 408 | quantifier = max === Infinity ? "+" : "{1," + max + "}"; 409 | else quantifier = "{" + min + "," + (max === Infinity ? "" : max) + "}"; 410 | 411 | if (quantifier) { 412 | if ((source.length > 2 || source.length === 2 && source[0] !== "\\") && hasManyBlocks(source)) 413 | source = "(?:" + source + ")"; 414 | source += quantifier + (settings.lazy ? "?" : ""); 415 | } 416 | } 417 | 418 | return source; 419 | } 420 | 421 | function getConstMap(consts) { 422 | var map = {}; 423 | for (var name in consts) 424 | map[name] = { value: consts[name], writable: false, configurable: false }; 425 | 426 | return map; 427 | } 428 | function setConsts(dest, consts) { 429 | return defineProps(dest, getConstMap(consts)); 430 | } 431 | 432 | function initFunc(fnc, consts, source) { 433 | consts.source = source || ""; 434 | return setConsts(fnc, consts); 435 | } 436 | 437 | /** 438 | */ 439 | function reparser(blocks) { 440 | var source = "", i = 0, block; 441 | while (i < blocks.length) { 442 | block = blocks[i++]; 443 | if (typeof block === "string") 444 | source += block.replace(this, "\\$&"); 445 | else if (block instanceof RegExp || isBuilder(block)) 446 | source += block.source; 447 | } 448 | return source; 449 | } 450 | var parseArgs = reparser.bind(/[\^\$\/\.\*\+\?\|\(\)\[\]\{\}\\]/g), 451 | parseSets = reparser.bind(/[\^\/\[\]\\-]/g); 452 | 453 | function hasManyBlocks(source) { 454 | var len = source.length; 455 | if (len < 2 || len === 2 && (source[0] === "\\" || source === "[]" || source === "()")) return false; 456 | 457 | if (source[0] === "[" && source[len - 1] === "]") 458 | return source.search(/[^\\]\]/) < len - 2; 459 | 460 | if (source[0] === "(" && source[len - 1] === ")") { 461 | var re = /[\(\)]/g, count = 1, match; 462 | re.lastIndex = 1; 463 | while (match = re.exec(source)) { 464 | if (source[match.index - 1] === "\\") continue; 465 | if (match[0] === ")") { 466 | if (!--count) 467 | return match.index < len - 1; 468 | } else count++; 469 | } 470 | } 471 | 472 | return true; 473 | } 474 | 475 | function getSettings(object, props) { 476 | if (!props) props = settingList; 477 | for (var i = 0, sets = {}; i < props.length; i++) 478 | sets[props[i]] = object[props[i]]; 479 | 480 | return sets; 481 | } 482 | function getFlags(object) { return getSettings(object, flags); } 483 | 484 | /** 485 | * RegExpBuilder factory function 486 | */ 487 | function buildBuilder(dest, bundles) { 488 | var i = 0, bundle, prop, defs = {}; 489 | while (i < bundles.length) { 490 | bundle = bundles[i++]; 491 | for (prop in bundle) { 492 | defs[prop] = { configurable: false, enumerable: true }; 493 | if (typeof bundle[prop] === "function") { 494 | defs[prop].get = bundle[prop]; 495 | } else { 496 | defs[prop].value = bundle[prop]; 497 | defs[prop].writable = false; 498 | } 499 | } 500 | } 501 | 502 | return defineProps(dest, defs); 503 | } 504 | 505 | var proto = { 506 | valueOf: function() { return this.regex; }, 507 | toString: function() { return "/" + this.source + "/" + this.flags; }, 508 | test: function(string) { return this.regex.test(string); }, 509 | exec: function(string) { return this.regex.exec(string); }, 510 | replace: function(string, subs) { return string.replace(this.regex, subs); }, 511 | split: function(string) { return string.split(this.regex); }, 512 | search: function(string) { return string.search(this.regex); } 513 | }; 514 | proto.toRegExp = proto.valueOf; 515 | 516 | function getPropDefs(settings, source) { 517 | if (typeof source !== "string") source = ""; 518 | 519 | var flags = (settings.global ? "g" : "") 520 | + (settings.ignoreCase ? "i" : "") 521 | + (settings.multiline ? "m" : "") 522 | + (settings.unicode ? "u" : "") 523 | + (settings.sticky ? "y" : ""); 524 | 525 | var defs = getConstMap({ 526 | global: settings.global, 527 | ignoreCase: settings.ignoreCase, 528 | multiline: settings.multiline, 529 | sticky: settings.sticky, 530 | negate: settings.negate, 531 | lazy: settings.lazy, 532 | min: settings.min, 533 | max: settings.max, 534 | source: source, 535 | flags: flags 536 | }); 537 | var regex; 538 | defs.regex = { 539 | get: function() { 540 | return regex || (regex = new RegExp(source, flags)); 541 | }, 542 | configurable: false 543 | }; 544 | 545 | return defs; 546 | } 547 | 548 | function createBuilder(settings, source) { 549 | var defs = getPropDefs(settings, source); 550 | defs.regex.configurable = false; 551 | 552 | return O.create(proto, defs); 553 | }; 554 | function isBuilder(object) { 555 | return proto.isPrototypeOf(object); 556 | } 557 | 558 | function RE() { 559 | return buildBuilder(createBuilder(getFlags(RE), parseArgs(arguments)), [ thenable ]); 560 | } 561 | 562 | buildBuilder(initFunc(RE, 563 | { global: false, ignoreCase: false, multiline: false, unicode: false, sticky: false }), 564 | [ openable, flagger, matcher ]); 565 | 566 | return RE; 567 | }); 568 | --------------------------------------------------------------------------------