├── docco-gen ├── guide ├── images │ ├── bg.gif │ ├── page.gif │ ├── clock.gif │ ├── horiz.jpg │ ├── open_0.png │ ├── open_1.png │ ├── button_0.jpg │ ├── closed_0.png │ ├── closed_1.png │ ├── comment.gif │ ├── headerbg.gif │ ├── tableft.gif │ ├── tabright.gif │ ├── headerphoto.jpg │ ├── left-corner.jpg │ ├── firefox-gray.jpg │ ├── right-corner.jpg │ └── PointLobosCropped.jpg ├── trace.html ├── unicode.html ├── lastIndex.html ├── defineUdt.html ├── exec.html ├── test.html ├── css │ ├── apgexp.css │ └── BrightSide.css ├── maxCallStackDepth.html ├── global.html ├── ast.html ├── sticky.html ├── source.html ├── exclude.html ├── split.html ├── lastMatch.html ├── toText.html ├── apgExpError.html ├── import.js ├── flags.html ├── nodeHits.html ├── replace.html ├── index.html ├── result.html └── debug.html ├── .npmignore ├── .gitignore ├── abnf └── replace-grammar.bnf ├── package.json ├── index.md ├── src ├── split.js ├── sabnf-generator.js ├── flags.js ├── parse-replacement.js ├── replace.js ├── replace-grammar.js ├── exec.js ├── result.js └── apg-exp.js ├── apg-exp-min.css ├── apg-exp.css └── README.md /docco-gen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docco -l classic index.md README.md src/*.js 3 | exit 0 -------------------------------------------------------------------------------- /guide/images/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/bg.gif -------------------------------------------------------------------------------- /guide/images/page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/page.gif -------------------------------------------------------------------------------- /guide/images/clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/clock.gif -------------------------------------------------------------------------------- /guide/images/horiz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/horiz.jpg -------------------------------------------------------------------------------- /guide/images/open_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/open_0.png -------------------------------------------------------------------------------- /guide/images/open_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/open_1.png -------------------------------------------------------------------------------- /guide/images/button_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/button_0.jpg -------------------------------------------------------------------------------- /guide/images/closed_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/closed_0.png -------------------------------------------------------------------------------- /guide/images/closed_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/closed_1.png -------------------------------------------------------------------------------- /guide/images/comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/comment.gif -------------------------------------------------------------------------------- /guide/images/headerbg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/headerbg.gif -------------------------------------------------------------------------------- /guide/images/tableft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/tableft.gif -------------------------------------------------------------------------------- /guide/images/tabright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/tabright.gif -------------------------------------------------------------------------------- /guide/images/headerphoto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/headerphoto.jpg -------------------------------------------------------------------------------- /guide/images/left-corner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/left-corner.jpg -------------------------------------------------------------------------------- /guide/images/firefox-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/firefox-gray.jpg -------------------------------------------------------------------------------- /guide/images/right-corner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/right-corner.jpg -------------------------------------------------------------------------------- /guide/images/PointLobosCropped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldthomas/apg-js2-exp/HEAD/guide/images/PointLobosCropped.jpg -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.npmignore 3 | *~ 4 | apgexp.js 5 | cmsg 6 | **html/ 7 | **RemoteSystemsTempFiles/ 8 | **workfiles/ 9 | **test/ 10 | **docs/ 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.npmignore 4 | *~ 5 | *.log 6 | *.out 7 | *.txt 8 | *.zip 9 | *.tgz 10 | cmsg 11 | build 12 | webpack-input 13 | **RemoteSystemsTempFiles/ 14 | **node_modules/ 15 | **work/ 16 | **test/ 17 | **stress-test/ 18 | **docs/ 19 | 20 | -------------------------------------------------------------------------------- /abnf/replace-grammar.bnf: -------------------------------------------------------------------------------- 1 | ; 2 | ; SABNF grammar for parsing out the replacement string parameters 3 | ; 4 | rule = *(*any-other [(escape / match / prefix/ suffix/ xname / error)]) 5 | error = "$" any-other 6 | escape = "$$" 7 | match = "$&" 8 | prefix = "$`" 9 | suffix = "$'" 10 | xname = "${" name "}" 11 | name = alpha *(alpha/digit/%d45/%d95) 12 | alpha = %d97-122 / %d65-90 13 | digit = %d48-57 14 | any-other = %x20-23 / %x25-FFFF / %xA-D 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apg-exp", 3 | "version": "2.1.0", 4 | "description": "Pattern-matching alternative to RegExp. Replaces the regular expression syntax with ABNF. Adds APG parser features such as User Defined Terminals (hand-written pattern matchers) and access to the AST.", 5 | "main": "./src/apg-exp.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "files": [ 10 | "src", 11 | "guide", 12 | "apg-exp.js", 13 | "apg-exp-min.js", 14 | "apg-exp.css", 15 | "apg-exp-min.css", 16 | "index.md", 17 | "docco-gen" 18 | ], 19 | "dependencies": { 20 | "apg-api": "1.0.x", 21 | "apg-lib": "3.1.x" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/ldthomas/apg-js2-exp.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/ldthomas/apg-js2-exp/issues" 29 | }, 30 | "keywords": [ 31 | "regexp", 32 | "regex", 33 | "patterns", 34 | "pattern-matching", 35 | "abnf", 36 | "search", 37 | "apg" 38 | ], 39 | "author": "Lowell D. Thomas (http://coasttocoastresearch.com)", 40 | "license": "BSD-3-Clause" 41 | } 42 | -------------------------------------------------------------------------------- /guide/trace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Property: trace

18 |

Syntax

19 |
20 | var exp    = new ApgExp(pattern, "d");
21 | var result = exp.exec(input);
22 | var html   = exp.trace.toHtml();
23 | 
24 |

25 | If the debug mode flag is set, then exp.trace is the APG trace object. 26 | exp.trace can be used to display the path of the parser through the parse tree. 27 | If the debug property is not set, exp.trace is undefined. 28 |

29 |

Example

30 |

31 | See the debug mode example. 32 |

33 |
34 |
35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | [ home](http://coasttocoastresearch.com/) 2 | 3 | **Annotated Table of Contents**
4 | *apg-exp - APG Expressions*
5 | 6 | 0. The GitHub & npm README page. 7 | > [README.md](./README.html) 8 | 9 | 0. The user interface. Here is were all of the functions that the user will call are defined and explained. 10 | > [apg-exp.js](./apg-exp.html)
11 | 12 | 0. The `exec()` and `test()` functions are implemented in this file. 13 | > [exec.js](./exec.html)
14 | 15 | 0. The `replace()` function is implemented in this file. 16 | > [replace.js](./replace.html)
17 | 18 | 0. The `split()` function is implemented in this file. 19 | matching phrases from the input string as it goes. 20 | > [split.js](./split.html)
21 | 22 | 0. The text and HTML display functions are implemented in this file. 23 | > [result.js](./result.html)
24 | 25 | 0. When the input is SABNF syntax text, this file will call the APG functions necessary to generate a parser from it. 26 | > [sabnf-generator.js](./sabnf-generator.html)
27 | 28 | 0. Parses the replacement string of the `replace()` function, coding the replacement phrase shorthands for the phrase parts, 29 | such as the left context ($`), matched phrase (`$&`), etc. 30 | > [parse-replacement.js](./parse-replacement.html)
31 | 32 | -------------------------------------------------------------------------------- /guide/unicode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Unicode Mode

18 |

Syntax

19 |
20 | var exp    = new ApgExp(pattern, "u");
21 | 
22 |

23 | The pattern match attempt is made according to the default mode 24 | or, if the global or sticky flag is set, according to that mode, respectively. 25 | If a match is found, the returned result object values 26 | are arrays of integer character codes rather than strings. 27 |

28 |

Example

29 |

30 |

31 | var pattern, str, exp, result;    
32 | pattern  = 'word  = alpha *(alpha / num)\n';
33 | pattern += 'alpha = %d65-90 / %d97-122\n';
34 | pattern += 'num   = %d48-57\n';
35 | exp = new ApgExp(pattern, "u");
36 | str = "ab12";
37 | result = exp.exec(str);
38 | console.log(result[0]);
39 | console.log(result.rules.alpha[0].phrase);
40 | console.log(result.rules.num[0].phrase);
41 | /* returns */
42 | [ 97, 98, 49, 50 ]
43 | [ 97 ]
44 | [ 49 ]
45 | 
46 |
47 |
48 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /guide/lastIndex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Property: lastIndex

18 |

Syntax

19 |
20 | var exp = new ApgExp(pattern);
21 | var n; /* integer > 0 */
22 | exp.lastIndex = n;
23 | 
24 |

25 | Regardless of the matching mode, the pattern match attempt is made starting at index, 26 | exp.lastIndex. 27 | The user can set exp.lastIndex to any value prior to the match attempt. 28 | The value of exp.lastIndex after the match attempt is 29 | mode and result dependent. 30 |

31 |

Example

32 |

33 |

34 | var pattern, str, exp, result;    
35 | pattern = 'pattern = "abc"\n';
36 | exp = new ApgExp(pattern);
37 | exp.lastIndex = 6;
38 | str = "---abc---ABC---";
39 | result = exp.exec(str);
40 | if(result){
41 |   console.log("found: " + result[0] + " :at: " + result.index);
42 | }else{
43 |   console.log("result: null");
44 | }
45 | /* returns */
46 | found: ABC :at: 9
47 | 
48 |
49 |
50 | 51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /guide/defineUdt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Method: defineUdt()

18 |

19 | APG parsers provide for user-written, phrase-matching functions. 20 | These are called "User Defined Terminals" or UDTs. UDTs can be used 21 | in apg-exp pattern syntax as well. This provides the user 22 | the opportunity to hand write a JavaScript function for special 23 | phrase-matching needs. 24 | The defineUdt() method is used to tell apg-exp 25 | what function to use. 26 |

27 |

Syntax

28 |
29 | var exp = new ApgExp(pattern[, flags]);
30 | exp.defineUdt(name, function);
31 | 
32 |

Parameters

33 |
    34 |
  • name: string: The name of the UDT as specified in the pattern syntax.
  • 35 |
  • function: a function reference to the user-written UDT.
  • 36 |
37 |

Return

38 |

none

39 |

Examples

40 |

41 | The use of APG's UDTs is beyond the scope of this guide. 42 | See the examples in the GitHub repository. 43 |

44 |
45 |
46 | 47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /src/split.js: -------------------------------------------------------------------------------- 1 | // This module implements the `split()` function. 2 | "use strict;" 3 | /* called by split() to split the string */ 4 | exports.split = function(p, str, limit) { 5 | var exp = p._this; 6 | var result, endi, beg, end, last; 7 | var phrases = []; 8 | var splits = []; 9 | var count = 0; 10 | exp.lastIndex = 0; 11 | while (true) { 12 | last = exp.lastIndex; 13 | result = exp.exec(str); 14 | if (result === null) { 15 | break; 16 | } 17 | phrases.push({ 18 | phrase : result[0], 19 | index : result.index 20 | }); 21 | /* ignore flags, uses bump-along mode (increment one character on empty string matches) */ 22 | if (result[0].length === 0) { 23 | exp.lastIndex = last + 1; 24 | } else { 25 | exp.lastIndex = result.index + result[0].length; 26 | } 27 | count += 1; 28 | if (count > limit) { 29 | break; 30 | } 31 | } 32 | if (phrases.length === 0) { 33 | /* no phrases found, return array with the original string */ 34 | return [ str.slice(0) ]; 35 | } 36 | if (phrases.length === 1 || phrases[0].phrase.length === str.length) { 37 | /* one phrase found and it is the entire string */ 38 | return [ "" ]; 39 | } 40 | /* first segment, if any */ 41 | if (phrases[0].index > 0) { 42 | beg = 0; 43 | end = phrases[0].index; 44 | splits.push(str.slice(beg, end)); 45 | } 46 | /* middle segments, if any */ 47 | endi = phrases.length - 1; 48 | for (var i = 0; i < endi; i++) { 49 | beg = phrases[i].index + phrases[i].phrase.length; 50 | end = phrases[i + 1].index; 51 | splits.push(str.slice(beg, end)); 52 | } 53 | /* last segment, if any */ 54 | last = phrases[phrases.length - 1]; 55 | beg = last.index + last.phrase.length; 56 | if (beg < str.length) { 57 | end = str.length; 58 | splits.push(str.slice(beg, end)); 59 | } 60 | return splits; 61 | } 62 | -------------------------------------------------------------------------------- /guide/exec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 |

Method: exec()

17 |

The exec() method executes a search for a match in a specified string. 18 |

19 |

Syntax

20 |
21 | var exp = new ApgExp(pattern[, flags]);
22 | var result = exp.exec(str);
23 |

Parameters

24 |
    25 |
  • str: string/array: The string to search for a pattern match in. 26 | May be a string or array of integer character codes.
  • 27 |
28 |

Return

29 |

30 | If the match succeeds, a result object 31 | is returned, otherwise null. 32 |

33 |

Example

34 |
35 | var pattern, str, exp, result;
36 | var printResult = function(result){
37 |   if(result){
38 |     console.log("found: " + result[0] + " :at: " + result.index);
39 |   }else{
40 |     console.log("not found: result: " + result);
41 |   }
42 | }
43 | pattern = 'pattern = "abc"\n';
44 | exp = new ApgExp(pattern);
45 | result = exp.exec("---abc");
46 | printResult(result);
47 | result = exp.exec([45, 45, 45, 97, 98, 99]);
48 | printResult(result);
49 | result = exp.exec("xyz");
50 | printResult(result);
51 | /* returns */
52 | found: abc :at: 3
53 | found: abc :at: 3
54 | not found: result: null
55 | 
56 |

57 |
58 |
59 | 60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /guide/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Method: test()

18 |

19 | The 20 | test() 21 | method executes a search for a match in the specified string. It operates like the 22 | 23 | exec() 24 | 25 | method in most regards, including modes, but operates with less overhead. 26 |

27 |

Syntax

28 |
29 | var exp = new ApgExp(pattern[, flags]);
30 | var result = exp.test(str);
31 |

Parameters

32 |
    33 |
  • str: string/array: The string to search for a pattern match in. May be a string or array of integer 34 | character codes.
  • 35 |
36 |

Return

37 |

38 | If a match is found, 39 | true 40 | is returned, otherwise 41 | false 42 | . 43 |

44 |

Example

45 |
46 | var pattern, str, exp, result;
47 | pattern = 'pattern = "abc"\n';
48 | exp = new ApgExp(pattern);
49 | result = exp.test("---abc");
50 | console.log("result: " + result);
51 | result = exp.test([45, 45, 45, 97, 98, 99]);
52 | console.log("result: " + result);
53 | result = exp.test("xyz");
54 | console.log("result: " + result);
55 | /* returns */
56 | result: true
57 | result: true
58 | result: false
59 | 
60 |
61 |
62 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /guide/css/apgexp.css: -------------------------------------------------------------------------------- 1 | .apg-mono{font-family: monospace;} 2 | .apg-active{font-weight: bold; color: #000000;} 3 | .apg-match{font-weight: bold; color: #264BFF;} 4 | .apg-empty{font-weight: bold; color: #0fbd0f;} 5 | .apg-nomatch{font-weight: bold; color: #FF4000;} 6 | .apg-lh-match{font-weight: bold; color: #1A97BA;} 7 | .apg-lb-match{font-weight: bold; color: #5F1687;} 8 | .apg-remainder{font-weight: bold; color: #999999;} 9 | .apg-ctrl-char{font-weight: bolder; font-style: italic; font-size: .6em;} 10 | .apg-line-end{font-weight: bold; color: #000000;} 11 | .apg-left-table{font-family:monospace;} 12 | .apg-left-table, 13 | .apg-left-table th, 14 | .apg-left-table td{text-align:left;border:1px solid black;border-collapse:collapse;} 15 | .apg-left-table caption{font-size:125%;font-weight:bold;text-align:left;} 16 | .apg-right-table{font-family:monospace;} 17 | .apg-right-table, 18 | .apg-right-table th, 19 | .apg-right-table td{text-align:right;border:1px solid black;border-collapse:collapse;} 20 | .apg-right-table caption{font-size:125%;font-weight:bold;text-align:left;} 21 | .apg-last-left-table{font-family:monospace;} 22 | .apg-last-left-table, 23 | .apg-last-left-table th, 24 | .apg-last-left-table td{text-align:right;border:1px solid black;border-collapse:collapse;} 25 | .apg-last-left-table th:last-child{text-align:left;} 26 | .apg-last-left-table td:last-child{text-align:left;} 27 | .apg-last-left-table caption{font-size:125%;font-weight:bold;text-align:left;} 28 | .apg-last2-left-table{font-family:monospace;} 29 | .apg-last2-left-table, 30 | .apg-last2-left-table th, 31 | .apg-last2-left-table td{text-align:right;border:1px solid black;border-collapse:collapse;} 32 | .apg-last2-left-table th:last-child{text-align:left;} 33 | .apg-last2-left-table th:nth-last-child(2){text-align:left;} 34 | .apg-last2-left-table td:last-child{text-align:left;} 35 | .apg-last2-left-table td:nth-last-child(2){text-align:left;} 36 | .apg-last2-left-table caption{font-size:125%;font-weight:bold;text-align:left;} 37 | -------------------------------------------------------------------------------- /guide/maxCallStackDepth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Method: maxCallStackDepth()

18 |

19 | maxCallStackDepth() returns an estimate of the maximum call stack depth. 20 | It is well known that recursive-descent parsers can exhibit exponential behavior 21 | for some patterns. This is due to recursion which opens the possibility that 22 | the parser may extend deep into the parse tree. This function is a simple 23 | helper to estimate the maximum call depth or recursion depth 24 | available to the current JavaScript engine. Because the call depth 25 | is limited by the call-stack space, the actual depth will be dependent 26 | on how much data a recursive function leaves on each stack frame. 27 | Therefore, the return of this function should be regarded as an upper bound. 28 |

29 |

Syntax

30 |
31 | var exp = new ApgExp(pattern[, flags]);
32 | var depth = exp.maxCallStackDepth();
33 |

Parameters

34 |

35 | none 36 |

37 |

Return

38 |

integer: An upper bound on the current JavaScript engine's call stack depth.

39 |

Example

40 |
41 | var pattern, exp, result;
42 | pattern  = 'word  = "abc"\n';
43 | exp = new ApgExp(pattern);
44 | result = exp.maxCallStackDepth();
45 | console.log("max depth: "+result);
46 | /* returns */
47 | max depth: 15699
48 | 
49 |
50 |
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /guide/global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Global Mode

18 |

Syntax

19 |
20 | var exp    = new ApgExp(pattern, "g");
21 | 
22 |

23 | The pattern match attempt is made at the index, exp.lastIndex. 24 | The user can set exp.lastIndex to any value prior to the match attempt. 25 | If a match is not found exp.lastIndex is incremented by one and the attempt repeated. 26 | This repeats until either a match is found or 27 | the end of string is reached. 28 | In global mode if a match is found, exp.lastIndex is 29 | incremented by the length of the matched pattern, or one if the length is zero. 30 | (Some patterns allow matches to empty strings. In this case exp.lastIndex 31 | is incremented by one to prevent infinite loops and to allow the global search to continue.) 32 | This allows for an iteration over all matched patterns in the input string. 33 | When the match fails, exp.lastIndex is then reset to zero. 34 |

35 |

Example

36 |

37 |

38 | var pattern, str, exp, result;    
39 | pattern = 'pattern = "abc"\n';
40 | str = "---abc---ABC---";
41 | exp = new ApgExp(pattern, "g");
42 | while(result = exp.exec(str)){
43 |   console.log("found: " + result[0] + " :at: " + result.index);
44 | }
45 | /* returns */
46 | found: abc :at: 3
47 | found: ABC :at: 9
48 | 
49 |
50 |
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /src/sabnf-generator.js: -------------------------------------------------------------------------------- 1 | // This module parses an input SABNF grammar string into a grammar object. 2 | // Errors are reported as an array of error message strings. 3 | // To be called only by the `apg-exp` contructor. 4 | // ``` 5 | // input - required, a string containing the SABNF grammar 6 | // errors - required, must be an array 7 | // ``` 8 | "use strict;"; 9 | module.exports = function(input){ 10 | var errorName = "apg-exp: generator: "; 11 | var api = require("apg-api"); 12 | var result = {obj: null, error: null, text: null, html: null}; 13 | var grammarTextTitle = "annotated grammar:\n"; 14 | var textErrorTitle = "annotated grammar errors:\n"; 15 | function resultError(api, result, header){ 16 | result.error = header; 17 | result.text = grammarTextTitle; 18 | result.text += api.linesToAscii(); 19 | result.text += textErrorTitle; 20 | result.text += api.errorsToAscii(); 21 | result.html = api.linesToHtml(); 22 | result.html += api.errorsToHtml(); 23 | } 24 | while(true){ 25 | /* verify the input string - preliminary analysis*/ 26 | try{ 27 | api = new api(input); 28 | api.scan(); 29 | }catch(e){ 30 | result.error = errorName + e.msg; 31 | break; 32 | } 33 | if(api.errors.length){ 34 | resultError(api, result, "grammar has validation errors"); 35 | break; 36 | } 37 | 38 | /* syntax analysis of the grammar */ 39 | api.parse() 40 | if(api.errors.length){ 41 | resultError(api, result, "grammar has syntax errors"); 42 | break; 43 | } 44 | 45 | /* semantic analysis of the grammar */ 46 | api.translate(); 47 | if(api.errors.length){ 48 | resultError(api, result, "grammar has semantic errors"); 49 | break; 50 | } 51 | 52 | /* attribute analysis of the grammar */ 53 | api.attributes(); 54 | if(api.errors.length){ 55 | resultError(api, result, "grammar has attribute errors"); 56 | break; 57 | } 58 | 59 | /* finally, generate a grammar object */ 60 | result.obj = api.toObject(); 61 | break; 62 | } 63 | return result; 64 | } -------------------------------------------------------------------------------- /src/flags.js: -------------------------------------------------------------------------------- 1 | // This module analyzes the flags string, setting the true/false flags accordingly. 2 | "use strict;" 3 | module.exports = function(obj, flags) { 4 | var errorName = "apg-exp: constructor: flags: "; 5 | var error = null; 6 | var readonly = { 7 | writable : false, 8 | enumerable : false, 9 | configurable : true 10 | }; 11 | /* defaults - all flags default to false */ 12 | /* set to true only if they appear in the input flags string */ 13 | obj.flags = ""; 14 | obj.global = false; 15 | obj.sticky = false; 16 | obj.unicode = false; 17 | obj.debug = false; 18 | while(true){ 19 | /* validation */ 20 | if (typeof (flags) === "undefined" || flags === null) { 21 | break; 22 | } 23 | if (typeof (flags) !== "string") { 24 | error = errorName + "Invalid flags supplied to constructor: must be null, undefined or string: '"+ typeof (flags) + "'"; 25 | break; 26 | } 27 | if (flags === "") { 28 | break; 29 | } 30 | /* set the flags */ 31 | var f = flags.toLowerCase().split(""); 32 | for (var i = 0; i < f.length; i += 1) { 33 | switch (f[i]) { 34 | case "d": 35 | obj.debug = true; 36 | break; 37 | case "g": 38 | obj.global = true; 39 | break; 40 | case "u": 41 | obj.unicode = true; 42 | break; 43 | case "y": 44 | obj.sticky = true; 45 | break; 46 | default: 47 | error = errorName + "Invalid flags supplied to constructor: '" + flags + "'"; 48 | return error; 49 | } 50 | } 51 | /* alphabetize the existing flags */ 52 | if (obj.debug) { 53 | obj.flags += "d"; 54 | } 55 | if (obj.global) { 56 | obj.flags += "g"; 57 | } 58 | if (obj.unicode) { 59 | obj.flags += "u"; 60 | } 61 | if (obj.sticky) { 62 | obj.flags += "y"; 63 | } 64 | break; 65 | } 66 | /* make flag properties read-only */ 67 | Object.defineProperty(obj, "flags", readonly); 68 | Object.defineProperty(obj, "global", readonly); 69 | Object.defineProperty(obj, "debug", readonly); 70 | Object.defineProperty(obj, "unicode", readonly); 71 | Object.defineProperty(obj, "sticky", readonly); 72 | return error; 73 | } -------------------------------------------------------------------------------- /guide/ast.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Property: ast

18 |

19 | The ApgExp object retains a copy of the APG parser's 20 | Abstract Syntax Tree (AST) object. 21 | The AST object is especially useful for making complex translations 22 | of the matched phrase. 23 | The use and details of the AST are beyond the scope of this guide. 24 | See here 25 | and here for examples of its use. 26 |

27 |

Example

28 |
29 | var pattern, exp, str, result;
30 | pattern  = 'word  = alpha *(alpha / num)\n';
31 | pattern += 'alpha = %d65-90 / %d97-122\n';
32 | pattern += 'num   = %d48-57\n';
33 | exp = new ApgExp(pattern);
34 | str = "ab12";
35 | result = exp.exec(str);
36 | console.log("exp.ast in XML format:");
37 | console.log(exp.ast.toXml());
38 | /* result */
39 | exp.ast in XML format:
40 | <?xml version="1.0" encoding="utf-8"?>
41 | <root nodes="5" characters="4">
42 | <!-- input string, decimal integer character codes -->
43 |   97,98,49,50
44 |  <node name="word" index="0" length="4">
45 |    97,98,49,50
46 |   <node name="alpha" index="0" length="1">
47 |     97
48 |   </node><!-- name="alpha" -->
49 |   <node name="alpha" index="1" length="1">
50 |     98
51 |   </node><!-- name="alpha" -->
52 |   <node name="num" index="2" length="1">
53 |     49
54 |   </node><!-- name="num" -->
55 |   <node name="num" index="3" length="1">
56 |     50
57 |   </node><!-- name="num" -->
58 |  </node><!-- name="word" -->
59 | </root>
60 |    
61 |
62 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /guide/sticky.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Sticky Mode

18 |

Syntax

19 |
20 | var exp    = new ApgExp(pattern, "g");
21 | 
22 |

23 | The pattern match attempt is made at the index, exp.lastIndex. 24 | The user can set exp.lastIndex to any value prior to the match attempt. 25 | If a match is not found exp.lastIndex the attempt fails and returns null. 26 | In sticky mode if a match is found, exp.lastIndex is 27 | incremented by the length of the matched pattern, or one if the length is zero. 28 | (Some patterns allow matches to empty strings. In this case exp.lastIndex 29 | is incremented by one to prevent infinite loops and to allow the sticky search to continue.) 30 | This allows for an iteration over all consecutive matched patterns in the input string. 31 | When the match fails, exp.lastIndex is then reset to zero. 32 |

33 |

Example 1

34 |
35 | var pattern, str, exp, result;    
36 | pattern = 'pattern = "abc"\n';
37 | exp = new ApgExp(pattern, "y");
38 | str = "---abc---ABC---";
39 | while(result = exp.exec(str)){
40 |   console.log("found: " + result[0] + " :at: " + result.index);
41 | }
42 | console.log("result: null");
43 | /* returns */
44 | result: null
45 | 
46 |

Example 2

47 |
48 | var pattern, str, exp, result;    
49 | pattern = 'pattern = "abc"\n';
50 | exp = new ApgExp(pattern, "y");
51 | exp.lastIndex = 3;
52 | str = "---abc---ABC---";
53 | while(result = exp.exec(str)){
54 |   console.log("found: " + result[0] + " :at: " + result.index);
55 | }
56 | console.log("result: null");
57 | /* returns */
58 | found: abc :at: 3
59 | result: null
60 | 
61 |

Example 3

62 |
63 | var pattern, str, exp, result;    
64 | pattern = 'pattern = "abc"\n';
65 | exp = new ApgExp(pattern, "y");
66 | str = "abcABCabc";
67 | while(result = exp.exec(str)){
68 |   console.log("found: " + result[0] + " :at: " + result.index);
69 | }
70 | console.log("result: null");
71 | /* returns */
72 | found: abc :at: 0
73 | found: ABC :at: 3
74 | found: abc :at: 6
75 | result: null
76 | 
77 |
78 |
79 | 80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /guide/source.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | headerphoto 15 | 16 |
17 | 18 |

Property: Source

19 |

20 | The ApgExp object retains a copy of the input pattern in the source property. 21 |

22 |

Syntax

23 |
24 | var exp    = new ApgExp(pattern);
25 | 
26 |

Parameters

27 |

28 | exp.source: Copy of pattern. 29 |

30 |

31 | exp.sourceToText(): Returns source as text. 32 |

33 |

34 | exp.sourceToHtml(): Returns source as HTML. 35 |

36 |

37 | exp.sourceToHtmlPage(): Returns source as HTML, wrapped in a header and body for to make a complete HTML page.. 38 |

39 |

Example 1

40 |
41 | var pattern, exp;
42 | pattern  = 'word  = alpha *(alpha / num)\n';
43 | pattern += 'alpha = %d65-90 / %d97-122\n';
44 | pattern += 'num   = %d48-57\n';
45 | exp = new ApgExp(pattern);
46 | console.log("source:");
47 | console.log(exp.source);
48 | / * result */
49 | source:
50 | word  = alpha *(alpha / num)
51 | alpha = %d65-90 / %d97-122
52 | num   = %d48-57
53 | 
54 |

Example 2

55 | The sourceToText() function returns the pattern. 56 | It simply returns exp.source and is mostly for 57 | symmetry in the output functions. 58 |
59 | /* same as Example 1 */
60 | console.log("source:");
61 | console.log(exp.sourceToText());
62 | / * result */
63 | source:
64 | word  = alpha *(alpha / num)
65 | alpha = %d65-90 / %d97-122
66 | num   = %d48-57
67 | 
68 |

Example 3

69 | exp.sourceToHtml() returns the pattern 70 | formatted as HTML. HTML characters, such as "<" and control characters are converted 71 | to entities and special display formats. 72 | The display page header should include the apgexp.css style sheet. e.g. 73 |
74 | <link rel="stylesheet" href="./css/apgexp.css" type="text/css" />
75 | 
76 | exp.sourceToHtml() returns the same, except wrapped 77 | in a complete page header and body. 78 |
79 | /* same as Example 1 */
80 | $("#this-page").html(exp.sourceToHtml());
81 | / * result */
82 | word  = alpha *(alpha / num)LF
83 | alpha = %d65-90 / %d97-122LF
84 | num   = %d48-57LF
85 | 
86 |
87 |
88 | 89 |
90 | 91 | 92 | -------------------------------------------------------------------------------- /apg-exp-min.css: -------------------------------------------------------------------------------- 1 | .apg-mono{font-family:monospace}.apg-active{font-weight:bold;color:#000}.apg-match{font-weight:bold;color:#264bff}.apg-empty{font-weight:bold;color:#0fbd0f}.apg-nomatch{font-weight:bold;color:#ff4000}.apg-lh-match{font-weight:bold;color:#1a97ba}.apg-lb-match{font-weight:bold;color:#5f1687}.apg-remainder{font-weight:bold;color:#999}.apg-ctrl-char{font-weight:bolder;font-style:italic;font-size:.6em}.apg-line-end{font-weight:bold;color:#000}.apg-error{font-weight:bold;color:#ff4000}.apg-phrase{color:#000;background-color:#8caae6}.apg-empty-phrase{color:#0fbd0f}table.apg-state{font-family:monospace;margin-top:5px;font-size:11px;line-height:130%;text-align:left;border:1px solid black;border-collapse:collapse}table.apg-state th,table.apg-state td{text-align:left;border:1px solid black;border-collapse:collapse}table.apg-state th:nth-last-child(2),table.apg-state td:nth-last-child(2){text-align:right}table.apg-state caption{font-size:125%;line-height:130%;font-weight:bold;text-align:left}table.apg-stats{font-family:monospace;margin-top:5px;font-size:11px;line-height:130%;text-align:right;border:1px solid black;border-collapse:collapse}table.apg-stats th,table.apg-stats td{text-align:right;border:1px solid black;border-collapse:collapse}table.apg-stats caption{font-size:125%;line-height:130%;font-weight:bold;text-align:left}table.apg-trace{font-family:monospace;margin-top:5px;font-size:11px;line-height:130%;text-align:right;border:1px solid black;border-collapse:collapse}table.apg-trace caption{font-size:125%;line-height:130%;font-weight:bold;text-align:left}table.apg-trace th,table.apg-trace td{text-align:right;border:1px solid black;border-collapse:collapse}table.apg-trace th:last-child,table.apg-trace th:nth-last-child(2),table.apg-trace td:last-child,table.apg-trace td:nth-last-child(2){text-align:left}table.apg-grammar{font-family:monospace;margin-top:5px;font-size:11px;line-height:130%;text-align:right;border:1px solid black;border-collapse:collapse}table.apg-grammar caption{font-size:125%;line-height:130%;font-weight:bold;text-align:left}table.apg-grammar th,table.apg-grammar td{text-align:right;border:1px solid black;border-collapse:collapse}table.apg-grammar th:last-child,table.apg-grammar td:last-child{text-align:left}table.apg-rules{font-family:monospace;margin-top:5px;font-size:11px;line-height:130%;text-align:right;border:1px solid black;border-collapse:collapse}table.apg-rules caption{font-size:125%;line-height:130%;font-weight:bold;text-align:left}table.apg-rules th,table.apg-rules td{text-align:right;border:1px solid black;border-collapse:collapse}table.apg-rules a{color:#039 !important}table.apg-rules a:hover{color:#8caae6 !important}table.apg-attrs{font-family:monospace;margin-top:5px;font-size:11px;line-height:130%;text-align:center;border:1px solid black;border-collapse:collapse}table.apg-attrs caption{font-size:125%;line-height:130%;font-weight:bold;text-align:left}table.apg-attrs th,table.apg-attrs td{text-align:center;border:1px solid black;border-collapse:collapse}table.apg-attrs th:nth-child(1),table.apg-attrs th:nth-child(2),table.apg-attrs th:nth-child(3){text-align:right}table.apg-attrs td:nth-child(1),table.apg-attrs td:nth-child(2),table.apg-attrs td:nth-child(3){text-align:right}table.apg-attrs a{color:#039 !important}table.apg-attrs a:hover{color:#8caae6 !important} 2 | -------------------------------------------------------------------------------- /guide/exclude.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Methods: include() & exclude()

18 |

19 | By default, the result.rules object retains the sub-phrases 20 | matched by all named rules. 21 | Often there are rule names that are of no interest. 22 | The include() and exclude() methods can be used to limit 23 | the list of rule names retained in the results. 24 |

25 |

Syntax

26 |
 27 | var exp = new ApgExp(pattern[, flags]);
 28 | exp.include(array);
 29 | exp.exclude(array);
 30 | 
31 |

Parameters

32 |

• array: An array of rule names (strings) to include/exclude.

33 |

Return

34 |

35 | none 36 |

37 |

Example 1

38 |

39 | By default, all matches to all rules are retained in the 40 | result.rules object. 41 |

42 |
 43 | var pattern, str, exp, result;
 44 | pattern  = 'word  = alpha *(alpha / num)\n';
 45 | pattern += 'alpha = %d65-90 / %d97-122\n';
 46 | pattern += 'num   = %d48-57\n';
 47 | str = "ab12";
 48 | exp = new ApgExp(pattern);
 49 | result = exp.exec(str);
 50 | console.log(result.toText());
 51 | /* returns */
 52 |     result:
 53 |        [0]: ab12
 54 |      input: ab12
 55 |      index: 0
 56 |     length: 4
 57 | tree depth: 7
 58 |  node hits: 26
 59 |      rules: word : 0: ab12
 60 |           : alpha : 0: a
 61 |           : alpha : 1: b
 62 |           : num : 2: 1
 63 |           : num : 3: 2
 64 | 
65 |

Example 2

66 |

67 | By using exclude(["alpha", "num"]) 68 | "alpha" and "num" are removed from the 69 | result.rules object. 70 |

71 |
 72 | /* same as Example 1 except */
 73 | exp = new ApgExp(pattern);
 74 | exp.exclude(["alpha", "num"])
 75 | result = exp.exec(str);
 76 | console.log(result.toText());
 77 | /* returns */
 78 |     result:
 79 |        [0]: ab12
 80 |      input: ab12
 81 |      index: 0
 82 |     length: 4
 83 | tree depth: 7
 84 |  node hits: 26
 85 |      rules: word : 0: ab12
 86 | 
87 |

Example 3

88 |

89 | By using include(["word"]) all rules except 90 | "word" are removed from the 91 | result.rules object. 92 |

93 |
 94 | /* same as Example 1 except */
 95 | exp = new ApgExp(pattern);
 96 | exp.include(["word"])
 97 | result = exp.exec(str);
 98 | console.log(result.toText());
 99 | /* returns */
100 |     result:
101 |        [0]: ab12
102 |      input: ab12
103 |      index: 0
104 |     length: 4
105 | tree depth: 7
106 |  node hits: 26
107 |      rules: word : 0: ab12
108 | 
109 |
110 |
111 | 112 |
113 | 114 | 115 | -------------------------------------------------------------------------------- /guide/split.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Method: split()

18 |

19 | split() splits the input string into an array of strings, 20 | each of which is the sub-string separating the patterns matched. 21 | It works very similarly to JavaScript's 22 | String.split(separator) 23 | function when separator is a regular expression. 24 |

25 |

Syntax

26 |
27 | var exp = new ApgExp(pattern[, flags]);
28 | var result = exp.split(str[, limit]);
29 |

Parameters

30 |

31 |

    32 |
  • str: string: The string to match patterns in.
  • 33 |
  • limit: integer > 0: default: Infinity: The maximum number of matches to find.
  • 34 |
35 |

36 |

Return

37 |

Returns an array of strings. The modes or flags parameters are ignored. 38 | An attempt is made to match the pattern in the input string up to and including 39 | limit times. 40 |

41 |
    42 |
  • 43 | str is empty, null or undefined: The array 44 | contains a single, empty string. 45 |
  • 46 |
  • 47 | pattern matches the entire input string, str: The array 48 | contains a single, empty string. 49 |
  • 50 |
  • 51 | pattern does not match: The array contains a single string, a copy of the original string. 52 |
  • 53 |
  • 54 | pattern finds matches multiple times: The array contains multiple strings, 55 | each being a sub-string of the input string—the 56 | prefix, separator and suffix sub-strings, if any. 57 |
  • 58 |
  • 59 | pattern is an empty string "": The array contains one string each 60 | for each character in the input string. In this regard it acts like JavaScript's 61 | String.split(""). 62 |
  • 63 |
64 |

Example 1

65 |

Split the string at the semicolons, spaces optional.

66 |
67 | var pattern, exp, str, result;
68 | pattern  = 'pattern = owsp ";" owsp\n';
69 | pattern += 'owsp    = *%d32\n';
70 | str = "one;two ;three ; four";
71 | exp = new ApgExp(pattern);
72 | result = exp.split(str);
73 | console.log("   str: " + str);
74 | console.log("result: " + result);
75 | / * result */
76 |    str: one;two ;three ; four
77 | result: one,two,three,four
78 | 
79 |

Example 2

80 |

Split the string into all of its constituent characters.

81 |
82 | var pattern, exp, str, result;
83 | pattern  = 'pattern = ""\n';
84 | str = "one;two;three;four";
85 | exp = new ApgExp(pattern);
86 | result = exp.split(str);
87 | console.log("   str: " + str);
88 | console.log("result: " + result);
89 | / * result */
90 |    str: one;two;three;four
91 | result: o,n,e,;,t,w,o,;,t,h,r,e,e,;,f,o,u,r
92 | 
93 |
94 |
95 | 96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /guide/lastMatch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Last Match Properties

18 |

19 | After a successful pattern-matching attempt, the ApgExp object retains 20 | information about the last-matched patterns or phrases. Unlike the 21 | result object which retains all 22 | matched phrases for all rule names, the ApgExp object retains only the last matched. 23 |

24 |

Syntax

25 |
26 | var exp    = new ApgExp(pattern[, flags]);
27 | var result = exe.exec(input);    
28 | 
29 |

Last Match Properties

30 |
    31 |
  • exe.input (alias exe["$_"]): 32 | A copy of the input string.
  • 33 |
  • exe.lastMatch (alias exe["$&"]): 34 | A copy of the last-matched pattern - same as result[0]
  • 35 |
  • exe.leftContext (alias exe["$`"]): 36 | Prefix of the input string up to the first character of the matched pattern.
  • 37 |
  • exe.rightContext (alias exe["$'"]): 38 | Suffix of the input string, the phrase following the matched pattern.
  • 39 |
  • exe.rules (alias exe["${rule-name}"]): 40 | The last-matched phrase for the named rule.
  • 41 |
42 |

If Unicode mode is true, i.e. flags = "u", all strings are arrays 43 | of integer character codes, otherwise it is a JavaScript string.

44 |

See Also

45 | 50 |

Example

51 |

52 |

53 | var pattern, exp, str, result;
54 | pattern  = 'word  = alpha *(alpha / num)\n';
55 | pattern += 'alpha = %d65-90 / %d97-122\n';
56 | pattern += 'num   = %d48-57\n';
57 | exp = new ApgExp(pattern);
58 | str = "---ab12...";
59 | result = exp.exec(str);
60 | console.log("exp.leftContext : "+exp.leftContext);
61 | console.log('exp["$`"]       : '+exp["$`"]);
62 | console.log("exp.lastMatch   : "+exp.lastMatch);
63 | console.log('exp["$&"]       : '+exp["$&"]);
64 | console.log("exp.rightContext: "+exp.rightContext);
65 | console.log('exp["$\'"]       : '+exp["$'"]);
66 | console.log("exp.rules.word  : "+exp.rules.word);
67 | console.log("exp[${word}]    : "+exp["${word}"]);
68 | console.log("exp.rules.alpha : "+exp.rules.alpha);
69 | console.log("exp[${alpha}]   : "+exp["${alpha}"]);
70 | console.log("exp.rules.num   : "+exp.rules.num);
71 | console.log("exp[${num}]     : "+exp["${num}"]);
72 | /* result */
73 | exp.leftContext : ---
74 | exp["$`"]       : ---
75 | exp.lastMatch   : ab12
76 | exp["$&"]       : ab12
77 | exp.rightContext: ...
78 | exp["$'"]       : ...
79 | exp.rules.word  : ab12
80 | exp[${word}]    : ab12
81 | exp.rules.alpha : b
82 | exp[${alpha}]   : b
83 | exp.rules.num   : 2
84 | exp[${num}]     : 2
85 | 
86 |

87 |
88 |
89 | 90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /guide/toText.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | headerphoto 15 | 16 |
17 | 18 |

Methods: toText(), toHtml() and toHtmlPage()

19 |

Syntax

20 |
 21 | var exp    = new ApgExp(pattern[, flags]);
 22 | var result = exp.exec(input);
 23 | var text = exp.toText();
 24 | var html = exp.toHtml();
 25 | var page = exp.toHtmlPage();
 26 | 
27 |

28 | These functions format the last match properties 29 | of the exp object for display. 30 |

31 |

Example 1

32 |
 33 | var pattern, exp, str, result;
 34 | pattern  = 'word  = alpha *(alpha / num)\n';
 35 | pattern += 'alpha = %d65-90 / %d97-122\n';
 36 | pattern += 'num   = %d48-57\n';
 37 | exp = new ApgExp(pattern);
 38 | str = "---ab12...";
 39 | result = exp.exec(str);
 40 | console.log(exp.toText());
 41 | /* result */
 42 |   last match:
 43 |    lastIndex: 0
 44 |        flags: ""
 45 |       global: false
 46 |       sticky: false
 47 |      unicode: false
 48 |        debug: false
 49 |        input: ---ab12...
 50 |  leftContext: ---
 51 |    lastMatch: ab12
 52 | rightContext: ...
 53 |        rules: word : ab12
 54 |             : alpha : b
 55 |             : num : 2
 56 | 
 57 | alias:
 58 |  ["$_"]: ---ab12...
 59 |  ["$`"]: ---
 60 |  ["$&"]: ab12
 61 |  ["$'"]: ...
 62 |  ["${word}"]: ab12
 63 |  ["${alpha}"]: b
 64 |  ["${num}"]: 2
 65 | 
66 |

Example 2

67 | Same as Example 1, except format the object for HTML page display. 68 | exp.toHtmlPage() displays the same HTML but wraps it in 69 | a complete page header and body. 70 | Include the apgexp.css style sheet. e.g. 71 |
 72 | <link rel="stylesheet" href="./css/apgexp.css" type="text/css" />
73 |
 74 | /* same as Example 1 */
 75 | $("#this-page").html(exp.toHtml());
 76 | /* result */
 77 | 
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
last match:
itemvalue
lastIndex0
flags""
globalfalse
stickyfalse
unicodefalse
debugfalse
itemphrase
input---ab12...
leftContext---
lastMatchab12
rightContext...
rulephrase
wordab12
alphab
num2
aliasphrase
["$_"]---ab12...
["$`"]---
["$&"]ab12
["$'"]...
["${word}"]ab12
["${alpha}"]b
["${num}"]2
105 |
106 |
107 | 108 |
109 | 110 | 111 | -------------------------------------------------------------------------------- /guide/apgExpError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | headerphoto 15 | 16 |
17 |

ApgExpError

18 |

19 | If the ApgExp constructor encounters and error it will throw an ApgExpError exception object. 20 | Derived from JavaScript's Error object, it has special display member functions. If the SABNF pattern syntax has errors, 21 | specially formatted data is added to the object. 22 |

23 |

Properties

24 |

25 |

    26 |
  • name
  • 27 |
      28 |
    • ApgExpError
    • 29 |
    30 |
  • message
  • 31 |
      32 |
    • a single-line error message
    • 33 |
    34 |
35 |

36 |

Methods

37 |

38 |

    39 |
  • toText()
  • 40 |
      41 |
    • Displays the error message and possibly other pattern syntax error information in text format 42 | suitable for console.log().
    • 43 |
    44 |
  • toHtml()
  • 45 |
      46 |
    • Displays the error message and possibly other pattern syntax error information in HTML table format 47 | suitable for displaying in a web page.
    • 48 |
    49 |
50 |

51 |

Example 1

52 |

The general set up for catching pattern syntax errors and failed pattern matches.

53 |
 54 | try{ 
 55 |   var exp, pattern, flags, result, strintToTest;
 56 |   exp    = new ApgExp(pattern, flags);
 57 |   result = exp.exec(stringToTest);
 58 |   if(result){
 59 |     /* do some thing with the result */
 60 |   }else{
 61 |     /* deal with failure */
 62 |   }
 63 | }catch(e){
 64 |   if(e.name === "ApgExpError"){
 65 |     console.log(e.toText());
 66 |     $("#errors-go-here").html(e.toHtml());
 67 |   }else{
 68 |     /* handle other exceptions */
 69 |   }
 70 | }
71 |

Example 2

72 |

A pattern syntax error as text.

73 |
 74 | var exp, pattern, result;
 75 | pattern  = 'word  = alpha *(alpha / num\n';
 76 | pattern += 'alpha = %d65-90 / %d97-122\n';
 77 | pattern += '1num   = %d48-57\n';
 78 | try{
 79 |   exp = new ApgExp(pattern);
 80 | }catch(e){
 81 |   if(e.name === "ApgExpError"){
 82 |     console.log(e.toText());
 83 |   }else{
 84 |     console.log("other exception: "+e.message);
 85 |   }
 86 | }
 87 | / * result */
 88 | grammar has syntax errors
 89 | 0: 0: 15: word  = alpha * >> (alpha / num
 90 | 0: 0: 15: error: Group, (...), opened but not closed.
 91 | 0: 0: 15: word  = alpha * >> (alpha / num
 92 | 0: 0: 15: error: Unrecognized SABNF element.
 93 | 2: 55: 0:  >> 1num   = %d48-57
 94 | 2: 55: 0: error: Rule names must be alphanum and begin with alphabetic character.
 95 | 
96 |

Example 3

97 |

Same as example 2, except pattern syntax error as HTML.

98 |
 99 | $("#this-page").html(e.toHtml());
100 | / * result */
101 | 
102 |

grammar has syntax errors

103 |

104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 |
line
no.
line
offset
error
offset

text
0015word  = alpha *»(alpha / numLF
↑: Group, (...), opened but not closed.
0015word  = alpha *»(alpha / numLF
↑: Unrecognized SABNF element.
2550»1num   = %d48-57LF
↑: Rule names must be alphanum and begin with alphabetic character.

112 |
113 |
114 | 115 |
116 | 117 | 118 | -------------------------------------------------------------------------------- /guide/import.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var header = ''; 3 | header += '

apg-exp User\'s Guide

'; 4 | header += ''; 9 | 10 | var footer = ''; 11 | footer += ''; 15 | footer += ''; 21 | 22 | var menu = ''; 23 | menu += '

'; 24 | menu += ' Properties'; 25 | menu += '

'; 26 | menu += '

Methods

'; 45 | 46 | menu += '

Other

'; 63 | menu += ''; 67 | 68 | var element; 69 | window.onload = function(){ 70 | element = document.getElementById("header"); 71 | element.innerHTML = header; 72 | element = document.getElementById("sidebar"); 73 | element.innerHTML = menu; 74 | element = document.getElementById("footer"); 75 | element.innerHTML = footer; 76 | } 77 | })() -------------------------------------------------------------------------------- /src/parse-replacement.js: -------------------------------------------------------------------------------- 1 | // This module will parse the replacement string and locate any special replacement characters. 2 | "use strict;" 3 | var errorName = "apgex: replace(): "; 4 | var synError = function(result, chars, phraseIndex, data) { 5 | if(data.isMatch(result.state)){ 6 | var value = data.charsToString(chars, phraseIndex, result.phraseLength); 7 | data.items.push({type: "error", index: phraseIndex, length: result.phraseLength, error: value}); 8 | data.errors += 1; 9 | data.count += 1; 10 | } 11 | } 12 | var synEscape = function(result, chars, phraseIndex, data) { 13 | if(data.isMatch(result.state)){ 14 | data.items.push({type: "escape", index: phraseIndex, length: result.phraseLength}); 15 | data.escapes += 1; 16 | data.count += 1; 17 | } 18 | } 19 | var synMatch = function(result, chars, phraseIndex, data) { 20 | if(data.isMatch(result.state)){ 21 | data.items.push({type: "match", index: phraseIndex, length: result.phraseLength}); 22 | data.matches += 1; 23 | data.count += 1; 24 | } 25 | } 26 | var synPrefix = function(result, chars, phraseIndex, data) { 27 | if(data.isMatch(result.state)){ 28 | data.items.push({type: "prefix", index: phraseIndex, length: result.phraseLength}); 29 | data.prefixes += 1; 30 | data.count += 1; 31 | } 32 | } 33 | var synSuffix = function(result, chars, phraseIndex, data) { 34 | if(data.isMatch(result.state)){ 35 | data.items.push({type: "suffix", index: phraseIndex, length: result.phraseLength}); 36 | data.suffixes += 1; 37 | data.count += 1; 38 | } 39 | } 40 | var synXName = function(result, chars, phraseIndex, data) { 41 | if(data.isMatch(result.state)){ 42 | data.items.push({type: "name", index: phraseIndex, length: result.phraseLength, name: data.name}); 43 | data.names += 1; 44 | data.count += 1; 45 | } 46 | } 47 | var synName = function(result, chars, phraseIndex, data) { 48 | if(data.isMatch(result.state)){ 49 | var nameStr = data.charsToString(chars, phraseIndex, result.phraseLength); 50 | var nameChars = chars.slice(phraseIndex, phraseIndex + result.phraseLength) 51 | data.name = {nameString: nameStr, nameChars: nameChars}; 52 | } 53 | } 54 | module.exports = function(p, str){ 55 | var grammar = new (require("./replace-grammar.js"))(); 56 | var apglib = require("apg-lib"); 57 | var parser = new apglib.parser(); 58 | var data = { 59 | name: "", 60 | count: 0, 61 | errors: 0, 62 | escapes: 0, 63 | prefixes: 0, 64 | matches: 0, 65 | suffixes: 0, 66 | names: 0, 67 | isMatch: p.match, 68 | charsToString: apglib.utils.charsToString, 69 | items: [] 70 | } 71 | parser.callbacks["error"] = synError; 72 | parser.callbacks["escape"] = synEscape; 73 | parser.callbacks["prefix"] = synPrefix; 74 | parser.callbacks["match"] = synMatch; 75 | parser.callbacks["suffix"] = synSuffix; 76 | parser.callbacks["xname"] = synXName; 77 | parser.callbacks["name"] = synName; 78 | var chars = apglib.utils.stringToChars(str); 79 | var result = parser.parse(grammar, 0, chars, data); 80 | if(!result.success){ 81 | throw new Error(errorName + "unexpected error parsing replacement string"); 82 | } 83 | var ret = data.items; 84 | if(data.errors > 0){ 85 | var msg = "["; 86 | var i = 0; 87 | var e = 0; 88 | for(; i < data.items.length; i +=1){ 89 | var item = data.items[i]; 90 | if(item.type === "error"){ 91 | if(e > 0){ 92 | msg += ", " + item.error; 93 | }else{ 94 | msg += item.error; 95 | } 96 | e += 1; 97 | } 98 | } 99 | msg += "]"; 100 | throw new Error(errorName + "special character sequences ($...) errors: " + msg); 101 | } 102 | if(data.names > 0){ 103 | var badNames = []; 104 | var i = 0; 105 | for(; i < data.items.length; i +=1){ 106 | var item = data.items[i]; 107 | if(item.type === "name"){ 108 | var name = item.name.nameString; 109 | var lower = name.toLowerCase(); 110 | if( !p.parser.ast.callbacks[lower]){ 111 | /* name not in callback list, either a bad rule name or an excluded rule name */ 112 | badNames.push(name); 113 | } 114 | /* convert all item rule names to lower case */ 115 | item.name.nameString = lower; 116 | } 117 | } 118 | if(badNames.length > 0){ 119 | var msg = "["; 120 | for(var i = 0; i < badNames.length; i +=1){ 121 | if(i > 0){ 122 | msg += ", " + badNames[i]; 123 | }else{ 124 | msg += badNames[i]; 125 | } 126 | } 127 | msg += "]"; 128 | throw new Error(errorName + "special character sequences ${name}: names not found: " + msg); 129 | } 130 | } 131 | return ret; 132 | } -------------------------------------------------------------------------------- /guide/flags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Flags & Modes

18 |

19 | The optional flags parameter modifies the mode of the pattern-matching operation. 20 |

21 |

Syntax

22 |
23 | var exp    = new ApgExp(pattern[, flags]);
24 | var result = exp.exec(input);
25 |

The flags parameter may be absent, undefined, null or a string of any of the case-insensitive characters "gyud". 26 | The string may be empty, the characters may be in any order and characters may appear more than once. 27 | However, any character not in the list above will cause an ApgExpError exception to be thrown.

28 |

29 | The exp object has 30 | gobal, 31 | sticky, 32 | unicode and 33 | debug boolean flag properties. 34 | These are all initially false indicating default mode. 35 | The exp.flags property is the flags string in a normalized format. 36 |

37 |

Flag Values

38 |
    39 |
  • absent, undefined, null or "" - default mode
  • 40 |
  • g - Sets the exp.global property. global mode
  • 41 |
  • y - Sets the exp.sticky property. sticky mode
  • 42 |
  • u - Sets the exp.unicode property. Unicode mode
  • 43 |
  • d - Sets the exp.debug property. debug mode
  • 44 |
45 | 46 |

47 | If both the global and sticky flags are set, "gy", global is ignored and sticky mode is used. 48 |

49 |

Modes

50 |

default mode - The pattern match attempt is made at the index, exp.lastIndex. 51 | The user can set exp.lastIndex to any value prior to the match attempt. 52 | If a match is found, result is returned and exp.lastIndex is reset to zero. 53 | If a match is not found exp.lastIndex is incremented by one and the attempt repeated. 54 | This repeats until either a match is found or 55 | the end of string is reached. 56 | In all cases, exp.lastIndex is reset to zero after the search.

57 | 58 |

global mode - The pattern match is attempted exactly as for default mode. 59 | However, if a match is found, exp.lastIndex is not set to zero. 60 | It is incremented by the length of the matched pattern, or one if the length is zero. 61 | (Some patterns allow matches to empty strings. In this case exp.lastIndex 62 | is incremented by one to prevent infinite loops and to allow the global search to continue.) 63 | This allows for an iteration over all matched patterns in the input string. 64 | When the match fails, exp.lastIndex is then reset to zero. 65 | (example) 66 |

67 | 68 |

sticky mode - The search is anchored at exp.lastIndex. 69 | This means that either a match is found starting at exp.lastIndex or the search fails. 70 | However, if a match is found, exp.lastIndex is set to the first character in the string 71 | following the matched pattern, just as with global mode. 72 | This allows a loop to iterate over all consecutive matches in the input string 73 | (example) 74 |

75 | 76 |

Unicode mode - The pattern match is attempted according to the current mode above. 77 | If a match is found, the result values are returned as arrays of integer character codes 78 | rather than strings. 79 | (example) 80 |

81 | 82 |

debug mode - Exposes the APG trace object. 83 | exp.trace can be used to display the path of the parser through the parse tree. 84 | If the debug property is false, exp.trace is undefined. 85 | (example) 86 |

87 |
88 |
89 | 90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /guide/nodeHits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 | 17 |

Property: nodeHits and treeDepth

18 |

19 | A well-known problem with recursive-descent parsers is that they may require 20 | exponential time. 21 | This is also sometimes referred to as catastrophic backtracking. 22 | With APG parsers this is less of a problem than with most regular expression 23 | pattern-matching engines because they never backtrack on repetitions—only 24 | on alternates. And even with alternates, the "first match wins" algorithm reduces 25 | the amount of backtracking even further. Nonetheless, exponential-time patterns can occur. 26 | The nodeHits and treeDepth parameters provide upper limits 27 | on the number of unit steps the parser may take and the maximum parse tree depth 28 | that the parser can reach, respectively. 29 |

30 |

31 | Note on "first match wins", also sometimes refered to as "prioritized choice": 32 | Alternates are tried from left to right. The parser accepts the first 33 | successful match and no further alternates are tried. 34 |

35 |

36 |

37 |

Syntax

38 |
 39 | var exp    = new ApgExp(pattern, "", nodeHits, treeDepth);
 40 | var result = exp.exec(input);
 41 | 
42 |

Parameters

43 |

44 |

    45 |
  • nodeHits: integer > 0: default Infinity: 46 | The maximum number of unit steps (parse tree node hits) that the parser is allowed 47 | to take. The parser will throw an Error exception if the limit is exceeded. 48 |
  • 49 |
  • treeDepth: integer > 0: default Infinity: 50 | The maximum allowed parse tree depth. 51 | The parser will throw an Error exception if the limit is exceeded. 52 |
  • 53 |
54 |

55 |

Return

56 |
    57 |
  • exp.nodeHits - The input value of nodeHits.
  • 58 |
  • exp.treeDepth - The input value of treeDepth.
  • 59 |
  • result.nodeHits - The actual number of unit parser steps taken.
  • 60 |
  • result.treeDepth - The actual maximum parse tree depth reached.
  • 61 |
62 |

Examples

63 |

64 | The following exponential pattern, suggested by 65 | Bryan Ford, 66 | is used in the following examples. 67 |

68 |
 69 | var pattern;    
 70 | pattern  = 'S = *A\n';
 71 | pattern += 'A = B / C / "a"\n';
 72 | pattern += 'B = "a" S "b"\n';
 73 | pattern += 'C = "a" S "c"\n';
 74 | 
75 |

Example 1: nodeHits

76 |
 77 | var pattern, str, exp, result;    
 78 | exp = new ApgExp(pattern, "", 100000);
 79 | str = "aaaaa";
 80 | try{
 81 |   for (var i = 0; i < 5; i += 1) {
 82 |     result = exp.exec(str);
 83 |     console.log("input: "+str+" node hits: " + result.nodeHits);
 84 |     str += "a";
 85 |   }
 86 | }catch(e){
 87 |   console.log("EXCEPTION: "+e.message);
 88 | }
 89 | /* result */
 90 | input: aaaaa node hits: 1817
 91 | input: aaaaaa node hits: 5462
 92 | input: aaaaaaa node hits: 16397
 93 | input: aaaaaaaa node hits: 49202
 94 | EXCEPTION: parser: maximum number of node hits exceeded: 100000
 95 | 
96 |

Example 2: treeDepth

97 |
 98 | var pattern, str, exp, result;    
 99 | exp = new ApgExp(pattern, "", null, 50);
100 | str = "aaaaa";
101 | try{
102 |   for (var i = 0; i < 5; i += 1) {
103 |     result = exp.exec(str);
104 |     console.log("input: "+str+" tree depth: " + result.treeDepth);
105 |     str += "a";
106 |   }
107 | }catch(e){
108 |   console.log("EXCEPTION: "+e.message);
109 | }
110 | /* result */
111 | input: aaaaa tree depth: 32
112 | input: aaaaaa tree depth: 38
113 | input: aaaaaaa tree depth: 44
114 | input: aaaaaaaa tree depth: 50
115 | EXCEPTION: parser: maximum parse tree depth exceeded: 50
116 | 
117 |
118 |
119 | 120 |
121 | 122 | 123 | -------------------------------------------------------------------------------- /apg-exp.css: -------------------------------------------------------------------------------- 1 | /* This file automatically generated by toless() and LESS. */ 2 | .apg-mono { 3 | font-family: monospace; 4 | } 5 | .apg-active { 6 | font-weight: bold; 7 | color: #000000; 8 | } 9 | .apg-match { 10 | font-weight: bold; 11 | color: #264BFF; 12 | } 13 | .apg-empty { 14 | font-weight: bold; 15 | color: #0fbd0f; 16 | } 17 | .apg-nomatch { 18 | font-weight: bold; 19 | color: #FF4000; 20 | } 21 | .apg-lh-match { 22 | font-weight: bold; 23 | color: #1A97BA; 24 | } 25 | .apg-lb-match { 26 | font-weight: bold; 27 | color: #5F1687; 28 | } 29 | .apg-remainder { 30 | font-weight: bold; 31 | color: #999999; 32 | } 33 | .apg-ctrl-char { 34 | font-weight: bolder; 35 | font-style: italic; 36 | font-size: .6em; 37 | } 38 | .apg-line-end { 39 | font-weight: bold; 40 | color: #000000; 41 | } 42 | .apg-error { 43 | font-weight: bold; 44 | color: #FF4000; 45 | } 46 | .apg-phrase { 47 | color: #000000; 48 | background-color: #8caae6; 49 | } 50 | .apg-empty-phrase { 51 | color: #0fbd0f; 52 | } 53 | table.apg-state { 54 | font-family: monospace; 55 | margin-top: 5px; 56 | font-size: 11px; 57 | line-height: 130%; 58 | text-align: left; 59 | border: 1px solid black; 60 | border-collapse: collapse; 61 | } 62 | table.apg-state th, 63 | table.apg-state td { 64 | text-align: left; 65 | border: 1px solid black; 66 | border-collapse: collapse; 67 | } 68 | table.apg-state th:nth-last-child(2), 69 | table.apg-state td:nth-last-child(2) { 70 | text-align: right; 71 | } 72 | table.apg-state caption { 73 | font-size: 125%; 74 | line-height: 130%; 75 | font-weight: bold; 76 | text-align: left; 77 | } 78 | table.apg-stats { 79 | font-family: monospace; 80 | margin-top: 5px; 81 | font-size: 11px; 82 | line-height: 130%; 83 | text-align: right; 84 | border: 1px solid black; 85 | border-collapse: collapse; 86 | } 87 | table.apg-stats th, 88 | table.apg-stats td { 89 | text-align: right; 90 | border: 1px solid black; 91 | border-collapse: collapse; 92 | } 93 | table.apg-stats caption { 94 | font-size: 125%; 95 | line-height: 130%; 96 | font-weight: bold; 97 | text-align: left; 98 | } 99 | table.apg-trace { 100 | font-family: monospace; 101 | margin-top: 5px; 102 | font-size: 11px; 103 | line-height: 130%; 104 | text-align: right; 105 | border: 1px solid black; 106 | border-collapse: collapse; 107 | } 108 | table.apg-trace caption { 109 | font-size: 125%; 110 | line-height: 130%; 111 | font-weight: bold; 112 | text-align: left; 113 | } 114 | table.apg-trace th, 115 | table.apg-trace td { 116 | text-align: right; 117 | border: 1px solid black; 118 | border-collapse: collapse; 119 | } 120 | table.apg-trace th:last-child, 121 | table.apg-trace th:nth-last-child(2), 122 | table.apg-trace td:last-child, 123 | table.apg-trace td:nth-last-child(2) { 124 | text-align: left; 125 | } 126 | table.apg-grammar { 127 | font-family: monospace; 128 | margin-top: 5px; 129 | font-size: 11px; 130 | line-height: 130%; 131 | text-align: right; 132 | border: 1px solid black; 133 | border-collapse: collapse; 134 | } 135 | table.apg-grammar caption { 136 | font-size: 125%; 137 | line-height: 130%; 138 | font-weight: bold; 139 | text-align: left; 140 | } 141 | table.apg-grammar th, 142 | table.apg-grammar td { 143 | text-align: right; 144 | border: 1px solid black; 145 | border-collapse: collapse; 146 | } 147 | table.apg-grammar th:last-child, 148 | table.apg-grammar td:last-child { 149 | text-align: left; 150 | } 151 | table.apg-rules { 152 | font-family: monospace; 153 | margin-top: 5px; 154 | font-size: 11px; 155 | line-height: 130%; 156 | text-align: right; 157 | border: 1px solid black; 158 | border-collapse: collapse; 159 | } 160 | table.apg-rules caption { 161 | font-size: 125%; 162 | line-height: 130%; 163 | font-weight: bold; 164 | text-align: left; 165 | } 166 | table.apg-rules th, 167 | table.apg-rules td { 168 | text-align: right; 169 | border: 1px solid black; 170 | border-collapse: collapse; 171 | } 172 | table.apg-rules a { 173 | color: #003399 !important; 174 | } 175 | table.apg-rules a:hover { 176 | color: #8caae6 !important; 177 | } 178 | table.apg-attrs { 179 | font-family: monospace; 180 | margin-top: 5px; 181 | font-size: 11px; 182 | line-height: 130%; 183 | text-align: center; 184 | border: 1px solid black; 185 | border-collapse: collapse; 186 | } 187 | table.apg-attrs caption { 188 | font-size: 125%; 189 | line-height: 130%; 190 | font-weight: bold; 191 | text-align: left; 192 | } 193 | table.apg-attrs th, 194 | table.apg-attrs td { 195 | text-align: center; 196 | border: 1px solid black; 197 | border-collapse: collapse; 198 | } 199 | table.apg-attrs th:nth-child(1), 200 | table.apg-attrs th:nth-child(2), 201 | table.apg-attrs th:nth-child(3) { 202 | text-align: right; 203 | } 204 | table.apg-attrs td:nth-child(1), 205 | table.apg-attrs td:nth-child(2), 206 | table.apg-attrs td:nth-child(3) { 207 | text-align: right; 208 | } 209 | table.apg-attrs a { 210 | color: #003399 !important; 211 | } 212 | table.apg-attrs a:hover { 213 | color: #8caae6 !important; 214 | } 215 | -------------------------------------------------------------------------------- /guide/replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | headerphoto 15 | 16 |
17 | 18 |

Method: replace()

19 |

20 | replace() replaces the matched pattern or patterns in the string with a replacement string. 21 | It works very similarly to JavaScript's 22 | String.replace(regex, string|function). 23 |

24 |

Syntax

25 |
 26 | var exp = new ApgExp(pattern[, flags]);
 27 | var result = exp.replace(str, replacement);
28 |

Parameters

29 |

• str: string: The string to match pattern in.

30 |

• replacement: string/function

31 |
    32 |
  • string: The matched patterns are replaced with this string. 33 | The string can contain the following special characters: 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    PatternInserts
    $$Inserts the character "$".
    $`Inserts the portion of the string preceding the matched pattern.
    $&Inserts the matched pattern.
    $'Inserts the portion of the string following the matched pattern.
    ${name}Inserts the sub-string last matched by rule "name" or undefined if none.
    42 |
  • 43 |
  •  
  • 44 |
  • function: A function with the prototype: 45 |
     46 | var func = function(result, exp){
     47 |   var replacement;
     48 |   /* construct replacement possibly using the values */
     49 |   /* in result and/or exp */
     50 |   return replacement;
     51 | }
     52 | 
    53 |
  • 54 |
55 |

Return

56 |

Returns a copy of str with the matched pattern or patterns replaced with replacement.

57 |

If the flags argument is absent or empty, 58 | only the first match in str is replaced. 59 | If flags is "g" or "y", 60 | all matches in str are replaced. 61 |

62 |

Example 1

63 |

64 | The global flag is set, so all matches are replaced. 65 |

66 |
 67 | var pattern, str, exp, result;
 68 | pattern  = 'pattern = A / X\n';
 69 | pattern += 'A       = "abc"\n';
 70 | pattern += 'X       = "xyz"\n';
 71 | str = "---abc---xyz---ABC---XYZ---";
 72 | exp = new ApgExp(pattern, "g");
 73 | result = exp.replace(str, "555");
 74 | console.log("   str: " + str);
 75 | console.log("result: " + result);
 76 | /* returns */
 77 |    str: ---abc---xyz---ABC---XYZ---
 78 | result: ---555---555---555---555---
 79 | 
80 |

Example 2

81 | The global flag is not set, so only the first match is replaced. 82 | The replacement string contains special characters indicating 83 | a replacement string of "$-|||-$" 84 | since ||| is the prefix ($`) to the match. 85 |

86 |

87 |
 88 | var pattern, str, exp, result;
 89 | pattern  = 'pattern = A / X\n';
 90 | pattern += 'A       = "abc"\n';
 91 | pattern += 'X       = "xyz"\n';
 92 | str = "|||abc---xyz---ABC---XYZ---";
 93 | exp = new ApgExp(pattern, "");
 94 | result = exp.replace(str, "$$-$`-$$");
 95 | console.log("   str: " + str);
 96 | console.log("result: " + result);
 97 | /* returns */
 98 |    str: |||abc---xyz---ABC---XYZ---
 99 | result: |||$-|||-$---xyz---ABC---XYZ---
100 | 
101 |

Example 3

102 |

103 | The replacement function will examine result 104 | and replace with "555" if rule A is matched 105 | or with "666" if rule X is matched. 106 |

107 |
108 | var pattern, str, exp, result;
109 | var rfunc = function(result, exp){
110 |   var str = "???";
111 |   if(result.rules.A){
112 |     str = "555";
113 |   }else if(result.rules.X){
114 |     str = "666";
115 |   }
116 |   return str;
117 | }
118 | pattern  = 'pattern = A / X\n';
119 | pattern += 'A       = "abc"\n';
120 | pattern += 'X       = "xyz"\n';
121 | str = "---abc---xyz---ABC---XYZ---";
122 | exp = new ApgExp(pattern, "g");
123 | result = exp.replace(str, rfunc);
124 | console.log("   str: " + str);
125 | console.log("result: " + result);
126 | /* returns */
127 |    str: ---abc---xyz---ABC---XYZ---
128 | result: ---555---666---555---666---
129 | 
130 |
131 |
132 | 133 |
134 | 135 | 136 | -------------------------------------------------------------------------------- /src/replace.js: -------------------------------------------------------------------------------- 1 | // This module implements the `replace()` function. 2 | "use strict;" 3 | var errorName = "apg-exp: replace(): "; 4 | var parseReplacementString = require("./parse-replacement.js"); 5 | /* replace special replacement patterns, `$&`, etc. */ 6 | var generateReplacementString = function(p, rstr, items) { 7 | var exp = p._this; 8 | if (items.length === 0) { 9 | /* no special characters in the replacement string */ 10 | /* just return a copy of the replacement string */ 11 | return rstr; 12 | } 13 | var replace = rstr.slice(0); 14 | var first, last; 15 | items.reverse(); 16 | items.forEach(function(item) { 17 | first = replace.slice(0, item.index); 18 | last = replace.slice(item.index + item.length); 19 | switch (item.type) { 20 | case "escape": 21 | replace = first.concat("$", last); 22 | break; 23 | case "prefix": 24 | replace = first.concat(exp.leftContext, last); 25 | break; 26 | case "match": 27 | replace = first.concat(exp.lastMatch, last); 28 | break; 29 | case "suffix": 30 | replace = first.concat(exp.rightContext, last); 31 | break; 32 | case "name": 33 | /* If there are multiple matches to this rule name, only the last is used */ 34 | /* If this is a problem, modify the grammar and use different rule names for the different places. */ 35 | var ruleName = p.ruleNames[item.name.nameString]; 36 | replace = first.concat(exp.rules[ruleName], last); 37 | break; 38 | default: 39 | throw new Error(errorName + "generateREplacementString(): unrecognized item type: " + item.type); 40 | } 41 | }); 42 | return replace; 43 | } 44 | /* creates a special object with the apg-exp object's "last match" properites */ 45 | var lastObj = function(exp) { 46 | var obj = {} 47 | obj.ast = exp.ast; 48 | obj.input = exp.input; 49 | obj.leftContext = exp.leftContext; 50 | obj.lastMatch = exp.lastMatch; 51 | obj.rightContext = exp.rightContext; 52 | obj["$_"] = exp.input; 53 | obj["$`"] = exp.leftContext; 54 | obj["$&"] = exp.lastMatch; 55 | obj["$'"] = exp.rightContext; 56 | obj.rules = []; 57 | for (name in exp.rules) { 58 | var el = "${" + name + "}"; 59 | obj[el] = exp[el]; 60 | obj.rules[name] = exp.rules[name]; 61 | } 62 | return obj; 63 | } 64 | /* call the user's replacement function for a single pattern match */ 65 | var singleReplaceFunction = function(p, ostr, func) { 66 | var result = p._this.exec(ostr); 67 | if (result === null) { 68 | return ostr; 69 | } 70 | rstr = func(result, lastObj(p._this)); 71 | var ret = (p._this.leftContext).concat(rstr, p._this.rightContext); 72 | return ret; 73 | } 74 | /* call the user's replacement function to replace all pattern matches */ 75 | var globalReplaceFunction = function(p, ostr, func) { 76 | var exp = p._this; 77 | var retstr = ostr.slice(0); 78 | while (true) { 79 | var result = exp.exec(retstr); 80 | if (result === null) { 81 | break; 82 | } 83 | var newrstr = func(result, lastObj(exp)); 84 | retstr = (exp.leftContext).concat(newrstr, exp.rightContext); 85 | exp.lastIndex = exp.leftContext.length + newrstr.length; 86 | if (result[0].length === 0) { 87 | /* an empty string IS a match and is replaced */ 88 | /* but use "bump-along" mode to prevent infinite loop */ 89 | exp.lastIndex += 1; 90 | } 91 | } 92 | return retstr; 93 | } 94 | /* do a single replacement with the caller's replacement string */ 95 | var singleReplaceString = function(p, ostr, rstr) { 96 | var exp = p._this; 97 | var result = exp.exec(ostr); 98 | if (result === null) { 99 | return ostr; 100 | } 101 | var ritems = parseReplacementString(p, rstr); 102 | rstr = generateReplacementString(p, rstr, ritems); 103 | var ret = (exp.leftContext).concat(rstr, exp.rightContext); 104 | return ret; 105 | } 106 | /* do a global replacement of all matches with the caller's replacement string */ 107 | var globalReplaceString = function(p, ostr, rstr) { 108 | var exp = p._this; 109 | var retstr = ostr.slice(0); 110 | var ritems = null; 111 | while (true) { 112 | var result = exp.exec(retstr); 113 | if (result == null) { 114 | break; 115 | } 116 | if (ritems === null) { 117 | ritems = parseReplacementString(p, rstr); 118 | } 119 | var newrstr = generateReplacementString(p, rstr, ritems); 120 | retstr = (exp.leftContext).concat(newrstr, exp.rightContext); 121 | exp.lastIndex = exp.leftContext.length + newrstr.length; 122 | if (result[0].length === 0) { 123 | /* an empty string IS a match and is replaced */ 124 | /* but use "bump-along" mode to prevent infinite loop */ 125 | exp.lastIndex += 1; 126 | } 127 | } 128 | return retstr; 129 | } 130 | /* the replace() function calls this to replace the matched patterns with a string */ 131 | exports.replaceString = function(p, str, replacement) { 132 | if (p._this.global || p._this.sticky) { 133 | return globalReplaceString(p, str, replacement); 134 | } else { 135 | return singleReplaceString(p, str, replacement); 136 | } 137 | } 138 | /* the replace() function calls this to replace the matched patterns with a function */ 139 | exports.replaceFunction = function(p, str, func) { 140 | if (p._this.global || p._this.sticky) { 141 | return globalReplaceFunction(p, str, func); 142 | } else { 143 | return singleReplaceFunction(p, str, func); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /guide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | headerphoto 14 | 15 |
16 |

ApgExp Constructor

17 |

18 |

19 |

20 | apg-exp is a pattern-matching engine designed to have the look 21 | and feel of JavaScript's RegExp but to use the 22 | SABNF syntax 23 | (a superset of ABNF) 24 | for pattern definitions. 25 |

26 |

27 | apg-exp uses APG as the underlying parser generator. 28 | For usage of APG refer to the GitHub repository 29 | and documentation. 30 | A large number of examples of APG usage can be found in the examples 31 | repository and 32 | documentation. 33 |

34 |

35 | For pattern syntax, refer to the SABNF guide. 36 |

37 |

38 | The apg-exp constructor can be acquired from GitHub or 39 | npm. 40 | In a node.js project use: 41 |

npm install apg-exp --save
 42 | var ApgExp = require("apg-exp");
43 |

44 |

45 | To acquire the pre-defined constructor variable ApgExp in a web page use: 46 |

git clone https://github.com/ldthomas/apg-js2-exp.git repo
 47 | <script src="./repo/apgexp.js" charset="utf-8"></script>
 48 | /* or for the minimized version */  
 49 | <script src="./repo/apgexp-min.js" charset="utf-8"></script>
50 |

51 |

52 | In either case, it will be assumed throughout the remainder of this guide that the apg-exp constructor 53 | is available in the user's code as ApgExp. 54 |

55 |

Syntax

56 |

57 |

var exp = new ApgExp(pattern[, flags[, nodeHits[, treeDepth]]])
58 |

59 |

Parameters

60 |

61 |

    62 |
  • 63 | pattern: string or object 64 |
  • 65 |
      66 |
    • string - a valid SABNF pattern syntax
    • 67 |
    • object - an instantiated APG parser object
    • 68 |
    69 |
  • 70 | flags: string, any of the characters "gyud" 71 |
  • 72 |
      73 |
    • default - Sets lastIndex to zero after each match attempt.
    • 74 |
    • g - global Advances lastIndex after each successful match. 75 |
    • 76 |
    • y - sticky Anchors the search to lastIndex. 77 | Advances lastIndex on each successful match.
    • 78 |
    • u - unicode Returns matched patterns as integer arrays of character codes, rather than strings
    • 79 |
    • d - debug Adds the APG trace object to exp.
    • 80 |
    81 |
      82 |
    83 |
  • 84 | nodeHits: integer > 0: default: Infinity 85 |
  • 86 |
      87 |
    • Constrains the maximum number of parser steps taken to nodeHits. 88 | Can be used to protect against "exponential-time" or "catestrophic-backtracking" pattern syntaxes.
    • 89 |
    90 |
  • 91 | treeDepth: integer > 0: default: Infinity 92 |
  • 93 |
      94 |
    • Constrains the maximum parse tree depth to treeDepth. 95 | Can be used to protect against "exponential-time" or "catestrophic-backtracking" pattern syntaxes.
    • 96 |
    97 |
98 |

99 |

Return

100 |

101 | Returns the instantiated apg-exp object. 102 |

103 |

104 | An ApgExpError exception is thrown on any encountered error. 105 |

106 |

Examples

107 |

108 | Pattern syntax as a string: 109 |

var exp = new ApgExp('pattern = "abc"\n');
110 | var result = exp.test("abc"); /* result -> true */
111 |     result = exp.test("xyz"); /* result -> false */
112 |

113 |

114 | Pattern syntax as an object: 115 |

/* example.bnf file */
116 | pattern = "abc"\n
117 | /* generate APG parser */
118 | npm install apg -g
119 | apg -in example.bnf -js example.js
120 | /* application */    
121 | var pattern = require("./example.js");
122 | var obj = new pattern();    
123 | var exp = new ApgExp(obj);
124 | var result = exp.test("abc"); /* result -> true */
125 |     result = exp.test("xyz"); /* result -> false */
126 |

127 |
128 |
129 | 130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /src/replace-grammar.js: -------------------------------------------------------------------------------- 1 | // Generated by JavaScript APG, Version [`apg-js2`](https://github.com/ldthomas/apg-js2) 2 | module.exports = function(){ 3 | "use strict"; 4 | //``` 5 | // SUMMARY 6 | // rules = 11 7 | // udts = 0 8 | // opcodes = 39 9 | // --- ABNF original opcodes 10 | // ALT = 4 11 | // CAT = 4 12 | // REP = 4 13 | // RNM = 12 14 | // TLS = 7 15 | // TBS = 2 16 | // TRG = 6 17 | // --- SABNF superset opcodes 18 | // UDT = 0 19 | // AND = 0 20 | // NOT = 0 21 | // BKA = 0 22 | // BKN = 0 23 | // BKR = 0 24 | // ABG = 0 25 | // AEN = 0 26 | // characters = [10 - 65535] 27 | //``` 28 | /* OBJECT IDENTIFIER (for internal parser use) */ 29 | this.grammarObject = 'grammarObject'; 30 | 31 | /* RULES */ 32 | this.rules = []; 33 | this.rules[0] = {name: 'rule', lower: 'rule', index: 0, isBkr: false}; 34 | this.rules[1] = {name: 'error', lower: 'error', index: 1, isBkr: false}; 35 | this.rules[2] = {name: 'escape', lower: 'escape', index: 2, isBkr: false}; 36 | this.rules[3] = {name: 'match', lower: 'match', index: 3, isBkr: false}; 37 | this.rules[4] = {name: 'prefix', lower: 'prefix', index: 4, isBkr: false}; 38 | this.rules[5] = {name: 'suffix', lower: 'suffix', index: 5, isBkr: false}; 39 | this.rules[6] = {name: 'xname', lower: 'xname', index: 6, isBkr: false}; 40 | this.rules[7] = {name: 'name', lower: 'name', index: 7, isBkr: false}; 41 | this.rules[8] = {name: 'alpha', lower: 'alpha', index: 8, isBkr: false}; 42 | this.rules[9] = {name: 'digit', lower: 'digit', index: 9, isBkr: false}; 43 | this.rules[10] = {name: 'any-other', lower: 'any-other', index: 10, isBkr: false}; 44 | 45 | /* UDTS */ 46 | this.udts = []; 47 | 48 | /* OPCODES */ 49 | /* rule */ 50 | this.rules[0].opcodes = []; 51 | this.rules[0].opcodes[0] = {type: 3, min: 0, max: Infinity};// REP 52 | this.rules[0].opcodes[1] = {type: 2, children: [2,4]};// CAT 53 | this.rules[0].opcodes[2] = {type: 3, min: 0, max: Infinity};// REP 54 | this.rules[0].opcodes[3] = {type: 4, index: 10};// RNM(any-other) 55 | this.rules[0].opcodes[4] = {type: 3, min: 0, max: 1};// REP 56 | this.rules[0].opcodes[5] = {type: 1, children: [6,7,8,9,10,11]};// ALT 57 | this.rules[0].opcodes[6] = {type: 4, index: 2};// RNM(escape) 58 | this.rules[0].opcodes[7] = {type: 4, index: 3};// RNM(match) 59 | this.rules[0].opcodes[8] = {type: 4, index: 4};// RNM(prefix) 60 | this.rules[0].opcodes[9] = {type: 4, index: 5};// RNM(suffix) 61 | this.rules[0].opcodes[10] = {type: 4, index: 6};// RNM(xname) 62 | this.rules[0].opcodes[11] = {type: 4, index: 1};// RNM(error) 63 | 64 | /* error */ 65 | this.rules[1].opcodes = []; 66 | this.rules[1].opcodes[0] = {type: 2, children: [1,2]};// CAT 67 | this.rules[1].opcodes[1] = {type: 7, string: [36]};// TLS 68 | this.rules[1].opcodes[2] = {type: 4, index: 10};// RNM(any-other) 69 | 70 | /* escape */ 71 | this.rules[2].opcodes = []; 72 | this.rules[2].opcodes[0] = {type: 7, string: [36,36]};// TLS 73 | 74 | /* match */ 75 | this.rules[3].opcodes = []; 76 | this.rules[3].opcodes[0] = {type: 7, string: [36,38]};// TLS 77 | 78 | /* prefix */ 79 | this.rules[4].opcodes = []; 80 | this.rules[4].opcodes[0] = {type: 7, string: [36,96]};// TLS 81 | 82 | /* suffix */ 83 | this.rules[5].opcodes = []; 84 | this.rules[5].opcodes[0] = {type: 7, string: [36,39]};// TLS 85 | 86 | /* xname */ 87 | this.rules[6].opcodes = []; 88 | this.rules[6].opcodes[0] = {type: 2, children: [1,2,3]};// CAT 89 | this.rules[6].opcodes[1] = {type: 7, string: [36,123]};// TLS 90 | this.rules[6].opcodes[2] = {type: 4, index: 7};// RNM(name) 91 | this.rules[6].opcodes[3] = {type: 7, string: [125]};// TLS 92 | 93 | /* name */ 94 | this.rules[7].opcodes = []; 95 | this.rules[7].opcodes[0] = {type: 2, children: [1,2]};// CAT 96 | this.rules[7].opcodes[1] = {type: 4, index: 8};// RNM(alpha) 97 | this.rules[7].opcodes[2] = {type: 3, min: 0, max: Infinity};// REP 98 | this.rules[7].opcodes[3] = {type: 1, children: [4,5,6,7]};// ALT 99 | this.rules[7].opcodes[4] = {type: 4, index: 8};// RNM(alpha) 100 | this.rules[7].opcodes[5] = {type: 4, index: 9};// RNM(digit) 101 | this.rules[7].opcodes[6] = {type: 6, string: [45]};// TBS 102 | this.rules[7].opcodes[7] = {type: 6, string: [95]};// TBS 103 | 104 | /* alpha */ 105 | this.rules[8].opcodes = []; 106 | this.rules[8].opcodes[0] = {type: 1, children: [1,2]};// ALT 107 | this.rules[8].opcodes[1] = {type: 5, min: 97, max: 122};// TRG 108 | this.rules[8].opcodes[2] = {type: 5, min: 65, max: 90};// TRG 109 | 110 | /* digit */ 111 | this.rules[9].opcodes = []; 112 | this.rules[9].opcodes[0] = {type: 5, min: 48, max: 57};// TRG 113 | 114 | /* any-other */ 115 | this.rules[10].opcodes = []; 116 | this.rules[10].opcodes[0] = {type: 1, children: [1,2,3]};// ALT 117 | this.rules[10].opcodes[1] = {type: 5, min: 32, max: 35};// TRG 118 | this.rules[10].opcodes[2] = {type: 5, min: 37, max: 65535};// TRG 119 | this.rules[10].opcodes[3] = {type: 5, min: 10, max: 13};// TRG 120 | 121 | // The `toString()` function will display the original grammar file(s) that produced these opcodes. 122 | this.toString = function(){ 123 | var str = ""; 124 | str += ";\n"; 125 | str += "; SABNF grammar for parsing out the replacement string parameters\n"; 126 | str += ";\n"; 127 | str += "rule = *(*any-other [(escape / match / prefix/ suffix/ xname / error)])\n"; 128 | str += "error = \"$\" any-other\n"; 129 | str += "escape = \"$$\"\n"; 130 | str += "match = \"$&\"\n"; 131 | str += "prefix = \"$`\"\n"; 132 | str += "suffix = \"$'\"\n"; 133 | str += "xname = \"${\" name \"}\"\n"; 134 | str += "name = alpha *(alpha/digit/%d45/%d95)\n"; 135 | str += "alpha = %d97-122 / %d65-90\n"; 136 | str += "digit = %d48-57\n"; 137 | str += "any-other = %x20-23 / %x25-FFFF / %xA-D\n"; 138 | return str; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /guide/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | headerphoto 15 | 16 |
17 |

The result Object

18 |

19 | The result object contains the phrase matched by the pattern in the input string along with other information. 20 | It has the location in the string where the match was found and parsing information. 21 | It also has the location and phrase matched for all matches to all pattern syntax rules. 22 |

23 |

Syntax

24 |
 25 | var exp    = new ApgExp(pattern[, flags]);
 26 | var result = exp.exec(input);
27 |

Properties

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 |
[0]string/arraythe matched phrase
inputstring/arraya copy of the input string
indexintegerthe index or offset from the beginning of the input string to the matched phrase
lengthintegerthe length of the matched phrase
treeDepthintegerthe actual maximum depth reached in the parse tree by the parser during the match
nodeHitsintegerthe actual number of unit steps required by the parser to make the match
rulesobjectAn object with phrase information for each of the named rules defined in the pattern syntax. 36 | The first rule in the pattern defines the entire phrase to be matched. 37 | The property, result[0], is just an alias for the first-named phrase.
rules[name]null if no match to rule "name" was found
rules[name]an array of phrase objects, one item for each phrase
rules[name][0]{phrase: string/array, index: integer}
42 |

If Unicode mode is true, i.e. flags = "u", phrase is an array 43 | of integer character codes, otherwise it is a string.

44 |

Methods

45 |
    46 |
  • toText()
  • 47 |
      48 |
    • displays the result object in text format, suitable for output with console.log()
    • 49 |
    50 |
  • toHtml()
  • 51 |
      52 |
    • displays the result object in HTML format, suitable for display on a web page
    • 53 |
    54 |
  • toHtmlPage()
  • 55 |
      56 |
    • displays the result object in HTML format as a complete, stand-alone web page
    • 57 |
    58 |
59 |

Example 1

60 |

The following set up is used for all three examples. Example 1 is figurative. 61 | It illustrates the format of the rules object.

62 |
 63 | var pattern, str, exp, result;
 64 | pattern  = 'word  = 1*(alpha / num)\n';
 65 | pattern += 'alpha = %d65-90 / %d97-122\n';
 66 | pattern += 'num   = %d48-57\n';
 67 | str = "ab12";
 68 | exp = new ApgExp(pattern);
 69 | result = exp.exec(str);
 70 | /* result */
 71 | 
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
itemvalue
[0]ab12
inputab12
index0
length4
tree depth6
node hits26
rules["word"][0]{phrase: "ab12", index: 0}
rules["alpha"][0]{phrase: "a", index: 0}
rules["alpha"][1]{phrase: "b", index: 1}
rules["num"][0]{phrase: "1", index: 2}
rules["num"][1]{phrase: "2", index: 3}
86 |

Example 2

87 |

Example 2 shows the same result object as example 1, 88 | but shows how it is actually displayed with the toText() function.

89 |
 90 | console.log(result.toText());
 91 | /* result */
 92 |     result:
 93 |        [0]: ab12
 94 |      input: ab12
 95 |      index: 0
 96 |     length: 4
 97 | tree depth: 7
 98 |  node hits: 26
 99 |      rules: word : 0: ab12
100 |           : alpha : 0: a
101 |           : alpha : 1: b
102 |           : num : 2: 1
103 |           : num : 3: 2
104 | 
105 |

Example 3

106 |

Example 3 shows the same result object as example 1, 107 | but shows how it is actually displayed with the toHtml() function. 108 | The toHtmlPage() function would give the same output 109 | but wrapped in a complete HTML page header and footer.

110 |
111 | $("#this-page").html(result.toHtml());
112 | /* result */
113 | 
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
result:
itemvaluephrase
[0]0ab12
input0ab12
index0
length4
tree depth7
node hits26
rulesindexphrase
word0ab12
alpha0a
alpha1b
num21
num32
130 |

 

131 |
132 |
133 | 134 |
135 | 136 | 137 | -------------------------------------------------------------------------------- /guide/debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apg-exp 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | headerphoto 15 | 16 |
17 | 18 |

Debug Mode

19 |

Syntax

20 |
 21 | var exp    = new ApgExp(pattern, "d");
 22 | 
23 |

Debug mode exposes the APG trace object as 24 | exp.trace. It can be used to display the path of the parser through the parse tree. 25 | If the debug property is false, exp.trace is undefined. 26 |

27 |

Example

28 |

29 |

 30 | var pattern, str, exp, result;    
 31 | pattern  = 'word  = alpha *(alpha / num)\n';
 32 | pattern += 'alpha = %d65-90 / %d97-122\n';
 33 | pattern += 'num   = %d48-57\n';
 34 | exp = new ApgExp(pattern, "yd");
 35 | str = "ab12";
 36 | result = exp.exec(str);
 37 | if(result){
 38 |   var htmlPage = exp.trace.toHtmlPage();
 39 |   /* display as HTML page */
 40 |   var html = exp.trace.toHtml();
 41 |   /* display in HTML page */
 42 |   console.log(html);
 43 | }
 44 | /* returns */
 45 | 
46 |

JavaScript APG Trace

47 |

    display mode: ASCII

48 |
    Thu May 19 2016 14:06:20 GMT-0400 (EDT)
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
(a)(b)(c)(d)(e)(f)operatorphrase
013000↓ RNM(word) ab12
12001↓ .RNM(alpha) ab12
21011↑M.RNM(alpha) ab12
34101↓ .RNM(alpha) b12
43111↑M.RNM(alpha) b12
56201↓ .RNM(alpha) 12
65201↑N.RNM(alpha) 12
78201↓ .RNM(num) 12
87211↑M.RNM(num) 12
910301↓ .RNM(alpha) 2
109301↑N.RNM(alpha) 2
1112301↓ .RNM(num) 2
1211311↑M.RNM(num) 2
130040↑MRNM(word) ab12
(a)(b)(c)(d)(e)(f)operatorphrase
67 | 68 |

legend:
69 | (a) - line number
70 | (b) - matching line number
71 | (c) - phrase offset
72 | (d) - phrase length
73 | (e) - tree depth
74 | (f) - operator state
75 |     -   phrase opened
76 |     - ↑M phrase matched
77 |     - ↑E empty phrase matched
78 |     - ↑N phrase not matched
79 | operator - ALT, CAT, REP, RNM, TRG, TLS, TBS, UDT, AND, NOT, BKA, BKN, BKR, ABG, AEN
80 | phrase   - up to 80 characters of the phrase being matched
81 |          - matched characters
82 |          - matched characters in look ahead mode
83 |          - matched characters in look behind mode
84 |          - remainder characters(not yet examined by parser)
85 |          - control characters, TAB, LF, CR, etc. (ASCII mode only)
86 |          - 𝜺 empty string
87 |          -  end of input string
88 |          -  input string display truncated
89 |

90 |

91 | original ABNF operators:
92 | ALT - alternation
93 | CAT - concatenation
94 | REP - repetition
95 | RNM - rule name
96 | TRG - terminal range
97 | TLS - terminal literal string (case insensitive)
98 | TBS - terminal binary string (case sensitive)
99 |
100 | super set SABNF operators:
101 | UDT - user-defined terminal
102 | AND - positive look ahead
103 | NOT - negative look ahead
104 | BKA - positive look behind
105 | BKN - negative look behind
106 | BKR - back reference
107 | ABG - anchor - begin of input string
108 | AEN - anchor - end of input string
109 |

110 |
111 |
112 | 113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /src/exec.js: -------------------------------------------------------------------------------- 1 | // This module implements the `exec()` function. 2 | "use strict;" 3 | var funcs = require('./result.js'); 4 | /* turns on or off the read-only attribute of the `last result` properties of the object */ 5 | var setProperties = function(p, readonly) { 6 | readonly = (readonly === true) ? true : false; 7 | var exp = p._this; 8 | var prop = { 9 | writable : readonly, 10 | enumerable : false, 11 | configurable : true 12 | }; 13 | Object.defineProperty(exp, "input", prop); 14 | Object.defineProperty(exp, "leftContext", prop); 15 | Object.defineProperty(exp, "lastMatch", prop); 16 | Object.defineProperty(exp, "rightContext", prop); 17 | Object.defineProperty(exp, "$_", prop); 18 | Object.defineProperty(exp, "$`", prop); 19 | Object.defineProperty(exp, "$&", prop); 20 | Object.defineProperty(exp, "$'", prop); 21 | prop.enumerable = true; 22 | Object.defineProperty(exp, "rules", prop); 23 | if (!exp.rules) { 24 | exp.rules = []; 25 | } 26 | for ( var name in exp.rules) { 27 | var des = "${" + name + "}"; 28 | Object.defineProperty(exp, des, prop); 29 | Object.defineProperty(exp.rules, name, prop); 30 | } 31 | } 32 | /* generate the results object for JavaScript strings */ 33 | var sResult = function(p) { 34 | var chars = p.chars; 35 | var result = p.result; 36 | var ret = { 37 | index : result.index, 38 | length : result.length, 39 | input : p.charsToString(chars, 0), 40 | treeDepth : result.treeDepth, 41 | nodeHits : result.nodeHits, 42 | rules : [], 43 | toText : function() { 44 | return funcs.s.resultToText(this); 45 | }, 46 | toHtml : function() { 47 | return funcs.s.resultToHtml(this); 48 | }, 49 | toHtmlPage : function() { 50 | return funcs.s.resultToHtmlPage(this); 51 | } 52 | } 53 | ret[0] = p.charsToString(p.chars, result.index, result.length); 54 | /* each rule is either 'undefined' or an array of phrases */ 55 | for ( var name in result.rules) { 56 | var rule = result.rules[name]; 57 | if (rule) { 58 | ret.rules[name] = []; 59 | for (var i = 0; i < rule.length; i += 1) { 60 | ret.rules[name][i] = { 61 | index : rule[i].index, 62 | phrase : p.charsToString(chars, rule[i].index, rule[i].length) 63 | }; 64 | } 65 | } else { 66 | ret.rules[name] = undefined; 67 | } 68 | } 69 | return ret; 70 | } 71 | /* generate the results object for integer arrays of character codes */ 72 | var uResult = function(p) { 73 | var chars = p.chars; 74 | var result = p.result; 75 | var beg, end; 76 | var ret = { 77 | index : result.index, 78 | length : result.length, 79 | input : chars.slice(0), 80 | treeDepth : result.treeDepth, 81 | nodeHits : result.nodeHits, 82 | rules : [], 83 | toText : function(mode) { 84 | return funcs.u.resultToText(this, mode); 85 | }, 86 | toHtml : function(mode) { 87 | return funcs.u.resultToHtml(this, mode); 88 | }, 89 | toHtmlPage : function(mode) { 90 | return funcs.u.resultToHtmlPage(this, mode); 91 | } 92 | } 93 | beg = result.index; 94 | end = beg + result.length; 95 | ret[0] = chars.slice(beg, end); 96 | /* each rule is either 'undefined' or an array of phrases */ 97 | for ( var name in result.rules) { 98 | var rule = result.rules[name]; 99 | if (rule) { 100 | ret.rules[name] = []; 101 | for (var i = 0; i < rule.length; i += 1) { 102 | beg = rule[i].index; 103 | end = beg + rule[i].length; 104 | ret.rules[name][i] = { 105 | index : beg, 106 | phrase : chars.slice(beg, end) 107 | }; 108 | } 109 | } else { 110 | ret.rules[name] = undefined; 111 | } 112 | } 113 | return ret; 114 | } 115 | /* generate the apg-exp properties or "last match" object for JavaScript strings */ 116 | var sLastMatch = function(p, result) { 117 | var exp = p._this; 118 | var temp; 119 | exp.lastMatch = result[0]; 120 | temp = p.chars.slice(0, result.index); 121 | exp.leftContext = p.charsToString(temp); 122 | temp = p.chars.slice(result.index + result.length); 123 | exp.rightContext = p.charsToString(temp); 124 | exp["input"] = result.input.slice(0); 125 | exp["$_"] = exp["input"]; 126 | exp["$&"] = exp.lastMatch; 127 | exp["$`"] = exp.leftContext; 128 | exp["$'"] = exp.rightContext; 129 | exp.rules = {}; 130 | for ( var name in result.rules) { 131 | var rule = result.rules[name]; 132 | if (rule) { 133 | exp.rules[name] = rule[rule.length - 1].phrase; 134 | } else { 135 | exp.rules[name] = undefined; 136 | } 137 | exp["${" + name + "}"] = exp.rules[name]; 138 | } 139 | } 140 | /* generate the apg-exp properties or "last match" object for integer arrays of character codes */ 141 | var uLastMatch = function(p, result) { 142 | var exp = p._this; 143 | var chars = p.chars; 144 | var beg, end; 145 | beg = 0; 146 | end = beg + result.index; 147 | exp.leftContext = chars.slice(beg, end); 148 | exp.lastMatch = result[0].slice(0); 149 | beg = result.index + result.length; 150 | exp.rightContext = chars.slice(beg); 151 | exp["input"] = result.input.slice(0); 152 | exp["$_"] = exp["input"]; 153 | exp["$&"] = exp.lastMatch; 154 | exp["$`"] = exp.leftContext; 155 | exp["$'"] = exp.rightContext; 156 | exp.rules = {}; 157 | for ( var name in result.rules) { 158 | var rule = result.rules[name]; 159 | if (rule) { 160 | exp.rules[name] = rule[rule.length - 1].phrase; 161 | } else { 162 | exp.rules[name] = undefined; 163 | } 164 | exp["${" + name + "}"] = exp.rules[name]; 165 | } 166 | } 167 | /* set the returned result properties, and the `last result` properties of the object */ 168 | var setResult = function(p, parserResult) { 169 | var result; 170 | p.result = { 171 | index : parserResult.index, 172 | length : parserResult.length, 173 | treeDepth : parserResult.treeDepth, 174 | nodeHits : parserResult.nodeHits, 175 | rules : [] 176 | } 177 | /* set result in APG phrases {phraseIndex, phraseLength} */ 178 | /* p.ruleNames are all names in the grammar */ 179 | /* p._this.ast.callbacks[name] only defined for 'included' rule names */ 180 | var obj = p.parser.ast.phrases(); 181 | for ( var name in p._this.ast.callbacks) { 182 | var cap = p.ruleNames[name]; 183 | if (p._this.ast.callbacks[name]) { 184 | var cap = p.ruleNames[name]; 185 | if (Array.isArray(obj[cap])) { 186 | p.result.rules[cap] = obj[cap]; 187 | } else { 188 | p.result.rules[cap] = undefined; 189 | } 190 | } 191 | } 192 | /* p.result now has everything we need to know about the result of exec() */ 193 | /* generate the Unicode or JavaScript string version of the result & last match objects */ 194 | setProperties(p, true); 195 | if (p._this.unicode) { 196 | result = uResult(p); 197 | uLastMatch(p, result); 198 | } else { 199 | result = sResult(p); 200 | sLastMatch(p, result); 201 | } 202 | setProperties(p, false); 203 | return result; 204 | } 205 | 206 | /* create an unsuccessful parser result object */ 207 | var resultInit = function() { 208 | return { 209 | success : false 210 | }; 211 | } 212 | 213 | /* create a successful parser result object */ 214 | var resultSuccess = function(index, parserResult) { 215 | return { 216 | success : true, 217 | index : index, 218 | length : parserResult.matched, 219 | treeDepth : parserResult.maxTreeDepth, 220 | nodeHits : parserResult.nodeHits 221 | }; 222 | } 223 | /* search forward from `lastIndex` until a match is found or the end of string is reached */ 224 | var forward = function(p) { 225 | var result = resultInit(); 226 | for (var i = p._this.lastIndex; i < p.chars.length; i += 1) { 227 | var re = p.parser.parseSubstring(p.grammarObject, 0, p.chars, i, p.chars.length - i); 228 | if (p.match(re.state)) { 229 | result = resultSuccess(i, re); 230 | break; 231 | } 232 | } 233 | return result; 234 | } 235 | /* reset lastIndex after a search */ 236 | var setLastIndex = function(lastIndex, flag, parserResult) { 237 | if (flag) { 238 | if (parserResult.success) { 239 | var ret = parserResult.index; 240 | /* bump-along mode - increment is never zero */ 241 | ret += (parserResult.length > 0) ? parserResult.length : 1; 242 | return ret; 243 | } 244 | return 0; 245 | } 246 | return lastIndex; 247 | } 248 | /* attempt a match at lastIndex only - does look further if a match is not found */ 249 | var anchor = function(p) { 250 | var result = resultInit(); 251 | if (p._this.lastIndex < p.chars.length) { 252 | var re = p.parser.parseSubstring(p.grammarObject, 0, p.chars, p._this.lastIndex, p.chars.length - p._this.lastIndex); 253 | if (p.match(re.state)) { 254 | result = resultSuccess(p._this.lastIndex, re); 255 | } 256 | } 257 | return result; 258 | } 259 | /* called by exec() for a forward search */ 260 | exports.execForward = function(p) { 261 | var parserResult = forward(p); 262 | var result = null; 263 | if (parserResult.success) { 264 | result = setResult(p, parserResult); 265 | } 266 | p._this.lastIndex = setLastIndex(p._this.lastIndex, p._this.global, parserResult); 267 | return result; 268 | } 269 | /* called by exec() for an anchored search */ 270 | exports.execAnchor = function(p) { 271 | var parserResult = anchor(p); 272 | var result = null; 273 | if (parserResult.success) { 274 | result = setResult(p, parserResult); 275 | } 276 | p._this.lastIndex = setLastIndex(p._this.lastIndex, p._this.sticky, parserResult); 277 | return result; 278 | } 279 | /* search forward from lastIndex looking for a match */ 280 | exports.testForward = function(p) { 281 | var parserResult = forward(p); 282 | p._this.lastIndex = setLastIndex(p._this.lastIndex, p._this.global, parserResult); 283 | return parserResult.success; 284 | } 285 | /* test for a match at lastIndex only, do not look further if no match is found */ 286 | exports.testAnchor = function(p) { 287 | var parserResult = anchor(p); 288 | p._this.lastIndex = setLastIndex(p._this.lastIndex, p._this.sticky, parserResult); 289 | return parserResult.success; 290 | } 291 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apg-exp - APG Expressions 2 | 3 | > _**Deprecated** Use the updated version [**apg-js**](https://github.com/ldthomas/apg-js) instead._ 4 | 5 | **apg-exp** is a regex-like pattern-matching engine that uses a superset of the [ABNF syntax](https://tools.ietf.org/html/rfc5234) for the pattern definitions and [**APG**](https://github.com/ldthomas/apg-js2) to create and apply the pattern-matching parser. 6 | 7 | **Tutorial:** Don't miss the [tutorial on sitepoint.com](https://www.sitepoint.com/alternative-to-regular-expressions/). 8 | It will walk you through the basics from simple to some fairly sophisticated pattern matching of nested, paired parentheses and other brackets. (Something you can't do with RegExp.) It's all laid out for you with nine (9), hands-on, [CodePen](http://codepen.io/) examples. 9 | 10 | **Complete User's Guide:** A complete user's guide can be found at `./guide/index.html` 11 | or the [**APG** website](https://sabnf.com/docs/apg-exp-guide/index.html). 12 | 13 | **v2.1.0 release notes:** There are no functional changes in version 2.1.0. 14 | Its dependency on **apg** has been modified to depend instead on the new **apg** API, 15 | [**apg-api**](https://github.com/ldthomas/apg-js2-api). 16 | This removes all dependency on the node.js file system module "fs". 17 | Some development frameworks are incompatible with "fs". 18 | 19 | **apg-exp:** By way of introduction, the [regex Wikipedia article](https://en.wikipedia.org/wiki/Regular_expression) would be a good start and Jeffrey Friedl's book, [_Mastering Regular Expressions_](http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124) would be a lot better and more complete. This introduction will just mention features, a little on motivation and try to point out some possible advantages to **apg-exp**. 20 | 21 | **Features:** 22 | 23 |
    24 |
  1. 25 | The pattern syntax is a superset of ABNF (SABNF.) The ABNF syntax is standardized for and used to describe most Internet technical specifications. 26 |
  2. 27 |
  3. 28 | APG provides error checking and analysis for easy development of an accurate syntax for the desired pattern. 29 |
  4. 30 |
  5. 31 | Pattern syntax may be input as SABNF text or as an instantiated, APG parser object. 32 |
  6. 33 |
  7. 34 | Gives the user complete control over the pattern's character codes and their interpretation. 35 |
  8. 36 |
  9. 37 | Easy access to the full UTF-32 range of Unicode is provided naturally through the integer arrays that make up the character-coded strings and phrases. 38 |
  10. 39 |
  11. 40 | Results provide named access to all matched sub-phrases and the indexes where they were found, not just the last matched. 41 |
  12. 42 |
  13. 43 | Results can be returned as JavaScript strings or raw integer arrays of character codes. 44 |
  14. 45 |
  15. 46 | Global and "sticky" flags operate nearly identically to the same-named JavaScript RegExp flags. 47 |
  16. 48 |
  17. 49 | Recursive patterns are natural to the SABNF syntax for easy pair matching of opening and closing parentheses, brackets, HTML tags, etc. 50 |
  18. 51 |
  19. 52 | Fully implemented lookaround – positive and negative forms of both look-ahead and infinite-length look-behind. 53 |
  20. 54 |
  21. 55 | Back referencing – two modes, universal and parent. See the definitions in the 56 | SABNF documentation. 57 | For example, parent mode used with recursion can match not only the opening and closing tags of HTML but also the tag names in them. (See the back reference example.) 58 |
  22. 59 |
  23. 60 | Word and line boundaries are not pre-defined. By making them user-defined they are very flexible but nonetheless very easy to define and use. The user does not have to rely on or guess about what the engine considers a boundary to be. 61 |
  24. 62 |
  25. 63 | Character classes such as \w, \s and . are not pre-defined, providing greater flexibility and certainty to the meaning of any needed character classes. 64 |
  26. 65 |
  27. 66 | The syntax allows APG's User-Defined Terminals (UDTs) – write your own code for special phrase matching requirements. They make the phrase matching power of apg-exp essentially Turing complete. 67 |
  28. 68 |
  29. 69 | Provides the user with access to the Abstract Syntax Tree (AST) of the pattern match. The AST can be used for complex translations of the matched phrase. 70 | (See the dangling-else example.) 71 |
  30. 72 |
  31. 73 | Provides the user with access to APG's trace object which gives a complete, step-by-step picture of the parser's matching process for debugging purposes. 74 |
  32. 75 |
  33. 76 | A very flexible replacement function for replacing patterns in strings. 77 |
  34. 78 |
  35. 79 | A split function for using patterns to split strings. 80 |
  36. 81 |
  37. 82 | A test function for a quick yes/no answer. 83 |
  38. 84 |
  39. 85 | Tree depth and parser step controls to limit or "put the brakes on" an exponential or "catastrophic backtracking" syntax. 86 |
  40. 87 |
  41. 88 | Numerous display functions for a quick view of the results as text or HTML tables. 89 |
  42. 90 |
91 | 92 | **Introduction:** 93 | The motivation was originally twofold. 94 | 95 |
    96 |
  1. 97 | I wanted to replace the pattern syntax with ABNF, which to me at least, is much easier to read, write and debug than the conventional regex syntax. 98 |
  2. 99 |
  3. 100 | I felt (mistakenly) that a recursive-descent parser like APG would prove to be much more a powerful pattern matcher than regular expressions. 101 |
  4. 102 |
103 | 104 | Hardly any programmer has not needed regexes at some point, more likely lots of points, and it doesn't take much reading of the Internet forums to note that many others, like me, find the regex syntax to be quite cryptic. Additionally, because regexes have such a long, rich history with many versions from many (excellent) developers, there are many different syntax variations as you move from system to system and language to language. By contrast ABNF is standardized (although my non-standard superset additions are starting to pile up.) Whether or not the ABNF syntax is preferable to conventional regex syntax will always be a personal preference. But, for me and possibly others, ABNF offers a more transparent syntax to work with. 105 | 106 | At the outset I naively thought that the regular expressions of regexes were just that – the Chomsky hierarchy variety. Therefore, I thought that using an **APG** parser for the pattern matching would add a great deal of parsing power to the problem. I soon discovered that not only were regexes not real "regular expressions", they were powerful, recursive-descent parsers, loaded with features that went well beyond that of **APG**. I had to play a little catch up to add look behind, back referencing and anchors. That being done, however, I think there is still a case for claiming some added power. I'm not a regex expert and I won't be making any big claims here, but there are a couple of points I will mention. I think the way that **apg-exp** gives the user nearly full control over the input, output and interpretation of the character codes goes a long way to address a number of the cautions mentioned in Jeffrey Friedl's book, for example on pages 92 and 106. I also think it addresses a number of the things Larry Wall finds wrong with the regex culture in his [Apocalypse 5](http://perl6.org/archive/doc/design/apo/A05.html) page. For example, back referencing, support for named capture, nested patterns (recursive rules), capture of all matches to a sub-phrase and others. 107 | 108 | But the best thing to do, probably, is to head over to the 109 | [examples](https://github.com/ldthomas/apg-js2-examples/tree/master/apg-exp) and take a look. 110 | See and compare for yourself. I would suggest starting with the `flags`, `display` and `rules` examples to get your bearings and go from there. 111 | 112 | **Installation:** 113 | **GitHub:** In your project directory, 114 | 115 | ``` 116 | git clone https://github.com/ldthomas/apg-js2-exp.git apgexp 117 | npm install apgexp --save 118 | ``` 119 | 120 | **npm:** In your project directory, 121 | 122 | ``` 123 | npm install apg-exp --save 124 | ``` 125 | 126 | **web page:** 127 | 128 | ``` 129 | git clone https://github.com/ldthomas/apg-js2-exp.git apgexp 130 | ``` 131 | 132 | Then, in the header of your web page include, 133 | 134 | ``` 135 | 136 | 137 | ``` 138 | 139 | or, 140 | 141 | ``` 142 | 143 | 144 | ``` 145 | 146 | (Note that some **apg-exp** output is in HTML format and apgexp.css is needed to properly style it. 147 | Also, it is simply a copy of [apglib.css](https://github.com/ldthomas/apg-js2-lib).) 148 | 149 | Now access **apg-exp** as, 150 | 151 | ``` 152 | 155 | 156 | ``` 157 | 158 | See, specifically, the 159 | [email](https://github.com/ldthomas/apg-js2-examples/blob/master/apg-exp/webpage/email.js) example. 160 | 161 | **Examples:** 162 | See apg-js2-examples/apg-exp for many more examples of using 163 | **apg-exp**. 164 | 165 | **Documentation:** 166 | The full documentation is in the code in [`docco`](https://jashkenas.github.io/docco/) format. 167 | To generate the documentation, from the package directory: 168 | 169 | ``` 170 | npm install -g docco 171 | ./docco-gen 172 | ``` 173 | 174 | View `docs/index.html` in any web browser to get started. 175 | Or view it on the [APG website](https://sabnf.com) 176 | 177 | **Copyright:** 178 | _Copyright © 2017 Lowell D. Thomas, all rights reserved_ 179 | 180 | **License:** 181 | Released under the BSD-3-Clause license. 182 | -------------------------------------------------------------------------------- /guide/css/BrightSide.css: -------------------------------------------------------------------------------- 1 | /******************************************** 2 | AUTHOR: Erwin Aligam 3 | WEBSITE: http://www.styleshout.com/ 4 | TEMPLATE NAME: Bright Side of Life 5 | TEMPLATE CODE: S-0005 6 | VERSION: 1.0 7 | *******************************************/ 8 | 9 | /******************************************** 10 | HTML ELEMENTS 11 | ********************************************/ 12 | 13 | /* top elements */ 14 | * { padding: 0; margin: 0; } 15 | 16 | body { 17 | margin: 0; 18 | padding: 0; 19 | font: .80em/1.5em Verdana, Tahoma, Helvetica, sans-serif; 20 | /* color: #666666; */ 21 | color: #525252; 22 | background: #A9BAC3 url(../images/bg.gif) repeat-x; 23 | text-align: center; 24 | } 25 | 26 | /* links */ 27 | a { 28 | color: #4284B0; 29 | background-color: inherit; 30 | text-decoration: none; 31 | } 32 | a:hover { 33 | color: #9EC068; 34 | background-color: inherit; 35 | } 36 | 37 | /* headers */ 38 | h1, h2, h3, h4 { 39 | font: bold 1em 'Trebuchet MS', Arial, Sans-serif; 40 | color: #333; 41 | } 42 | h1 { font-size: 1.5em; color: #6297BC; } 43 | h2 { font-size: 1.4em; text-transform:uppercase;} 44 | h3 { font-size: 1.3em; } 45 | h4 { font-size: 1.0em; } 46 | 47 | p, h1, h2, h3, h4 { 48 | margin: 10px 15px; 49 | } 50 | ul, ol { 51 | margin: 10px 30px; 52 | padding: 0 15px; 53 | /*color: #4284B0;*/ 54 | } 55 | ul span, ol span { 56 | color: #666666; 57 | } 58 | 59 | /* images */ 60 | img { 61 | border: 2px solid #CCC; 62 | } 63 | img.no-border { 64 | border: none; 65 | } 66 | img.float-right { 67 | margin: 5px 0px 5px 15px; 68 | } 69 | img.float-left { 70 | margin: 5px 15px 5px 0px; 71 | } 72 | a img { 73 | border: 2px solid #568EB6; 74 | } 75 | a:hover img { 76 | border: 2px solid #CCC !important; /* IE fix*/ 77 | border: 2px solid #568EB6; 78 | } 79 | 80 | code { 81 | margin: 5px 0; 82 | padding: 10px; 83 | text-align: left; 84 | display: block; 85 | overflow: auto; 86 | font: 500 1em/1.5em 'Lucida Console', 'courier new', monospace; 87 | /* white-space: pre; */ 88 | background: #FAFAFA; 89 | border: 1px solid #f2f2f2; 90 | border-left: 4px solid #4284B0; 91 | border-left: 4px solid #CCC; 92 | } 93 | acronym { 94 | cursor: help; 95 | border-bottom: 1px solid #777; 96 | } 97 | blockquote { 98 | margin: 15px; 99 | padding: 0 0 0 20px; 100 | background: #FAFAFA; 101 | border: 1px solid #f2f2f2; 102 | border-left: 4px solid #4284B0; 103 | color: #4284B0; 104 | font: bold 1.2em/1.5em Georgia, 'Bookman Old Style', Serif; 105 | } 106 | 107 | /* form elements */ 108 | form { 109 | margin:10px; padding: 0; 110 | border: 1px solid #f2f2f2; 111 | background-color: #FAFAFA; 112 | } 113 | label { 114 | display:block; 115 | font-weight:bold; 116 | margin:5px 0; 117 | } 118 | input { 119 | padding: 2px; 120 | border:1px solid #eee; 121 | font: normal 1em Verdana, sans-serif; 122 | color:#777; 123 | } 124 | textarea.js { 125 | width:auto; 126 | height:auto; 127 | } 128 | textarea { 129 | /*width:300px;*/ 130 | width:99%; 131 | height:100px; 132 | padding:2px; 133 | font: normal 1em Verdana, sans-serif; 134 | border:1px solid #eee; 135 | display:block; 136 | color:#777; 137 | } 138 | input.button { 139 | margin: 0; 140 | font: bold 1em Arial, Sans-serif; 141 | border: 1px solid #CCC; 142 | background: #FFF; 143 | padding: 2px 3px; 144 | color: #4284B0; 145 | } 146 | input.button:hover 147 | { 148 | cursor:pointer; 149 | } 150 | 151 | /* search form */ 152 | form.searchform { 153 | background: transparent; 154 | border: none; 155 | margin: 0; padding: 0; 156 | } 157 | form.searchform input.textbox { 158 | margin: 0; 159 | width: 120px; 160 | border: 1px solid #9EC630; 161 | background: #FFF; 162 | color: #333; 163 | height: 14px; 164 | vertical-align: top; 165 | } 166 | form.searchform input.button { 167 | margin: 0; 168 | padding: 2px 3px; 169 | font: bold 12px Arial, Sans-serif; 170 | background: #FAFAFA; 171 | border: 1px solid #f2f2f2; 172 | color: #777; 173 | width: 60px; 174 | vertical-align: top; 175 | } 176 | 177 | /*********************** 178 | LAYOUT 179 | ************************/ 180 | #wrap { 181 | background: #FFF; 182 | width: 820px; height: 100%; 183 | margin: 0 auto; 184 | text-align: left; 185 | } 186 | #content-wrap { 187 | clear: both; 188 | margin: 0; padding: 0; 189 | background: #FFF; 190 | } 191 | 192 | /* header height: 85px;*/ 193 | #header { 194 | position: relative; 195 | height: 85px; 196 | background: #000 url(../images/headerbg.gif) repeat-x 0% 100%; 197 | } 198 | #header h1#logo { 199 | position: absolute; 200 | margin: 0; padding: 0; 201 | font: bolder 4.1em 'Trebuchet MS', Arial, Sans-serif; 202 | letter-spacing: -2px; 203 | text-transform: lowercase; 204 | top: 0; left: 5px; 205 | } 206 | #header h1#logo a{ 207 | height: 100%; 208 | width: 100%; 209 | color: inherit; 210 | background-color: inherit; 211 | text-decoration: none; 212 | 213 | 214 | } 215 | #header h2#slogan { 216 | position: absolute; 217 | top:37px; left: 80px; /*original*/ 218 | /*top:37px; left: 10px;*/ 219 | color: #666666; 220 | text-indent: 0px; 221 | font: bold 13px Tahoma, 'trebuchet MS', Sans-serif; 222 | text-transform: none; 223 | } 224 | #header form.searchform { 225 | position: absolute; 226 | top: 0; right: -12px; 227 | } 228 | 229 | /* main */ 230 | #main-1col { 231 | /* 232 | float: left; 233 | margin-left: 15px; 234 | padding: 0; 235 | width: 50%; 236 | */ 237 | float: left; 238 | margin: 0 75px; 239 | padding: 0; 240 | } 241 | 242 | #main-3col { 243 | float: left; 244 | margin-left: 0px; 245 | /* margin-left: 15px; */ 246 | padding: 0; 247 | width: 50%; 248 | } 249 | 250 | #main-2col { 251 | float: left; 252 | margin-left: 0px; 253 | /* margin-left: 15px; */ 254 | padding: 0; 255 | width: 70%; 256 | } 257 | 258 | .post-footer { 259 | background-color: #FAFAFA; 260 | padding: 5px; margin: 20px 15px 0 15px; 261 | border: 1px solid #f2f2f2; 262 | font-size: 95%; 263 | } 264 | .post-footer .date { 265 | background: url(../images/clock.gif) no-repeat left center; 266 | padding-left: 20px; margin: 0 10px 0 5px; 267 | } 268 | .post-footer .comments { 269 | background: url(../images/comment.gif) no-repeat left center; 270 | padding-left: 20px; margin: 0 10px 0 5px; 271 | } 272 | .post-footer .readmore { 273 | background: url(../images/page.gif) no-repeat left center; 274 | padding-left: 20px; margin: 0 10px 0 5px; 275 | } 276 | 277 | /* sidebar */ 278 | #sidebar { 279 | float: left; 280 | width: 26%; 281 | margin: 0; padding: 0; 282 | margin-right: 15px; 283 | display: inline; 284 | } 285 | #sidebar ul.sidemenu { 286 | list-style:none; 287 | margin:10px 0 10px 15px; 288 | padding:0; 289 | } 290 | #sidebar ul.sidemenu li { 291 | margin-bottom:1px; 292 | border: 1px solid #f2f2f2; 293 | } 294 | #sidebar ul.sidemenu a { 295 | display:block; 296 | font-weight:bold; 297 | color: #333; 298 | text-decoration:none; 299 | padding:2px 5px 2px 10px; 300 | background: #f2f2f2; 301 | border-left:4px solid #CCC; 302 | min-height:18px; 303 | } 304 | #sidebar ul.sidemenu li.subtoc a { 305 | color: #848484; 306 | font-size:10px; 307 | padding:0px 5px 0px 20px; 308 | } 309 | #sidebar ul.sidemenu li.subtoc a:hover { 310 | color: #339900; 311 | } 312 | 313 | * html body #sidebar ul.sidemenu a { height: 18px; } 314 | 315 | #sidebar ul.sidemenu a:hover { 316 | padding:2px 5px 2px 10px; 317 | background: #f2f2f2; 318 | color: #339900; 319 | border-left:4px solid #9EC630; 320 | } 321 | 322 | /* rightbar */ 323 | #rightbar { 324 | float: right; 325 | width: 23%; 326 | padding: 0; margin:0; 327 | } 328 | 329 | /* Footer */ 330 | #footer { 331 | clear: both; 332 | color: #FFF; 333 | background: #A9BAC3; 334 | border-top: 5px solid #568EB6; 335 | margin: 0; padding: 0; 336 | height: 50px; 337 | font-size: 95%; 338 | } 339 | #footer a { 340 | text-decoration: none; 341 | font-weight: bold; 342 | color: #FFF; 343 | } 344 | #footer .footer-left{ 345 | float: left; 346 | width: 55%; 347 | } 348 | #footer .footer-right{ 349 | float: right; 350 | width: 45%; 351 | } 352 | 353 | /* menu tabs */ 354 | #header ul { 355 | z-index: 999999; 356 | position: absolute; 357 | margin:0; padding: 0; 358 | list-style:none; 359 | right: 0; 360 | bottom: 6px !important; bottom: 5px; 361 | font: bold 13px Arial, 'Trebuchet MS', Tahoma, verdana, sans-serif; 362 | } 363 | #header li { 364 | display:inline; 365 | margin:0; padding:0; 366 | } 367 | #header a { 368 | float:left; 369 | background: url(../images/tableft.gif) no-repeat left top; 370 | margin:0; 371 | padding:0 0 0 4px; 372 | text-decoration:none; 373 | } 374 | #header a span { 375 | float:left; 376 | display:block; 377 | background: url(../images/tabright.gif) no-repeat right top; 378 | padding:6px 15px 3px 8px; 379 | color: #FFF; 380 | } 381 | /* Commented Backslash Hack hides rule from IE5-Mac \*/ 382 | #header a span {float:none;} 383 | /* End IE5-Mac hack */ 384 | #header a:hover span { 385 | color:#FFF; 386 | } 387 | #header a:hover { 388 | background-position:0% -42px; 389 | } 390 | #header a:hover span { 391 | background-position:100% -42px; 392 | } 393 | #header #current a { 394 | background-position:0% -42px; 395 | color: #FFF; 396 | } 397 | #header #current a span { 398 | background-position:100% -42px; 399 | color: #FFF; 400 | } 401 | /* end menu tabs */ 402 | 403 | /* alignment classes */ 404 | .float-left { float: left; } 405 | .float-right { float: right; } 406 | .align-left { text-align: left; } 407 | .align-right { text-align: right; } 408 | 409 | /* additional classes */ 410 | .clear { clear: both; } 411 | .green { color: #9EC630; } 412 | .gray { color: #BFBFBF; } 413 | 414 | /* apg-exp specific */ 415 | span.inline-code{ 416 | font: 500 1em/1.5em 'Lucida Console', 'courier new', monospace; 417 | color: #333333; 418 | background: #FAFAFA; 419 | } 420 | 421 | div#main-2col ul { 422 | font-weight: normal; 423 | list-style-type: none; 424 | } 425 | 426 | div#main-2col ul ul{ 427 | font-weight: normal; 428 | list-style-type: none; 429 | margin: 0; 430 | } 431 | 432 | div#main-2col pre { 433 | margin: 5px 0; 434 | padding: 10px; 435 | text-align: left; 436 | display: block; 437 | overflow: auto; 438 | font: 500 1em/1.5em 'Lucida Console', 'courier new', monospace; 439 | color: #333333; 440 | background: #FAFAFA; 441 | border: 1px solid #f2f2f2; 442 | border-left: 4px solid #4284B0; 443 | border-left: 4px solid #CCC; 444 | 445 | } 446 | 447 | body.if{ 448 | margin: 0; 449 | padding: 0; 450 | font: .80em/1.5em Verdana, Tahoma, Helvetica, sans-serif; 451 | color: #525252; 452 | background: white; 453 | text-align: center; 454 | } 455 | 456 | #menu-frame{ 457 | width: 100%; 458 | height: 500px; 459 | border: none; 460 | } 461 | 462 | kbd { 463 | font-family: "Courier New", monospace; 464 | background: #FAFAFA; 465 | color: #404040; 466 | /* 467 | border-radius: 3px; 468 | border: 1px solid #A9A9A9; 469 | */ 470 | } 471 | span.mono{ 472 | font-family: "Courier New", monospace; 473 | background: #FAFAFA; 474 | border-radius: 3px; 475 | border: 1px solid #A9A9A9; 476 | } 477 | 478 | .margin{ 479 | margin: 10px 15px; 480 | } 481 | 482 | table.margin td{ 483 | vertical-align: top; 484 | line-height: 150%; 485 | } 486 | -------------------------------------------------------------------------------- /src/result.js: -------------------------------------------------------------------------------- 1 | // This module defines all of the display functions. Text and HTML displays of 2 | // the grammar source, the result object and the `apg-exp` "last match" object. 3 | "use strict;" 4 | var apglib = require("apg-lib"); 5 | var utils = apglib.utils; 6 | var style = apglib.style; 7 | var MODE_HEX = 16; 8 | var MODE_DEC = 10; 9 | var MODE_ASCII = 8; 10 | var MODE_UNICODE = 32; 11 | /* add style to HTML phrases */ 12 | var phraseStyle = function(phrase, phraseStyle) { 13 | if (phrase === "") { 14 | return '𝜺'; 15 | } 16 | if (phrase === undefined) { 17 | return 'undefined'; 18 | } 19 | var classStyle = style.CLASS_REMAINDER; 20 | if (typeof (phraseStyle) === "string") { 21 | if (phraseStyle.toLowerCase() === "match") { 22 | classStyle = style.CLASS_MATCH; 23 | } else if (phraseStyle.toLowerCase() === "nomatch") { 24 | classStyle = style.CLASS_NOMATCH; 25 | } 26 | } 27 | var chars = apglib.utils.stringToChars(phrase); 28 | var html = ''; 29 | html += apglib.utils.charsToAsciiHtml(chars); 30 | return html + ""; 31 | } 32 | /* result object - string phrases to ASCII text */ 33 | var sResultToText = function(result) { 34 | var txt = ""; 35 | txt += " result:\n"; 36 | txt += " [0]: "; 37 | txt += result[0]; 38 | txt += "\n"; 39 | txt += " input: " + result.input 40 | txt += "\n"; 41 | txt += " index: " + result.index 42 | txt += "\n"; 43 | txt += " length: " + result.length 44 | txt += "\n"; 45 | txt += "tree depth: " + result.treeDepth 46 | txt += "\n"; 47 | txt += " node hits: " + result.nodeHits 48 | txt += "\n"; 49 | txt += " rules: " 50 | var prefix = ""; 51 | var indent = " : "; 52 | var rules = result.rules; 53 | for ( var name in rules) { 54 | var rule = rules[name]; 55 | if (rule) { 56 | for (var i = 0; i < rule.length; i += 1) { 57 | var ruleobj = rule[i]; 58 | txt += prefix + name + " : " + ruleobj.index + ": "; 59 | txt += ruleobj.phrase; 60 | txt += "\n"; 61 | prefix = indent; 62 | } 63 | } else { 64 | txt += prefix + name + ": "; 65 | txt += "undefined"; 66 | txt += "\n"; 67 | } 68 | prefix = indent; 69 | } 70 | return txt; 71 | } 72 | /* result object - string to HTML text */ 73 | var sResultToHtml = function(result) { 74 | var html = ""; 75 | var caption = "result:"; 76 | html += '\n'; 77 | html += '\n'; 78 | html += ''; 79 | html += ''; 80 | html += '\n'; 81 | 82 | html += ''; 83 | html += ''; 84 | html += ''; 85 | html += ''; 86 | html += '\n'; 87 | 88 | html += ''; 89 | html += ''; 90 | html += ''; 91 | html += ''; 92 | html += '\n'; 93 | 94 | html += ''; 95 | html += ''; 96 | html += ''; 97 | html += '\n'; 98 | 99 | html += ''; 100 | html += ''; 101 | html += ''; 102 | html += '\n'; 103 | 104 | html += ''; 105 | html += ''; 106 | html += ''; 107 | html += '\n'; 108 | 109 | html += ''; 110 | html += ''; 111 | html += ''; 112 | html += '\n'; 113 | 114 | html += ''; 115 | html += ''; 116 | html += '\n'; 117 | 118 | var rules = result.rules; 119 | for ( var name in rules) { 120 | var rule = rules[name]; 121 | if (rule) { 122 | for (var i = 0; i < rule.length; i += 1) { 123 | var ruleobj = rule[i]; 124 | html += ''; 125 | html += ""; 126 | html += ""; 127 | html += ""; 128 | html += "\n"; 129 | } 130 | } else { 131 | html += ''; 132 | html += ""; 133 | html += ""; 134 | html += ""; 135 | html += "\n"; 136 | } 137 | } 138 | html += '
' + caption + '
itemvaluephrase
[0]' + result.index + '' + phraseStyle(result[0], "match") + '
input0' + phraseStyle(result.input) + '
index' + result.index + '
length' + result.length + '
tree depth' + result.treeDepth + '
node hits' + result.nodeHits + '
rulesindexphrase
" + name + "" + ruleobj.index + "" + phraseStyle(ruleobj.phrase, "match") + "
" + name + "" + phraseStyle(undefined) + "
\n'; 139 | return html; 140 | } 141 | /* result object - string to HTML page */ 142 | var sResultToHtmlPage = function(result) { 143 | return utils.htmlToPage(sResultToHtml(result), "apg-exp result"); 144 | } 145 | /* apg-exp object - string to ASCII text */ 146 | var sLastMatchToText = function(exp) { 147 | var txt = ''; 148 | txt += " last match:\n"; 149 | txt += " lastIndex: "; 150 | txt += exp.lastIndex; 151 | txt += "\n"; 152 | txt += ' flags: "'; 153 | txt += exp.flags + '"'; 154 | txt += "\n"; 155 | txt += " global: "; 156 | txt += exp.global; 157 | txt += "\n"; 158 | txt += " sticky: "; 159 | txt += exp.sticky; 160 | txt += "\n"; 161 | txt += " unicode: "; 162 | txt += exp.unicode; 163 | txt += "\n"; 164 | txt += " debug: "; 165 | txt += exp.debug; 166 | txt += "\n"; 167 | if (exp["$&"] === undefined) { 168 | txt += " last match: undefined"; 169 | txt += "\n"; 170 | return txt; 171 | } 172 | txt += " input: "; 173 | txt += exp.input; 174 | txt += "\n"; 175 | txt += " leftContext: "; 176 | txt += exp.leftContext; 177 | txt += "\n"; 178 | txt += " lastMatch: "; 179 | txt += exp.lastMatch; 180 | txt += "\n"; 181 | txt += "rightContext: "; 182 | txt += exp.rightContext; 183 | txt += "\n"; 184 | txt += " rules: "; 185 | var prefix = ""; 186 | var indent = " : "; 187 | for ( var name in exp.rules) { 188 | txt += prefix + name + " : " 189 | txt += exp.rules[name]; 190 | txt += "\n"; 191 | prefix = indent; 192 | } 193 | txt += "\n"; 194 | txt += "alias:\n"; 195 | txt += ' ["$_"]: '; 196 | txt += exp['$_']; 197 | txt += "\n"; 198 | txt += ' ["$`"]: '; 199 | txt += exp['$`']; 200 | txt += '\n'; 201 | txt += ' ["$&"]: '; 202 | txt += exp['$&']; 203 | txt += '\n'; 204 | txt += ' ["$\'"]: '; 205 | txt += exp["$'"]; 206 | txt += '\n'; 207 | for ( var name in exp.rules) { 208 | txt += ' ["${' + name + '}"]: ' 209 | txt += exp['${' + name + '}']; 210 | txt += "\n"; 211 | } 212 | return txt; 213 | } 214 | /* apg-exp object - string to HTML text */ 215 | var sLastMatchToHtml = function(exp) { 216 | var html = ""; 217 | var caption = "last match:"; 218 | html += '\n'; 219 | html += '\n'; 220 | html += ''; 221 | html += ''; 222 | html += '\n'; 223 | 224 | html += ''; 225 | html += ''; 226 | html += ''; 227 | html += '\n'; 228 | 229 | html += ''; 230 | html += ''; 231 | html += ''; 232 | html += '\n'; 233 | 234 | html += ''; 235 | html += ''; 236 | html += ''; 237 | html += '\n'; 238 | 239 | html += ''; 240 | html += ''; 241 | html += ''; 242 | html += '\n'; 243 | 244 | html += ''; 245 | html += ''; 246 | html += ''; 247 | html += '\n'; 248 | 249 | html += ''; 250 | html += ''; 251 | html += ''; 252 | html += '\n'; 253 | 254 | if (exp["$&"] === undefined) { 255 | html += ''; 256 | html += ''; 257 | html += ''; 258 | html += '\n'; 259 | html += '
' + caption + '
itemvalue
lastIndex' + exp.lastIndex + '
flags"' + exp.flags + '"
global' + exp.global + '
sticky' + exp.sticky + '
unicode' + exp.unicode + '
debug' + exp.debug + '
lastMatch' + phraseStyle(undefined) + '
\n'; 260 | return html; 261 | } 262 | html += 'itemphrase'; 263 | html += '\n'; 264 | html += ''; 265 | html += 'input'; 266 | html += '' + phraseStyle(exp.input) + ''; 267 | html += '\n'; 268 | 269 | html += ''; 270 | html += 'leftContext'; 271 | html += '' + phraseStyle(exp.leftContext) + ''; 272 | html += '\n'; 273 | 274 | html += ''; 275 | html += 'lastMatch'; 276 | html += '' + phraseStyle(exp.lastMatch, "match") + ''; 277 | html += '\n'; 278 | 279 | html += ''; 280 | html += 'rightContext'; 281 | html += '' + phraseStyle(exp.rightContext) + ''; 282 | html += '\n'; 283 | 284 | html += ''; 285 | html += 'rulephrase'; 286 | html += '\n'; 287 | 288 | for ( var name in exp.rules) { 289 | html += ''; 290 | html += '' + name + ''; 291 | html += '' + phraseStyle(exp.rules[name]) + ''; 292 | html += '\n'; 293 | } 294 | 295 | html += ''; 296 | html += 'aliasphrase'; 297 | html += '\n'; 298 | html += ''; 299 | html += '["$_"]'; 300 | html += '' + phraseStyle(exp['$_']) + ''; 301 | html += '\n'; 302 | 303 | html += ''; 304 | html += '["$`"]'; 305 | html += '' + phraseStyle(exp['$`']) + ''; 306 | html += '\n'; 307 | 308 | html += ''; 309 | html += '["$&"]'; 310 | html += '' + phraseStyle(exp['$&'], "match") + ''; 311 | html += '\n'; 312 | 313 | html += ''; 314 | html += '["$\'"]'; 315 | html += '' + phraseStyle(exp['$\'']) + ''; 316 | html += '\n'; 317 | 318 | for ( var name in exp.rules) { 319 | html += ''; 320 | html += '["${' + name + '}"]'; 321 | html += '' + phraseStyle(exp['${' + name + '}']) + ''; 322 | html += '\n'; 323 | } 324 | html += '\n'; 325 | return html; 326 | } 327 | /* apg-exp object - string to HTML page */ 328 | var sLastMatchToHtmlPage = function(exp) { 329 | return utils.htmlToPage(sLastMatchToHtml(exp), "apg-exp last result"); 330 | } 331 | /* translates ASCII string to integer mode identifier - defaults to ASCII */ 332 | var getMode = function(modearg) { 333 | var mode = MODE_ASCII; 334 | if (typeof (modearg) === "string" && modearg.length >= 3) { 335 | var modein = modearg.toLowerCase().slice(0, 3); 336 | if (modein === 'hex') { 337 | mode = MODE_HEX; 338 | } else if (modein === 'dec') { 339 | mode = MODE_DEC; 340 | } else if (modein === 'uni') { 341 | mode = MODE_UNICODE; 342 | } 343 | } 344 | return mode; 345 | } 346 | /* translate integer mode identifier to standard text string */ 347 | var modeToText = function(mode) { 348 | var txt; 349 | switch (mode) { 350 | case MODE_ASCII: 351 | txt = "ascii"; 352 | break; 353 | case MODE_HEX: 354 | txt = "hexidecimal"; 355 | break; 356 | case MODE_DEC: 357 | txt = "decimal"; 358 | break; 359 | case MODE_UNICODE: 360 | txt = "Unicode"; 361 | break; 362 | } 363 | return txt; 364 | } 365 | /* convert integer to hex with leading 0 if necessary */ 366 | var charToHex = function(char) { 367 | var ch = char.toString(16); 368 | if (ch.length % 2 !== 0) { 369 | ch = "0" + ch; 370 | } 371 | return ch; 372 | } 373 | /* convert integer character code array to formatted text string */ 374 | var charsToMode = function(chars, mode) { 375 | var txt = ''; 376 | if (mode === MODE_ASCII) { 377 | txt += apglib.utils.charsToString(chars); 378 | } else if (mode === MODE_DEC) { 379 | txt += "["; 380 | if (chars.length > 0) { 381 | txt += chars[0]; 382 | for (var i = 1; i < chars.length; i += 1) { 383 | txt += "," + chars[i]; 384 | } 385 | } 386 | txt += "]"; 387 | } else if (mode === MODE_HEX) { 388 | txt += "["; 389 | if (chars.length > 0) { 390 | txt += "\\x" + charToHex(chars[0]); 391 | for (var i = 1; i < chars.length; i += 1) { 392 | txt += ",\\x" + charToHex(chars[i]); 393 | } 394 | } 395 | txt += "]"; 396 | } else if (mode === MODE_UNICODE) { 397 | txt += "["; 398 | if (chars.length > 0) { 399 | txt += "\\u" + charToHex(chars[0]); 400 | for (var i = 1; i < chars.length; i += 1) { 401 | txt += ",\\u" + charToHex(chars[i]); 402 | } 403 | } 404 | txt += "]"; 405 | } 406 | return txt; 407 | } 408 | /* result object - Unicode mode to ASCII text */ 409 | var uResultToText = function(result, mode) { 410 | mode = getMode(mode); 411 | var txt = ""; 412 | txt += " result(" + modeToText(mode) + ")\n"; 413 | txt += " [0]: "; 414 | txt += charsToMode(result[0], mode); 415 | txt += "\n"; 416 | txt += " input: " + charsToMode(result.input, mode); 417 | txt += "\n"; 418 | txt += " index: " + result.index 419 | txt += "\n"; 420 | txt += " length: " + result.length 421 | txt += "\n"; 422 | txt += "tree depth: " + result.treeDepth 423 | txt += "\n"; 424 | txt += " node hits: " + result.nodeHits 425 | txt += "\n"; 426 | txt += " rules: " 427 | txt += "\n"; 428 | var rules = result.rules; 429 | for ( var name in rules) { 430 | var rule = rules[name]; 431 | if (rule) { 432 | for (var i = 0; i < rule.length; i += 1) { 433 | var ruleobj = rule[i]; 434 | txt += " :" + name + " : " + ruleobj.index + ": "; 435 | txt += charsToMode(ruleobj.phrase, mode); 436 | txt += "\n"; 437 | } 438 | } else { 439 | txt += " :" + name + ": "; 440 | txt += "undefined"; 441 | txt += "\n"; 442 | } 443 | } 444 | return txt; 445 | } 446 | /* result object - Unicode mode to HTML text */ 447 | var uResultToHtml = function(result, mode) { 448 | mode = getMode(mode); 449 | var html = ""; 450 | var caption = "result:"; 451 | caption += "(" + modeToText(mode) + ")"; 452 | html += '\n'; 453 | html += '\n'; 454 | html += ''; 455 | html += ''; 456 | html += '\n'; 457 | 458 | html += ''; 459 | html += ''; 460 | html += ''; 461 | html += ''; 462 | html += '\n'; 463 | 464 | html += ''; 465 | html += ''; 466 | html += ''; 467 | html += ''; 468 | html += '\n'; 469 | 470 | html += ''; 471 | html += ''; 472 | html += ''; 473 | html += '\n'; 474 | 475 | html += ''; 476 | html += ''; 477 | html += ''; 478 | html += '\n'; 479 | 480 | html += ''; 481 | html += ''; 482 | html += ''; 483 | html += '\n'; 484 | 485 | html += ''; 486 | html += ''; 487 | html += ''; 488 | html += '\n'; 489 | 490 | html += ''; 491 | html += ''; 492 | html += '\n'; 493 | 494 | var rules = result.rules; 495 | for ( var name in rules) { 496 | var rule = rules[name]; 497 | if (rule) { 498 | for (var i = 0; i < rule.length; i += 1) { 499 | var ruleobj = rule[i]; 500 | html += ''; 501 | html += ""; 502 | html += ""; 503 | html += ""; 504 | html += "\n"; 505 | } 506 | } else { 507 | html += ''; 508 | html += ""; 509 | html += ""; 510 | html += ""; 511 | html += "\n"; 512 | } 513 | } 514 | html += '
' + caption + '
itemvaluephrase
[0]' + result.index + '' + phraseStyle(charsToMode(result[0], mode), "match") + '
input0' + phraseStyle(charsToMode(result.input, mode)) + '
index' + result.index + '
length' + result.length + '
tree depth' + result.treeDepth + '
node hits' + result.nodeHits + '
rulesindexphrase
" + name + "" + ruleobj.index + "" + phraseStyle(charsToMode(ruleobj.phrase, mode), "match") + "
" + name + "" + phraseStyle(undefined) + "
\n'; 515 | return html; 516 | 517 | } 518 | /* result object - Unicode mode to HTML page */ 519 | var uResultToHtmlPage = function(result, mode) { 520 | return utils.htmlToPage(uResultToHtml(result, mode)); 521 | } 522 | /* apg-exp object - Unicode mode to ASCII text */ 523 | var uLastMatchToText = function(exp, mode) { 524 | mode = getMode(mode); 525 | var txt = ''; 526 | txt += " last match(" + modeToText(mode) + ")\n"; 527 | txt += " lastIndex: " + exp.lastIndex; 528 | txt += "\n"; 529 | txt += ' flags: "' + exp.flags + '"'; 530 | txt += "\n"; 531 | txt += " global: " + exp.global; 532 | txt += "\n"; 533 | txt += " sticky: " + exp.sticky; 534 | txt += "\n"; 535 | txt += " unicode: " + exp.unicode; 536 | txt += "\n"; 537 | txt += " debug: " + exp.debug; 538 | txt += "\n"; 539 | if (exp["$&"] === undefined) { 540 | txt += " lastMatch: undefined"; 541 | txt += "\n"; 542 | return txt; 543 | } 544 | txt += " input: "; 545 | txt += charsToMode(exp.input, mode); 546 | txt += "\n"; 547 | txt += " leftContext: "; 548 | txt += charsToMode(exp.leftContext, mode); 549 | txt += "\n"; 550 | txt += " lastMatch: "; 551 | txt += charsToMode(exp.lastMatch, mode); 552 | txt += "\n"; 553 | txt += "rightContext: "; 554 | txt += charsToMode(exp.rightContext, mode); 555 | txt += "\n"; 556 | 557 | txt += " rules:"; 558 | var prefix = ""; 559 | var indent = " :"; 560 | for ( var name in exp.rules) { 561 | txt += prefix + name + " : " 562 | txt += (exp.rules[name]) ? charsToMode(exp.rules[name], mode) : "undefined"; 563 | txt += "\n"; 564 | prefix = indent; 565 | } 566 | txt += "\n"; 567 | txt += " alias:\n"; 568 | txt += ' ["$_"]: '; 569 | txt += charsToMode(exp['$_'], mode); 570 | txt += "\n"; 571 | txt += ' ["$`"]: '; 572 | txt += charsToMode(exp['$`'], mode); 573 | txt += "\n"; 574 | txt += ' ["$&"]: '; 575 | txt += charsToMode(exp['$&'], mode); 576 | txt += "\n"; 577 | txt += ' ["$\'"]: '; 578 | txt += charsToMode(exp["$'"], mode); 579 | txt += "\n"; 580 | for ( var name in exp.rules) { 581 | txt += ' ["${' + name + '}"]: ' 582 | txt += (exp['${' + name + '}']) ? charsToMode(exp['${' + name + '}'], mode) : "undefined"; 583 | txt += "\n"; 584 | } 585 | return txt; 586 | } 587 | /* apg-exp object - Unicode mode to HTML text */ 588 | var uLastMatchToHtml = function(exp, mode) { 589 | mode = getMode(mode); 590 | var html = ""; 591 | var caption = "last match:"; 592 | caption += "(" + modeToText(mode) + ")"; 593 | html += '\n'; 594 | html += '\n'; 595 | html += ''; 596 | html += ''; 597 | html += '\n'; 598 | 599 | html += ''; 600 | html += ''; 601 | html += ''; 602 | html += '\n'; 603 | 604 | html += ''; 605 | html += ''; 606 | html += ''; 607 | html += '\n'; 608 | 609 | html += ''; 610 | html += ''; 611 | html += ''; 612 | html += '\n'; 613 | 614 | html += ''; 615 | html += ''; 616 | html += ''; 617 | html += '\n'; 618 | 619 | html += ''; 620 | html += ''; 621 | html += ''; 622 | html += '\n'; 623 | 624 | html += ''; 625 | html += ''; 626 | html += ''; 627 | html += '\n'; 628 | 629 | html += ''; 630 | html += ''; 631 | html += '\n'; 632 | if (exp["$&"] === undefined) { 633 | html += ''; 634 | html += ''; 635 | html += ''; 636 | html += '\n'; 637 | html += '
' + caption + '
itemvalue
lastIndex' + exp.lastIndex + '
flags"' + exp.flags + '"
global' + exp.global + '
sticky' + exp.sticky + '
unicode' + exp.unicode + '
debug' + exp.debug + '
itemphrase
lastMatch' + phraseStyle(undefined) + '
\n'; 638 | return html; 639 | } 640 | html += ''; 641 | html += 'input'; 642 | html += '' + phraseStyle(charsToMode(exp.input, mode)) + ''; 643 | html += '\n'; 644 | 645 | html += ''; 646 | html += 'leftContext'; 647 | html += '' + phraseStyle(charsToMode(exp.leftContext, mode)) + ''; 648 | html += '\n'; 649 | 650 | html += ''; 651 | html += 'lastMatch'; 652 | html += '' + phraseStyle(charsToMode(exp.lastMatch, mode), "match") + ''; 653 | html += '\n'; 654 | 655 | html += ''; 656 | html += 'rightContext'; 657 | html += '' + phraseStyle(charsToMode(exp.rightContext, mode)) + ''; 658 | html += '\n'; 659 | 660 | html += ''; 661 | html += 'rulesphrase'; 662 | html += '\n'; 663 | 664 | for ( var name in exp.rules) { 665 | html += ''; 666 | html += '' + name + ''; 667 | if (exp.rules[name]) { 668 | html += '' + phraseStyle(charsToMode(exp.rules[name], mode)) + ''; 669 | } else { 670 | html += '' + phraseStyle(undefined) + ''; 671 | } 672 | html += '\n'; 673 | } 674 | 675 | html += ''; 676 | html += 'aliasphrase'; 677 | html += '\n'; 678 | html += ''; 679 | html += '["$_"]'; 680 | html += '' + phraseStyle(charsToMode(exp['$_'], mode)) + ''; 681 | html += '\n'; 682 | 683 | html += ''; 684 | html += '["$`"]'; 685 | html += '' + phraseStyle(charsToMode(exp['$`'], mode)) + ''; 686 | html += '\n'; 687 | 688 | html += ''; 689 | html += '["$&"]'; 690 | html += '' + phraseStyle(charsToMode(exp['$&'], mode), "match") + ''; 691 | html += '\n'; 692 | 693 | html += ''; 694 | html += '["$\'"]'; 695 | html += '' + phraseStyle(charsToMode(exp['$\''], mode)) + ''; 696 | html += '\n'; 697 | 698 | for ( var name in exp.rules) { 699 | html += ''; 700 | html += '["${' + name + '}"]'; 701 | if (exp['${' + name + '}']) { 702 | html += '' + phraseStyle(charsToMode(exp['${' + name + '}'], mode)) + ''; 703 | } else { 704 | html += '' + phraseStyle(undefined) + ''; 705 | } 706 | html += '\n'; 707 | } 708 | html += '\n'; 709 | return html; 710 | } 711 | /* apg-exp object - Unicode mode to HTML page */ 712 | var uLastMatchToHtmlPage = function(exp, mode) { 713 | return utils.htmlToPage(uLastMatchToHtml(exp, mode)); 714 | } 715 | /* SABNF grammar souce to ASCII text */ 716 | var sourceToText = function(exp) { 717 | return exp.source; 718 | } 719 | /* SABNF grammar souce to HTML */ 720 | var sourceToHtml = function(exp) { 721 | var rx = /.*(\r\n|\n|\r)/g; 722 | var result, chars, html; 723 | html = "
\n";
724 |   while (true) {
725 |     result = rx.exec(exp.source);
726 |     if (result === null || result[0] === "") {
727 |       break;
728 |     }
729 |     chars = apglib.utils.stringToChars(result[0]);
730 |     html += apglib.utils.charsToAsciiHtml(chars);
731 |     html += "\n";
732 |   }
733 |   html += "
\n"; 734 | return html; 735 | } 736 | /* SABNF grammar souce to HTML page */ 737 | var sourceToHtmlPage = function(exp) { 738 | return apglib.utils.htmlToPage(sourceToHtml(exp), "apg-exp source"); 739 | } 740 | /* export modules needed by the apg-exp and result objects to display their values */ 741 | module.exports = { 742 | s : { 743 | resultToText : sResultToText, 744 | resultToHtml : sResultToHtml, 745 | resultToHtmlPage : sResultToHtmlPage, 746 | expToText : sLastMatchToText, 747 | expToHtml : sLastMatchToHtml, 748 | expToHtmlPage : sLastMatchToHtmlPage, 749 | sourceToText : sourceToText, 750 | sourceToHtml : sourceToHtml, 751 | sourceToHtmlPage : sourceToHtmlPage 752 | }, 753 | u : { 754 | resultToText : uResultToText, 755 | resultToHtml : uResultToHtml, 756 | resultToHtmlPage : uResultToHtmlPage, 757 | expToText : uLastMatchToText, 758 | expToHtml : uLastMatchToHtml, 759 | expToHtmlPage : uLastMatchToHtmlPage 760 | } 761 | } 762 | -------------------------------------------------------------------------------- /src/apg-exp.js: -------------------------------------------------------------------------------- 1 | // This is the `apg-exp` object constructor. 2 | // `apg-exp` functions similarly to the built-in JavaScript `RegExp` pattern matching engine. 3 | // However, patterns are described with an [SABNF]() 4 | // syntax and matching is done with an 5 | // [`apg`](https://github.com/ldthomas/apg-js2) parser. 6 | // See the [README](./README.html) file for a more detailed description. 7 | // 8 | // The input parameters are: 9 | //

 10 | // input - required, type "string" or "object"
 11 | //         if it is a string, it must be a
 12 | //           valid SABNF grammar (see note(*) below) 
 13 | //         if it is an object, it must be a
 14 | //           valid APG-generated grammar object.
 15 | // flags - optional, string of flag characters,
 16 | //         g - global search, advances lastIndex to end of
 17 | //             matched phrase. Repeated calls to exec()
 18 | //             will find all matches in the input string.
 19 | //         y - sticky, similar to g but lastIndex acts
 20 | //             as an anchor - a match must be found beginning
 21 | //             at lastIndex. Repeated calls to exec() will
 22 | //             find all *consecutive* matches in the string. 
 23 | //         u - unicode, does not change the behavior of the
 24 | //             pattern matching, only the form of the results.
 25 | //             With the u flag set all resulting matched phrases
 26 | //             are returned as arrays of integer character codes
 27 | //             rather than JavaScript strings.
 28 | //         d - debug, when set the **APG** trace object is
 29 | //             available to the user to trace steps the parser
 30 | //             took. Handy for debugging syntax and phrases that
 31 | //             aren't behaving as expected.
 32 | //         defaults are all false
 33 | // nodeHits -  optional, the maximum number of node hits the parser
 34 | //             is allowed (default Infinity)
 35 | // treeDepth - optional, the maximum parse tree depth allowed
 36 | //             (default Infinity)
 37 | //             (see note (**) below)
 38 | //
39 | // To skip over default values, enter `null` or `undefined`. e.g. 40 | //
 41 | //
 42 | // var abnfexp = require("abnf-exp");
 43 | // var exp = new abnfexp('rule = "abc"\n', null, null, 100);
 44 | //
 45 | //
46 | // This will set the maximum tree depth to 100 and leave `flags` and `nodeHits` at their default. 47 | // 48 | // **Note(\*):** 49 | // For longer, more complex grammars, it is recommended to use [APG](https://github.com/ldthomas/apg-js2) 50 | // to generate the grammar object 51 | // in advance. 52 | // In addition to saving the compile time each time you run the application, 53 | // its error reporting is much more complete and getting the grammar right is much easier. 54 | // 55 | // **Note(\*\*):** 56 | // Some pathological grammars can push a recursive-decent parser to exponential behavior 57 | // with an exceptionally large number of parse tree node operations (node hits) and/or 58 | // an exceptionally large parse tree depth. For most grammars this is not a problem. 59 | // But if you want to protect against this kind of behavior you can set limits on either or both of these. 60 | // The parser will throw an exception with a message telling you that the maximum number was exceeded. 61 | // You would probably get an exception anyway (or a hung application), but with these exceptions it should 62 | // be a little easier to figure out what went wrong. 63 | module.exports = function(input, flags, nodeHits, treeDepth) { 64 | "use strict;" 65 | var _this = this; 66 | var thisFileName = "apg-exp: "; 67 | var errorName = thisFileName; 68 | var apglib = require("apg-lib"); 69 | var execFuncs = require("./exec.js"); 70 | var replaceFuncs = require("./replace.js"); 71 | var resultFuncs = require("./result.js"); 72 | var splitFuncs = require("./split.js"); 73 | var setFlags = require("./flags.js"); 74 | var sabnfGenerator = require("./sabnf-generator.js"); 75 | var readonly = { 76 | writable : false, 77 | enumerable : false, 78 | configurable : true 79 | }; 80 | /* private object data that needs to be passed around to supporting modules */ 81 | var priv = { 82 | _this : this, 83 | grammarObject : null, 84 | ruleNames : [], 85 | str : null, 86 | chars : null, 87 | parser : null, 88 | result : null, 89 | charsToString : null, 90 | match : function(state) { 91 | return (state === apglib.ids.MATCH || state === apglib.ids.EMPTY); 92 | } 93 | } 94 | // This is a custom exception object. 95 | // Derived from Error, it is named `ApgExpError` and in addition to the error `message` 96 | // it has two functions, `toText()` and `toHtml()` which will display the errors 97 | // in a user-friendly ASCII text format or HTML format like the formats used by APG. 98 | // e. g. 99 | //``` 100 | // try{ 101 | // ... 102 | // }catch(e){ 103 | // if(e.name === "ApgExpError"){ 104 | // console.log(e.toText()); 105 | // }else{ 106 | // console.log(e.message); 107 | // } 108 | //``` 109 | // All errors from the constructor and all object functions are reported by throwing an `ApgExpError` Error object. 110 | var ApgExpError = function(msg, t, h){ 111 | this.message = msg; 112 | this.name = "ApgExpError"; 113 | var text = t; 114 | var html = h; 115 | this.toText = function(){ 116 | var ret = ""; 117 | ret += this.message; 118 | ret += "\n"; 119 | if(text){ 120 | ret += text; 121 | } 122 | return ret; 123 | } 124 | this.toHtml = function(){ 125 | var ret = ""; 126 | ret += "

" + apglib.utils.stringToAsciiHtml(this.message) + "

"; 127 | ret += "\n"; 128 | if(html){ 129 | ret += html; 130 | } 131 | return ret; 132 | } 133 | } 134 | ApgExpError.prototype = new Error(); 135 | 136 | /* verifies that all UDT callback functions have been defined */ 137 | var checkParserUdts = function(errorName) { 138 | var udterrors = [] 139 | var error = null; 140 | for (var i = 0; i < priv.grammarObject.udts.length; i += 1) { 141 | var lower = priv.grammarObject.udts[i].lower; 142 | if (typeof (priv.parser.callbacks[lower]) !== "function") { 143 | udterrors.push(priv.ruleNames[lower]); 144 | } 145 | } 146 | if (udterrors.length > 0) { 147 | error = "undefined UDT callback functions: " + udterrors; 148 | } 149 | return error; 150 | } 151 | 152 | /* the constructor */ 153 | errorName = thisFileName + "constructor: "; 154 | var error = null; 155 | var result = null; 156 | try { 157 | while (true) { 158 | /* flags */ 159 | error = setFlags(this, flags); 160 | if (error) { 161 | error = new ApgExpError(error); 162 | break; 163 | } 164 | /* grammar object for the defining SABNF grammar */ 165 | if (typeof (input) === "string") { 166 | this.source = input; 167 | result = sabnfGenerator(input); 168 | if (result.error) { 169 | error = new ApgExpError(result.error, result.text, result.html); 170 | break; 171 | } 172 | priv.grammarObject = result.obj; 173 | } else if (typeof (input) === "object" && typeof (input.grammarObject) === "string" 174 | && input.grammarObject === "grammarObject") { 175 | priv.grammarObject = input; 176 | this.source = priv.grammarObject.toString(); 177 | } else { 178 | error = new ApgExpError(thisFileName + "invalid SABNF grammar input"); 179 | this.source = ""; 180 | break; 181 | } 182 | Object.defineProperty(this, "source", readonly); 183 | /* the parser & AST */ 184 | priv.charsToString = apglib.utils.charsToString; 185 | priv.parser = new apglib.parser(); 186 | this.ast = new apglib.ast(); 187 | this.trace = this.debug ? (new apglib.trace()) : null; 188 | for (var i = 0; i < priv.grammarObject.rules.length; i += 1) { 189 | var rule = priv.grammarObject.rules[i]; 190 | priv.ruleNames[rule.lower] = rule.name 191 | priv.parser.callbacks[rule.lower] = false; 192 | this.ast.callbacks[rule.lower] = true; 193 | } 194 | for (var i = 0; i < priv.grammarObject.udts.length; i += 1) { 195 | var rule = priv.grammarObject.udts[i]; 196 | priv.ruleNames[rule.lower] = rule.name 197 | priv.parser.callbacks[rule.lower] = false; 198 | this.ast.callbacks[rule.lower] = true; 199 | } 200 | /* nodeHit and treeDepth limits */ 201 | if (typeof (nodeHits) === "number") { 202 | this.nodeHits = Math.floor(nodeHits); 203 | if (this.nodeHits > 0) { 204 | priv.parser.setMaxNodeHits(this.nodeHits); 205 | } else { 206 | error = new ApgExpError(thisFileName + "nodeHits must be integer > 0: " + nodeHits); 207 | this.nodeHits = Infinity; 208 | break; 209 | } 210 | } else { 211 | this.nodeHits = Infinity; 212 | } 213 | if (typeof (treeDepth) === "number") { 214 | this.treeDepth = Math.floor(treeDepth); 215 | if (this.treeDepth > 0) { 216 | priv.parser.setMaxTreeDepth(this.treeDepth); 217 | } else { 218 | error = new ApgExpError(thisFileName + "treeDepth must be integer > 0: " + treeDepth); 219 | this.treeDepth = Infinity; 220 | break; 221 | } 222 | } else { 223 | this.treeDepth = Infinity; 224 | } 225 | Object.defineProperty(this, "nodeHits", readonly); 226 | Object.defineProperty(this, "treeDepth", readonly); 227 | /* success */ 228 | this.lastIndex = 0; 229 | break; 230 | } 231 | } catch (e) { 232 | error = new ApgExpError(e.name + ": " + e.message); 233 | } 234 | if (error) { 235 | throw error; 236 | } 237 | //

238 |   // str - the input string to find the patterns in
239 |   // may be a JavaScript string or an array of
240 |   // character codes
241 |   // 
242 | 243 | // Find the SABNF-defined pattern in the input string. 244 | // Can be called multiple times with the `g` or `y` flags. 245 | // If both `g` and `y` are specified, `g` is ignored. 246 | // Be aware that SABNF grammars, like regular expressions, 247 | // can define empty string (`""`) patterns. 248 | // This oft-given global example can lead to an infinite loop: 249 | //
250 |   // 
251 |   // var exp = /a*/g;
252 |   // while((result = exp.exec("aaba")) !== null){
253 |   // /* do something */
254 |   // }
255 |   // 
256 |   // 
257 | // A better solution would be 258 | //
259 |   // 
260 |   // var grammar = "rule = *a\n";
261 |   // var exp = new `apg-exp`(grammar, "g");
262 |   // while(true){
263 |   // result = exp.exec("aaba");
264 |   // if(result === null){break;}
265 |   // /* do something */
266 |   // /* bump-along mode */
267 |   // if(result[0].length === 0){lastIndex += 1;}
268 |   // }
269 |   // 
270 |   // 
271 | /* public API */ 272 | this.exec = function(str) { 273 | var result = null; 274 | var error; 275 | errorName = thisFileName = "exec(): "; 276 | if (typeof (str) === "string") { 277 | priv.str = str; 278 | priv.chars = apglib.utils.stringToChars(str); 279 | } else if (Array.isArray(str)) { 280 | priv.str = null; 281 | priv.chars = str; 282 | } else { 283 | return result; 284 | } 285 | priv.parser.ast = this.ast; 286 | priv.parser.trace = this.trace; 287 | error = checkParserUdts(errorName); 288 | if(error){ 289 | throw new ApgExpError(errorName + error); 290 | } 291 | if (this.sticky) { 292 | result = execFuncs.execAnchor(priv); 293 | } else { 294 | result = execFuncs.execForward(priv); 295 | } 296 | return result; 297 | } 298 | // Test for a match of the SABNF-defined pattern in the input string. 299 | // Can be called multiple times with the `g` or `y` flags. 300 | // However, see caution above for `exec()`. 301 | this.test = function(str) { 302 | var result = null; 303 | var error; 304 | errorName = thisFileName + "test(): "; 305 | if (typeof (str) === "string") { 306 | priv.str = str; 307 | priv.chars = apglib.utils.stringToChars(str); 308 | } else if (Array.isArray(str)) { 309 | priv.str = null; 310 | priv.chars = str; 311 | } else { 312 | return result; 313 | } 314 | priv.parser.ast = null; 315 | priv.parser.trace = null; 316 | this.ast = null; 317 | this.trace = null; 318 | error = checkParserUdts(errorName); 319 | if(error){ 320 | throw new ApgExpError(errorName + error); 321 | } 322 | if (this.sticky) { 323 | result = execFuncs.testAnchor(priv); 324 | } else { 325 | result = execFuncs.testForward(priv); 326 | } 327 | return result; 328 | } 329 | //
330 |   // 
331 |   // str - the string to find patterns to be replaced in
332 |   // replacement - a string or function defining replacement
333 |   // phrases for the matched phrases.
334 |   // returns str with the matched phrases replaced
335 |   // 
336 |   // 
337 | // This is roughly equivalent to the JavaScript string replacement function, `str.replace(regex, replacement)`. 338 | // (It follows closely the 339 | // [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) description.) 340 | // If the global flag `g` is set, all matched phrases will be replaced, 341 | // otherwise, only the first. 342 | // If the sticky flag `y` is set, all matched 'consecutive' phrases will be replaced, 343 | // otherwise, only the first. 344 | // If the unicode flag `u` is set, an exception will be thrown. `replace()` only works on strings, not character code arrays. 345 | // The `replacement` string may contain the patterns patterns. 346 | //
347 |   // 
348 |   // $$ - insert the character $
349 |   // the escape sequence for the $ character
350 |   // $` - insert the prefix to the matched pattern
351 |   // $& - insert the matched pattern
352 |   // $' - insert the suffix of the matched pattern
353 |   // ${name} - insert the last match to the rule "name"
354 |   // 
355 |   // 
356 | // `replacement` may also be a user-written function of the form 357 | //
358 |   // 
359 |   // var replacement = function(result, exp){}
360 |   // result - the result object from the pattern match
361 |   // exp - the apg-exp object
362 |   // 
363 |   // 
364 | // There is quite a bit of redundancy here with both the `result` object and the `apg-exp` object being passed to the 365 | // replacement function. However, this provides the user with a great deal of flexibility in what might be the 366 | // most convenient way to create the replacement. Also, the `apg-exp` object has the AST which is a powerful 367 | // translation tool for really tough replacement jobs. See the [ast.js]() example. 368 | 369 | this.replace = function(str, replacement) { 370 | errorName = thisFileName + "replace(): "; 371 | if (this.unicode) { 372 | throw new ApgExpError(errorName + "cannot do string replacement in 'unicode' mode. Insure that 'u' flag is absent."); 373 | } 374 | if (typeof (str) !== "string") { 375 | throw new ApgExpError(errorName + "input type error: str not a string"); 376 | } 377 | if (typeof (replacement) === "string") { 378 | return replaceFuncs.replaceString(priv, str, replacement); 379 | } 380 | if (typeof (replacement) === "function") { 381 | return replaceFuncs.replaceFunction(priv, str, replacement); 382 | } 383 | throw new ApgExpError(errorName + "input type error: replacement not a string or function"); 384 | } 385 | //
386 |   // 
387 |   // str - the string to split
388 |   // limit - optional limit on the number of splits
389 |   // 
390 |   // 
391 | // Mimics the JavaScript `String.split(regexp)` function. That is, 392 | // `split(str[, limit])` is roughly equivalent to `str.split(regexp[, limit])` 393 | // Returns an array of strings. 394 | // If `str` is undefined or empty the returned array 395 | // contains a single, empty string. 396 | // Otherwise, `exp.exec(str)` is called in global mode. If a one or more matched phrases are found, they are removed from the 397 | // string 398 | // and the substrings are returned in an array. 399 | // If no matched phrases are found, the array contains one element consisting of the entire string, `["str"]`. 400 | // Empty string matches will split the string and advance `lastIndex` by one character (bump-along mode). 401 | // That means, for example, the grammar `rule=""\n` would match the empty string at every character 402 | // and an array of all characters would be returned. It would be similar to calling the JavaScript function `str.split("")`. 403 | // Unlike the JavaScript function, capturing parentheses (rules) are not spliced into the output string. 404 | // An exception is thrown if the unicode flag is set. `split()` works only on strings, not integer arrays of character codes. 405 | // If the `limit` argument is used, it must be a positive number and no more than `limit` matches will be returned. 406 | this.split = function(str, limit) { 407 | errorName = thisFileName + "split(): "; 408 | if (this.unicode) { 409 | throw new ApgExpError(errorName + "cannot do string split in 'unicode' mode. Insure that 'u' flag is absent."); 410 | } 411 | if (str === undefined || str === null || str === "") { 412 | return [ "" ]; 413 | } 414 | if (typeof (str) !== "string") { 415 | throw new ApgExpError(errorName + "argument must be a string: typeof(arg): " + typeof (str)); 416 | } 417 | if (typeof (limit) !== "number") { 418 | limit = Infinity; 419 | } else { 420 | limit = Math.floor(limit); 421 | if (limit <= 0) { 422 | throw new ApgExpError(errorName + "limit must be >= 0: limit: " + limit); 423 | } 424 | } 425 | return splitFuncs.split(priv, str, limit); 426 | } 427 | // Select specific rule/UDT names to include in the result object. 428 | // `list` is an array of rule/UDT names to include. 429 | // All other names, not in the array, are excluded. 430 | // Excluding a rule/UDT name does not affect the operation of any functions, 431 | // it simply excludes its phrases from the results. 432 | this.include = function(list) { 433 | errorName = thisFileName + "include(): "; 434 | if (list === undefined || list == null || (typeof (list) === "string" && list.toLowerCase() === "all")) { 435 | /* set all to true */ 436 | for ( var name in priv.grammarObject.callbacks) { 437 | _this.ast.callbacks[name] = true; 438 | } 439 | return; 440 | } 441 | if (Array.isArray(list)) { 442 | /* set all to false */ 443 | for ( var name in priv.grammarObject.callbacks) { 444 | _this.ast.callbacks[name] = false; 445 | } 446 | /* then set those in the list to true */ 447 | for (var i = 0; i < list.length; i += 1) { 448 | var l = list[i]; 449 | if (typeof (l) !== "string") { 450 | throw new ApgExpError(errorName + "invalid name type in list"); 451 | } 452 | l = l.toLowerCase(); 453 | if (_this.ast.callbacks[l] === undefined) { 454 | throw new ApgExpError(errorName + "unrecognized name in list: " + list[i]); 455 | } 456 | _this.ast.callbacks[l] = true; 457 | } 458 | return; 459 | } 460 | throw new ApgExpError(errorName + "unrecognized list type"); 461 | } 462 | // Select specific rule/UDT names to exclude in the result object. 463 | // `list` is an array of rule/UDT names to exclude. 464 | // All other names, not in the array, are included. 465 | // Excluding a rule/UDT name does not affect the operation of any functions, 466 | // it simply excludes its phrases from the results. 467 | this.exclude = function(list) { 468 | errorName = thisFileName + "exclude(): "; 469 | if (list === undefined || list == null || (typeof (list) === "string" && list.toLowerCase() === "all")) { 470 | /* set all to false */ 471 | for ( var name in priv.grammarObject.callbacks) { 472 | _this.ast.callbacks[name] = false; 473 | } 474 | return; 475 | } 476 | if (Array.isArray(list)) { 477 | /* set all to true */ 478 | for ( var name in priv.grammarObject.callbacks) { 479 | _this.ast.callbacks[name] = true; 480 | } 481 | /* then set all in list to false */ 482 | for (var i = 0; i < list.length; i += 1) { 483 | var l = list[i]; 484 | if (typeof (l) !== "string") { 485 | throw new ApgExpError(errorName + "invalid name type in list"); 486 | } 487 | l = l.toLowerCase(); 488 | if (_this.ast.callbacks[l] === undefined) { 489 | throw new ApgExpError(errorName + "unrecognized name in list: " + list[i]); 490 | } 491 | _this.ast.callbacks[l] = false; 492 | } 493 | return; 494 | } 495 | throw new ApgExpError(errorName + "unrecognized list type"); 496 | } 497 | // Defines a UDT callback function. *All* UDTs appearing in the SABNF phrase syntax must be defined here. 498 | //

499 |   // name - the (case-insensitive) name of the UDT
500 |   // func - the UDT callback function
501 |   // 
502 | // See the udt example for the callback function details. 503 | this.defineUdt = function(name, func) { 504 | errorName = thisFileName + "defineUdt(): "; 505 | if (typeof (name) !== "string") { 506 | throw new ApgExpError(errorName + "'name' must be a string"); 507 | } 508 | if (typeof (func) !== "function") { 509 | throw new ApgExpError(errorName + "'func' must be a function reference"); 510 | } 511 | var lowerName = name.toLowerCase(); 512 | for (var i = 0; i < priv.grammarObject.udts.length; i += 1) { 513 | if (priv.grammarObject.udts[i].lower === lowerName) { 514 | priv.parser.callbacks[lowerName] = func; 515 | return; 516 | } 517 | } 518 | throw new ApgExpError(errorName + "'name' not a UDT name: " + name); 519 | } 520 | // Estimates the upper bound of the call stack depth for this JavaScript 521 | // engine. Taken from [here](http://www.2ality.com/2014/04/call-stack-size.html) 522 | this.maxCallStackDepth = function() { 523 | try { 524 | return 1 + this.maxCallStackDepth(); 525 | } catch (e) { 526 | return 1; 527 | } 528 | } 529 | // Returns the "last match" information in the `apg-exp` object in ASCII text. 530 | // Patterned after and similar to the JavaScript 531 | // [`RegExp` properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). 532 | this.toText = function(mode) { 533 | if (this.unicode) { 534 | // mode: 535 | // - "ascii", (default) display characters arrays as ASCII text 536 | // - "decimal", display character arrays as decimal integers 537 | // - "hexidecimal", display character arrays as hexidecimal (\xHH) integers 538 | // - "unicode", display character arrays as unicode (\uHH) integers 539 | return resultFuncs.u.expToText(this, mode); 540 | } 541 | return resultFuncs.s.expToText(this); 542 | } 543 | // Returns the "last match" information in the `apg-exp` object formatted as an HTML table. 544 | this.toHtml = function(mode) { 545 | if (this.unicode) { 546 | // *see mode definitions above 547 | return resultFuncs.u.expToHtml(this, mode); 548 | } 549 | return resultFuncs.s.expToHtml(this); 550 | } 551 | // Same as `toHtml()` except the output is a complete HTML page. 552 | this.toHtmlPage = function(mode) { 553 | if (this.unicode) { 554 | // *see mode definitions above 555 | return resultFuncs.u.expToHtmlPage(this, mode); 556 | } 557 | return resultFuncs.s.expToHtmlPage(this); 558 | } 559 | // Returns the SABNF syntax or grammar defining the pattern in ASCII text format. 560 | this.sourceToText = function() { 561 | return resultFuncs.s.sourceToText(this); 562 | } 563 | // Returns the SABNF syntax or grammar defining the pattern in HTML format. 564 | this.sourceToHtml = function() { 565 | return resultFuncs.s.sourceToHtml(this); 566 | } 567 | // Returns the SABNF syntax or grammar defining the pattern as a complete HTML page. 568 | this.sourceToHtmlPage = function() { 569 | return resultFuncs.s.sourceToHtmlPage(this); 570 | } 571 | }; --------------------------------------------------------------------------------