├── .eslintrc ├── .gitignore ├── README.md ├── api.md ├── internal-api.md ├── lib ├── assert-no-self-loops.js ├── decision-graph.js ├── grammar-graph.js ├── guided-decision-graph.js ├── parse-grammar.js ├── recognizer.js ├── reduce-grammar.js └── utils │ └── clone.js ├── package.json └── test ├── assert-no-self-loops.test.js ├── decision-graph.test.js ├── grammar-graph.test.js ├── guided-decision-graph.test.js ├── parse-grammar.test.js ├── recognizer.test.js ├── reduce-grammar.test.js └── utils └── clone.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "root": true, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "indent": [ 10 | 2, 11 | 2, 12 | { "ArrayExpression": "first" } 13 | ], 14 | "key-spacing": [ 15 | 1, 16 | { "align": { 17 | "beforeColon": true, 18 | "afterColon": true, 19 | "on": "colon", 20 | "mode": "strict" 21 | } 22 | } 23 | ], 24 | "linebreak-style": [ 25 | 2, 26 | "unix" 27 | ], 28 | "quotes": [ 29 | 2, 30 | "single" 31 | ], 32 | "semi": [ 33 | 2, 34 | "never" 35 | ] 36 | } 37 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Interactively construct a sentence from a [context-free grammar](https://en.wikipedia.org/wiki/Context-free_grammar), or check if some text is a valid sentence in a grammar. 2 | 3 | 4 | ## Create a GrammarGraph 5 | 6 | Install the npm module. 7 | ``` 8 | npm install grammar-graph 9 | ``` 10 | 11 | Require GrammarGraph, input a grammar, and construct a new graph. See [grammar format](https://github.com/jrleszcz/grammar-graph#grammar) for details on how a grammar works. 12 | ```js 13 | var GrammarGraph = require('grammar-graph') 14 | 15 | var grammar = { 16 | Sentence: ['NounPhrase VerbPhrase'], 17 | NounPhrase: ['the Noun', 18 | 'the Noun RelativeClause'], 19 | VerbPhrase: ['Verb', 20 | 'Verb NounPhrase'], 21 | RelativeClause: ['that VerbPhrase'], 22 | Noun: ['dog', 23 | 'cat', 24 | 'bird', 25 | 'squirrel'], 26 | Verb: ['befriended', 27 | 'loved', 28 | 'ate', 29 | 'attacked'] 30 | } 31 | 32 | var graph = new GrammarGraph(grammar) 33 | ``` 34 | 35 | 36 | Check out the vertices in your graph. The constructor creates a vertex for every terminal and non-terminal symbol in the grammar. 37 | ```js 38 | graph.vertices() => 39 | [ 'Sentence', 'NounPhrase', 'VerbPhrase', 'RelativeClause', 'Noun', 40 | 'Verb', '_NounPhrase_1', '_NounPhrase_2', '_VerbPhrase_1', 'that', 41 | 'dog', 'cat', 'bird', 'squirrel', 'befriended', 'loved', 'ate', 42 | 'attacked', 'the' ] 43 | ``` 44 | Where did `'_NounPhrase_1'`, `'_NounPhrase_2'`, and `'_VerbPhrase_1'` come from? Look at the definition of `NounPhrase` in the original grammar declaration. Both options contained multiple symbols, and the constructor has automatically created a name for each combination. In the case of `VerbPhrase`, only the second option contained multiple symbols, so only one extra name is needed. The automatic expansion of the original `NounPhrase` and `VerbPhrase` definitions result in the following equivalent definitions: 45 | ```js 46 | { 47 | NounPhrase: ['_NounPhrase_1', 48 | '_NounPhrase_2'], 49 | VerbPhrase: ['Verb', 50 | '_VerbPhrase_1'], 51 | _NounPhrase_1: ['the Noun'], 52 | _NounPhrase_2: ['the Noun RelativeClause'], 53 | _VerbPhrase_1: ['Verb NounPhrase'] 54 | } 55 | ``` 56 | 57 | 58 | ## GrammarGraph.createGuide 59 | 60 | Let's create a new guide for constructing sentences from the langauge. Just indicate a starting point in the grammar, in this case `Sentence`. The guide will help you construct a complete Sentence. 61 | ```js 62 | var guide = graph.createGuide('Sentence') 63 | ``` 64 | 65 | The guide gives choices for the next terminal in your construction. Behind the scenes, it is doing a breadth-first search for terminals from the current position. In our grammar, the only possible first terminal is `'the'`: 66 | ```js 67 | guide.choices() => ['the'] 68 | ``` 69 | 70 | You can also check all the possible constructs at any point in time. In this case, we will see that even though `'the'` is the only possible first terminal, there are actually two possible paths for the construction. 71 | ```js 72 | guide.constructs() => 73 | [ 'the Noun RelativeClause VerbPhrase', 'the Noun VerbPhrase' ] 74 | ``` 75 | To get to this point, the Guide has expanded `'Sentence'` => `'NounPhrase VerbPhrase'` => `'the Noun RelativeClause VerbPhrase'` **or** `'the Noun VerbPhrase'`. It stops at this point because it has reached terminal symbol `'the'` in both possible paths. 76 | 77 | 78 | `'the'` is the only choice, so let's choose it. We can then check our construction and possible constructs. 79 | ```js 80 | guide.choose('the') 81 | guide.construction() => ['the'] 82 | guide.constructs() => 83 | [ 'the bird RelativeClause VerbPhrase', 84 | 'the bird VerbPhrase', 85 | 'the cat RelativeClause VerbPhrase', 86 | 'the cat VerbPhrase', 87 | 'the dog RelativeClause VerbPhrase', 88 | 'the dog VerbPhrase', 89 | 'the squirrel RelativeClause VerbPhrase', 90 | 'the squirrel VerbPhrase' ] 91 | guide.choices() => ['squirrel', 'bird', 'cat', 'dog' ] 92 | ``` 93 | 94 | 95 | Let's continue the construction. 96 | ```js 97 | guide.choices() => ['dog', 'cat', 'squirrel', 'bird'] 98 | guide.choose('dog') 99 | 100 | guide.choices() => ['that', 'befriended', 'loved', 'ate', 'attacked'] 101 | guide.choose('ate') 102 | 103 | guide.choices() => ['the'] 104 | ``` 105 | 106 | We could choose 'the' from this last set of choices, but it just so happens that the current construction could be considered a complete `Sentence` (our starting point): 107 | ```js 108 | guide.construction() => ['the', 'dog', 'ate'] 109 | guide.isComplete() => true 110 | guide.constructs() => 111 | [ 'the dog ate', 112 | 'the dog ate the Noun', 113 | 'the dog ate the Noun RelativeClause' ] 114 | ``` 115 | 116 | If we go ahead and choose 'the', we no longer have a complete sentence. 117 | ```js 118 | guide.choose('the') 119 | guide.construction() => ['the', 'dog', 'ate', 'the'] 120 | guide.isComplete() => false 121 | ``` 122 | 123 | At any point, you can move back a step by popping off the last choice. 124 | ```js 125 | guide.pop() => 'the' 126 | guide.construction() => ['the', 'dog', 'ate'] 127 | guide.isComplete() => true 128 | ``` 129 | 130 | You can optionally provide `guide.choices()` with a number indicating the depth of choices you want. If you request a depth greater than 1, instead of an array of strings, it will return an array of TreeNodes which are each at most nDeep (or less if a path ends in a terminal). 131 | 132 | ```js 133 | guide.choose('the') 134 | guide.construction() => ['the', 'dog', 'ate', 'the'] 135 | guide.choices() => ['squirrel', 'bird', 'cat', 'dog'] 136 | guide.choices(3) => 137 | [ { val: 'squirrel', // squirrel 138 | next: [ { val: 'that', // squirrel that 139 | next: 140 | [ { val: 'attacked', next: [] }, // squirrel that attacked 141 | { val: 'ate', next: [] }, // squirrel that ate 142 | { val: 'loved', next: [] }, // squirrel that loved 143 | { val: 'befriended', next: [] } // squirrel that befriended 144 | ] 145 | }, 146 | 147 | { val: 'bird', // bird 148 | next: [ { val: 'that', // bird that 149 | next: 150 | [ { val: 'attacked', next: [] }, // bird that attacked 151 | { val: 'ate', next: [] }, // bird that ate 152 | { val: 'loved', next: [] }, // bird that loved 153 | { val: 'befriended', next: [] } // bird that befriended 154 | ] 155 | }, 156 | 157 | { val: 'cat', 158 | next: [ etc... ] }, 159 | 160 | { val: 'dog', 161 | next: [ etc... ] } 162 | ] 163 | ``` 164 | 165 | In addition to a single terminal string, `guide.choose()` can also accept an array of terminal strings. 166 | ```js 167 | guide.choose(['squirrel', 'that', 'attacked']) 168 | guide.construction() => ['the', 'dog', 'ate', 'the', 'squirrel', 'that', 'attacked'] 169 | guide.complete() => true 170 | ``` 171 | 172 | ## GrammarGraph.createRecognizer 173 | A `Recognizer` can be used to check if some text is a valid sentence in a grammar. Just like when creating a guide, you need to indicate a starting terminal: 174 | ```js 175 | // (using graph declared before) var graph = new GrammarGraph(grammar) 176 | var sentence = graph.createRecognizer('Sentence') 177 | ``` 178 | A recognizer can check whether or not a text is a valid and complete construction: 179 | ```js 180 | sentence.isComplete('the dog ate the cat') => true 181 | sentence.isComplete('the dog ate the cat that') => false 182 | sentence.isComplete('the dog ate the cat that orange juice') => false 183 | sentence.isComplete('the dog ate the cat that attacked') => true 184 | ``` 185 | 186 | or whether the text is valid so far (though it may not be complete): 187 | ```js 188 | sentence.isValid('the dog ate the cat') => true 189 | sentence.isValid('the dog ate the cat that') => true 190 | sentence.isValid('the dog ate the cat that orange juice') => false 191 | sentence.isValid('the dog ate the cat that attacked') => true 192 | ``` 193 | 194 | 195 | ## Grammar 196 | A context-free grammar is a list of rules. Here is a grammar with eight rules that builds creatures like this: 197 | 198 | `~~(^__^)~~` or `~~(-______-)~~` or `~~(*_*)~~`. 199 | 200 | ```js 201 | { 202 | Creature: ['Arm Head Arm'], 203 | Head: ['( Face )'], 204 | Face: ['HappyFace', 205 | 'ZenFace', 206 | 'SleepyFace'], 207 | HappyFace: ['^ Mouth ^'], 208 | ZenFace: ['- Mouth -'], 209 | SleepyFace: ['* Mouth *'], 210 | Mouth: ['_', 211 | '_ Mouth'], 212 | Arm: ['~~'] 213 | } 214 | ``` 215 | 216 | #### Rules 217 | A rule simply means to replace a word like `Creature` with its definition. If we are [constructing](https://github.com/jrleszcz/grammar-graph#building-a-creature) a sentence with the Creature grammar and come across the word `Head`, we will replace it with its definition: `( Face )`. Some rules have multiple options, such as a `Face` which can be rewritten as `HappyFace`, `ZenFace`, or `SleepyFace`. 218 | 219 | More formally, a grammar is an object consisting of key-value pairs, with each [non-terminal symbol](https://github.com/jrleszcz/grammar-graph#non-terminal-symbols) pointing to an array of one or more [symbol chains](https://github.com/jrleszcz/grammar-graph#symbol-chains) choices for this non-terminal. 220 | 221 | #### Symbol Chains 222 | `Arm Head Arm` and `( Face )` are symbol chains. By default, each symbol is seperated by white-space, so both of these symbol chains are made up of three symbols: `Arm, Head, Arm` and `(, Face, )`. 223 | 224 | #### Terminal Symbols 225 | If a symbol has no definition in the grammar, it is a terminal. The six terminal symbols in the creature grammar are: `(, ), ^, *, _, ~~`. These are the actual building blocks of the language, and are the only symbols that will make it into a final construction. 226 | 227 | #### Non-terminal Symbols 228 | If a symbol has a definition in the grammar, it is non-terminal and can be broken down further. A non-terminal's definition is an array of one or more symbol chains indicating possible choices for this rule. 229 | ``` 230 | { 231 | RuleName: ['I am this', 'or this', 'or could be this'], 232 | RuleName2: ['I mean only one thing'] 233 | } 234 | ``` 235 | #### Recursive definitions 236 | Recursive definitions are what make a grammar interesting and powerful. The creature grammar has only one recursive definition: `Mouth: ['_', '_ Mouth']`. This allows creatures to have mouths of one character if we immediately choose the first option, or up to infinite characters if we always choose the second option. 237 | 238 | Do not define a non-terminal to equal only itself. This will not work: `Mouth: ['Mouth']`. 239 | 240 | #### Building a Creature 241 | When constructing from a grammar, you need to indicate a starting point. In this case it only makes sense to start from `Creature`. Let's break down `Creature` until we are left with only terminal symbols. 242 | ``` 243 | // construction // replacement on this step 244 | 245 | Creature // Creature => Arm Head Arm 246 | Arm Head Arm // Arm => ~~ 247 | ~~Head Arm // Head => ( Face ) 248 | ~~(Face) Arm // Face => ZenFace 249 | ~~(ZenFace) Arm // Mouth => _ Mouth 250 | ~~(-Mouth-) Arm // Mouth => _ Mouth 251 | ~~(-_Mouth-) Arm // Mouth => _ Mouth 252 | ~~(-__Mouth-) Arm // Mouth => _ 253 | ~~(-___-) Arm // Arm => ~~ 254 | ~~(-___-)~~ 255 | ``` 256 | 257 | 258 | ## Docs 259 | [View the api documentation here.](api.md) 260 | 261 | ## Development 262 | [View the internal api documentation here.](internal-api.md) 263 | 264 | Clone the git repository and install development dependencies: 265 | ``` 266 | git clone https://github.com/jrleszcz/grammar-graph.git 267 | cd grammar-graph 268 | npm install 269 | ``` 270 | 271 | To run eslint and tape tests: 272 | ``` 273 | npm test 274 | ``` 275 | 276 | To generate api documentation for [api.md](api.md): 277 | ``` 278 | npm run docs 279 | ``` 280 | 281 | ## Credit 282 | This module is based on Alex Shkotin's [Graph representation of context-free grammars](http://arxiv.org/pdf/cs/0703015.pdf). -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
GrammarGraph
5 |
6 |
GuidedDecisionGraph
7 |
8 |
Recognizer
9 |
10 |
11 | 12 | ## Typedefs 13 | 14 |
15 |
SymbolChain : string
16 |

a string of one or more symbol names seperated by whitespace or 17 | another user defined seperator (see: seperator param for GrammarGraph)

18 |
19 |
Grammar : Object
20 |

a user defined context-free grammar formatted as an object consisting of key-value pairs, 21 | with each non-terminal symbol 22 | pointing to an array of one or more symbol chains 23 | choices for this non-terminal.

24 |
25 |
TreeNode : object
26 |
27 |
28 | 29 | 30 | ## GrammarGraph 31 | **Kind**: global class 32 | 33 | * [GrammarGraph](#GrammarGraph) 34 | * [new GrammarGraph(grammar, [seperator], [epsilonSymbol])](#new_GrammarGraph_new) 35 | * [.vertices()](#GrammarGraph+vertices) ⇒ Array.<string> 36 | * [.adj(v)](#GrammarGraph+adj) ⇒ Array.<string> 37 | * [.isTypeAND(v)](#GrammarGraph+isTypeAND) ⇒ boolean 38 | * [.createGuide(start)](#GrammarGraph+createGuide) ⇒ [GuidedDecisionGraph](#GuidedDecisionGraph) 39 | * [.createRecognizer(start)](#GrammarGraph+createRecognizer) ⇒ [Recognizer](#Recognizer) 40 | 41 | 42 | ### new GrammarGraph(grammar, [seperator], [epsilonSymbol]) 43 | creates a new GrammarGraph which can generate guides. 44 | 45 | 46 | | Param | Type | Default | Description | 47 | | --- | --- | --- | --- | 48 | | grammar | [Grammar](#Grammar) | | an object representing a grammar. | 49 | | [seperator] | string | RegExp | "/\\s+/" | how tokens will be divided in rules | 50 | | [epsilonSymbol] | string | "''" | Special terminal symbol that indicates this is an end of a construction. Defaults to the empty string. | 51 | 52 | 53 | ### grammarGraph.vertices() ⇒ Array.<string> 54 | get an array of vertex names in the graph 55 | 56 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 57 | **Returns**: Array.<string> - the vertex names in this graph 58 | **See**: [DecisionGraph#vertices](DecisionGraph#vertices) 59 | 60 | ### grammarGraph.adj(v) ⇒ Array.<string> 61 | get an array of all the vertices this vertex points to 62 | 63 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 64 | **Returns**: Array.<string> - an ordered list of all the vertices that v points to 65 | **See**: [DecisionGraph#adj](DecisionGraph#adj) 66 | 67 | | Param | Type | Description | 68 | | --- | --- | --- | 69 | | v | string | the name of a vertex | 70 | 71 | 72 | ### grammarGraph.isTypeAND(v) ⇒ boolean 73 | is this a type AND vertex (and not a type OR)? 74 | 75 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 76 | **Returns**: boolean - is this a type AND vertex (and not a type OR)? 77 | **See**: [DecisionGraph#isTypeAND](DecisionGraph#isTypeAND) 78 | 79 | | Param | Type | Description | 80 | | --- | --- | --- | 81 | | v | string | the name of a vertex | 82 | 83 | 84 | ### grammarGraph.createGuide(start) ⇒ [GuidedDecisionGraph](#GuidedDecisionGraph) 85 | get a new GuidedDecisionGraph using this decision graph 86 | 87 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 88 | **Returns**: [GuidedDecisionGraph](#GuidedDecisionGraph) - a new guide from the provided start point 89 | **See**: [GuidedDecisionGraph](#GuidedDecisionGraph) for the methods available on the Guide 90 | 91 | | Param | Type | Description | 92 | | --- | --- | --- | 93 | | start | string | the name of a vertex in the decision graph from which to start the guided expansion | 94 | 95 | 96 | ### grammarGraph.createRecognizer(start) ⇒ [Recognizer](#Recognizer) 97 | Returns a new Recognizer from the given start vertex 98 | 99 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 100 | **Returns**: [Recognizer](#Recognizer) - a new Recognizer 101 | 102 | | Param | Type | Description | 103 | | --- | --- | --- | 104 | | start | string | the name of a vertex in the decision graph from which to start the recognizer test | 105 | 106 | 107 | ## GuidedDecisionGraph 108 | **Kind**: global class 109 | 110 | * [GuidedDecisionGraph](#GuidedDecisionGraph) 111 | * [new GuidedDecisionGraph(dg, start)](#new_GuidedDecisionGraph_new) 112 | * [.construction()](#GuidedDecisionGraph+construction) ⇒ Array.<string> 113 | * [.isComplete()](#GuidedDecisionGraph+isComplete) ⇒ boolean 114 | * [.choose(terminal)](#GuidedDecisionGraph+choose) 115 | * [.constructs()](#GuidedDecisionGraph+constructs) ⇒ Array.<string> 116 | * [.pop()](#GuidedDecisionGraph+pop) ⇒ string 117 | * [.choices([nDeep])](#GuidedDecisionGraph+choices) ⇒ Array.<string> | [Array.<TreeNode>](#TreeNode) 118 | 119 | 120 | ### new GuidedDecisionGraph(dg, start) 121 | step-by-step construction of a language from a decision graph 122 | 123 | 124 | | Param | Type | Description | 125 | | --- | --- | --- | 126 | | dg | DecisionGraph | a Decision Graph that defines a grammar | 127 | | start | string | the name of a vertex in the decision graph from which to start the guided expansion | 128 | 129 | 130 | ### guidedDecisionGraph.construction() ⇒ Array.<string> 131 | the current construction 132 | 133 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 134 | **Returns**: Array.<string> - a terminal symbol chain 135 | 136 | ### guidedDecisionGraph.isComplete() ⇒ boolean 137 | is the current construction a valid, complete construction from the starting 138 | nonterminal? ie, could the construction be haulted at this point? Depending 139 | on the grammar, this may be true even if there are more choices at this point. 140 | 141 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 142 | **Returns**: boolean - is the construction complete 143 | 144 | ### guidedDecisionGraph.choose(terminal) 145 | adds the given terminal to the construction 146 | 147 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 148 | 149 | | Param | Type | Description | 150 | | --- | --- | --- | 151 | | terminal | string | Array.<string> | the name of a terminal vertex in the Decision Graph which is in the current set of possible choices. Or a valid sequence of terminal symbols as an array. | 152 | 153 | 154 | ### guidedDecisionGraph.constructs() ⇒ Array.<string> 155 | get a sorted array of possible construction strings from the current state, 156 | possibly including nonterminals after the next terminal 157 | 158 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 159 | **Returns**: Array.<string> - a list of possible constructions 160 | **Example** 161 | ```js 162 | // guide is an in-progress GuidedDecisionGraph 163 | guide.construction() => ['the', 'dog', 'ate'] 164 | guide.choices() => ['', 'the'] 165 | guide.constructs() 166 | => [ 'the dog ate', 167 | 'the dog ate the Noun' 168 | 'the dog ate the Noun RelativeClause' ] 169 | ``` 170 | 171 | ### guidedDecisionGraph.pop() ⇒ string 172 | pop the last choice off the construction 173 | 174 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 175 | **Returns**: string - the last element of the construction that was 176 | submitted through [choose](#GuidedDecisionGraph+choose) 177 | **Throws**: 178 | 179 | - throws an error if called when construction is empty 180 | 181 | 182 | ### guidedDecisionGraph.choices([nDeep]) ⇒ Array.<string> | [Array.<TreeNode>](#TreeNode) 183 | returns all possible next terminals, or an array of nDeep [TreeNodes](#TreeNode) 184 | 185 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 186 | **Returns**: Array.<string> | [Array.<TreeNode>](#TreeNode) - if nDeep=1, an array of terminal symbols (strings), 187 | else an array of [TreeNodes](#TreeNode) 188 | 189 | | Param | Type | Default | Description | 190 | | --- | --- | --- | --- | 191 | | [nDeep] | number | 1 | will search for nDeep possible choices | 192 | 193 | **Example** 194 | ```js 195 | // guide is an in-progress GuidedDecisionGraph 196 | guide.construction() => ['the', 'dog', 'ate'] 197 | guide.choices() => ['', 'the'] 198 | guide.choices(3) => 199 | [ { val: '', 200 | next: [] }, 201 | { val: 'the', 202 | next: [ { val: 'squirrel', 203 | next: [ { val: 'that', next: [] }, 204 | { val: '', next: [] } ] 205 | }, 206 | { val: 'bird', 207 | next: [ { val: 'that', next: [] }, 208 | { val: '', next: [] } ] 209 | }, 210 | { val: 'cat', 211 | next: [ { val: 'that', next: [] }, 212 | { val: '', next: [] } ] 213 | }, 214 | { val: 'dog', 215 | next: [ { val: 'that', next: [] }, 216 | { val: '', next: [] } ] 217 | } 218 | ] 219 | } 220 | ] 221 | ``` 222 | 223 | ## Recognizer 224 | **Kind**: global class 225 | 226 | * [Recognizer](#Recognizer) 227 | * [new Recognizer(dg, start, [seperator])](#new_Recognizer_new) 228 | * [.isValid(text)](#Recognizer+isValid) ⇒ boolean 229 | * [.isComplete(text)](#Recognizer+isComplete) ⇒ boolean 230 | 231 | 232 | ### new Recognizer(dg, start, [seperator]) 233 | create a Recognizer that can test if text is a valid sentence in a grammar 234 | 235 | 236 | | Param | Type | Default | Description | 237 | | --- | --- | --- | --- | 238 | | dg | DecisionGraph | | a Decision Graph that defines a grammar | 239 | | start | string | | the name of a vertex in the decision graph from which to start the test | 240 | | [seperator] | string | RegExp | "/\\s+/" | how tokens will be divided in given text | 241 | 242 | 243 | ### recognizer.isValid(text) ⇒ boolean 244 | is the text a valid in progress sentence in the grammar? Will return true 245 | even if the text is not complete. 246 | 247 | **Kind**: instance method of [Recognizer](#Recognizer) 248 | **Returns**: boolean - is the text valid? 249 | 250 | | Param | Type | Description | 251 | | --- | --- | --- | 252 | | text | string | the text to check | 253 | 254 | 255 | ### recognizer.isComplete(text) ⇒ boolean 256 | is the text a valid and complete text in the grammar? Will return true 257 | only if the text is complete. 258 | 259 | **Kind**: instance method of [Recognizer](#Recognizer) 260 | **Returns**: boolean - is the text valid and complete? 261 | 262 | | Param | Type | Description | 263 | | --- | --- | --- | 264 | | text | string | the text to check | 265 | 266 | 267 | ## SymbolChain : string 268 | a string of one or more symbol names seperated by whitespace or 269 | another user defined seperator (see: seperator param for [GrammarGraph](#GrammarGraph)) 270 | 271 | **Kind**: global typedef 272 | **See**: a SymbolChain is used as definitions in [Grammar](#Grammar) 273 | **Example** 274 | ```js 275 | 'dog' // just a single symbol, the word 'dog' 276 | 'the Noun RelativeClause' // three symbols 277 | ``` 278 | 279 | ## Grammar : Object 280 | a user defined context-free grammar formatted as an object consisting of key-value pairs, 281 | with each [non-terminal symbol](https://github.com/jrleszcz/grammar-graph#non-terminal-symbols) 282 | pointing to an array of one or more [symbol chains](https://github.com/jrleszcz/grammar-graph#symbol-chains) 283 | choices for this non-terminal. 284 | 285 | **Kind**: global typedef 286 | **Properties** 287 | 288 | | Name | Type | Description | 289 | | --- | --- | --- | 290 | | symbol | [Array.<SymbolChain>](#SymbolChain) | each element of the array is a possible definition for this symbol. | 291 | 292 | **Example** 293 | ```js 294 | var grammar = { 295 | Sentence: ['NounPhrase VerbPhrase'], // only one definition of 'Sentence' 296 | NounPhrase: ['the Noun', 'the Noun RelativeClause'], // two possible definitions of 'NounPhrase' 297 | VerbPhrase: ['Verb', 'Verb NounPhrase'], 298 | RelativeClause: ['that VerbPhrase'], 299 | Noun: ['dog', 'cat', 'bird', 'squirrel'], // four possible definitions of 'Noun' 300 | Verb: ['befriended', 'loved', 'ate', 'attacked'] 301 | } 302 | // non-terminals: Sentence, NounPhrase, VerbPhrase, RelativeClause, Noun, Verb 303 | // terminals: the, that, dog, cat, bird, squirrel, befriended, loved, ate, attacked 304 | ``` 305 | 306 | ## TreeNode : object 307 | **Kind**: global typedef 308 | **Properties** 309 | 310 | | Name | Type | Description | 311 | | --- | --- | --- | 312 | | val | string | a terminal string | 313 | | next | [Array.<TreeNode>](#TreeNode) | a list of TreeNodes this node links to | 314 | 315 | -------------------------------------------------------------------------------- /internal-api.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
DecisionGraph
5 |
6 |
GrammarGraph
7 |
8 |
GuidedDecisionGraph
9 |
10 |
Recognizer
11 |
12 |
13 | 14 | ## Functions 15 | 16 |
17 |
noSelfDefinitions(grammar, returns)
18 |

check a grammar for direct self-loops

19 |
20 |
parseGrammar(grammar, [seperator])DecisionGraph
21 |

parse a grammar given as an object and compile it into a decision graph

22 |
23 |
reduceGrammar(grammar, [seperator])object
24 |

reduces the rules of a grammar into a one to one form by assigning a name 25 | to all non-terminals. The end result is that each option on a rule with 26 | more than one choice will either be a single AND-rule or a single terminal.

27 |
28 |
clone(obj)object | array
29 |

helper function to clone a simple object/array made up of primitives. 30 | Will not work if the object or array contains non-primitives.

31 |
32 |
33 | 34 | ## Typedefs 35 | 36 |
37 |
SymbolChain : string
38 |

a string of one or more symbol names seperated by whitespace or 39 | another user defined seperator (see: seperator param for GrammarGraph)

40 |
41 |
Grammar : Object
42 |

a user defined context-free grammar formatted as an object consisting of key-value pairs, 43 | with each non-terminal symbol 44 | pointing to an array of one or more symbol chains 45 | choices for this non-terminal.

46 |
47 |
TreeNode : object
48 |
49 |
50 | 51 | 52 | ## DecisionGraph 53 | **Kind**: global class 54 | 55 | * [DecisionGraph](#DecisionGraph) 56 | * [new DecisionGraph()](#new_DecisionGraph_new) 57 | * [.addVertexAND(name)](#DecisionGraph+addVertexAND) 58 | * [.addVertexOR(name)](#DecisionGraph+addVertexOR) 59 | * [.addEdge(v, w)](#DecisionGraph+addEdge) 60 | * [.adj(v)](#DecisionGraph+adj) ⇒ Array.<string> 61 | * [.V()](#DecisionGraph+V) ⇒ number 62 | * [.isTerminal(v)](#DecisionGraph+isTerminal) ⇒ boolean 63 | * [.isVertex(v)](#DecisionGraph+isVertex) ⇒ boolean 64 | * [.isTypeAND(v)](#DecisionGraph+isTypeAND) ⇒ boolean 65 | * [.vertices()](#DecisionGraph+vertices) ⇒ Array.<string> 66 | 67 | 68 | ### new DecisionGraph() 69 | creates a new DecisionGraph 70 | 71 | 72 | ### decisionGraph.addVertexAND(name) 73 | add AND vertex to the graph. When moving through the decision graph, an AND vertex 74 | will require a visit down each of its outgoing edges, in the order the edges were 75 | added. 76 | 77 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 78 | **See**: [addVertexOR](#DecisionGraph+addVertexOR) 79 | 80 | | Param | Type | Description | 81 | | --- | --- | --- | 82 | | name | string | the name of this vertex | 83 | 84 | 85 | ### decisionGraph.addVertexOR(name) 86 | add OR vertex to the graph. When moving through the decision graph, an OR vertex 87 | chooses just one of its outgoing vertices. 88 | 89 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 90 | **See**: [addVertexAND](#DecisionGraph+addVertexAND) 91 | 92 | | Param | Type | Description | 93 | | --- | --- | --- | 94 | | name | string | the name of this vertex | 95 | 96 | 97 | ### decisionGraph.addEdge(v, w) 98 | add edge v->w to the graph 99 | 100 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 101 | 102 | | Param | Type | Description | 103 | | --- | --- | --- | 104 | | v | string | the name of a vertex this edge points from | 105 | | w | string | Array.<string> | the name of a vertex this edge points to or an array of vertex names. If vertex v is type AND, the order of w will be the exact order required. | 106 | 107 | 108 | ### decisionGraph.adj(v) ⇒ Array.<string> 109 | get an array of all the vertices this vertex points to 110 | 111 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 112 | **Returns**: Array.<string> - an ordered list of all the vertices that v points to 113 | 114 | | Param | Type | Description | 115 | | --- | --- | --- | 116 | | v | string | the name of a vertex | 117 | 118 | 119 | ### decisionGraph.V() ⇒ number 120 | get the number of vertices in this graph 121 | 122 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 123 | **Returns**: number - the number of vertices in this graph 124 | 125 | ### decisionGraph.isTerminal(v) ⇒ boolean 126 | is this a terminal vertex (does it have no outgoing edges?) 127 | 128 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 129 | **Returns**: boolean - is this a terminal vertex 130 | 131 | | Param | Type | Description | 132 | | --- | --- | --- | 133 | | v | string | the name of a vertex | 134 | 135 | 136 | ### decisionGraph.isVertex(v) ⇒ boolean 137 | is this the name of a vertex in the graph? 138 | 139 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 140 | **Returns**: boolean - is this a vertex in the graph 141 | 142 | | Param | Type | Description | 143 | | --- | --- | --- | 144 | | v | string | the name of a vertex | 145 | 146 | 147 | ### decisionGraph.isTypeAND(v) ⇒ boolean 148 | is this a type AND vertex (and not a type OR)? 149 | 150 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 151 | **Returns**: boolean - is this a type AND vertex (and not a type OR)? 152 | 153 | | Param | Type | Description | 154 | | --- | --- | --- | 155 | | v | string | the name of a vertex | 156 | 157 | 158 | ### decisionGraph.vertices() ⇒ Array.<string> 159 | get an array of vertex names 160 | 161 | **Kind**: instance method of [DecisionGraph](#DecisionGraph) 162 | **Returns**: Array.<string> - the vertex names in this graph 163 | 164 | ## GrammarGraph 165 | **Kind**: global class 166 | 167 | * [GrammarGraph](#GrammarGraph) 168 | * [new GrammarGraph(grammar, [seperator], [epsilonSymbol])](#new_GrammarGraph_new) 169 | * [.vertices()](#GrammarGraph+vertices) ⇒ Array.<string> 170 | * [.adj(v)](#GrammarGraph+adj) ⇒ Array.<string> 171 | * [.isTypeAND(v)](#GrammarGraph+isTypeAND) ⇒ boolean 172 | * [.createGuide(start)](#GrammarGraph+createGuide) ⇒ [GuidedDecisionGraph](#GuidedDecisionGraph) 173 | * [.createRecognizer(start)](#GrammarGraph+createRecognizer) ⇒ [Recognizer](#Recognizer) 174 | 175 | 176 | ### new GrammarGraph(grammar, [seperator], [epsilonSymbol]) 177 | creates a new GrammarGraph which can generate guides. 178 | 179 | 180 | | Param | Type | Default | Description | 181 | | --- | --- | --- | --- | 182 | | grammar | [Grammar](#Grammar) | | an object representing a grammar. | 183 | | [seperator] | string | RegExp | "/\\s+/" | how tokens will be divided in rules | 184 | | [epsilonSymbol] | string | "''" | Special terminal symbol that indicates this is an end of a construction. Defaults to the empty string. | 185 | 186 | 187 | ### grammarGraph.vertices() ⇒ Array.<string> 188 | get an array of vertex names in the graph 189 | 190 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 191 | **Returns**: Array.<string> - the vertex names in this graph 192 | **See**: [vertices](#DecisionGraph+vertices) 193 | 194 | ### grammarGraph.adj(v) ⇒ Array.<string> 195 | get an array of all the vertices this vertex points to 196 | 197 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 198 | **Returns**: Array.<string> - an ordered list of all the vertices that v points to 199 | **See**: [adj](#DecisionGraph+adj) 200 | 201 | | Param | Type | Description | 202 | | --- | --- | --- | 203 | | v | string | the name of a vertex | 204 | 205 | 206 | ### grammarGraph.isTypeAND(v) ⇒ boolean 207 | is this a type AND vertex (and not a type OR)? 208 | 209 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 210 | **Returns**: boolean - is this a type AND vertex (and not a type OR)? 211 | **See**: [isTypeAND](#DecisionGraph+isTypeAND) 212 | 213 | | Param | Type | Description | 214 | | --- | --- | --- | 215 | | v | string | the name of a vertex | 216 | 217 | 218 | ### grammarGraph.createGuide(start) ⇒ [GuidedDecisionGraph](#GuidedDecisionGraph) 219 | get a new GuidedDecisionGraph using this decision graph 220 | 221 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 222 | **Returns**: [GuidedDecisionGraph](#GuidedDecisionGraph) - a new guide from the provided start point 223 | **See**: [GuidedDecisionGraph](#GuidedDecisionGraph) for the methods available on the Guide 224 | 225 | | Param | Type | Description | 226 | | --- | --- | --- | 227 | | start | string | the name of a vertex in the decision graph from which to start the guided expansion | 228 | 229 | 230 | ### grammarGraph.createRecognizer(start) ⇒ [Recognizer](#Recognizer) 231 | Returns a new Recognizer from the given start vertex 232 | 233 | **Kind**: instance method of [GrammarGraph](#GrammarGraph) 234 | **Returns**: [Recognizer](#Recognizer) - a new Recognizer 235 | 236 | | Param | Type | Description | 237 | | --- | --- | --- | 238 | | start | string | the name of a vertex in the decision graph from which to start the recognizer test | 239 | 240 | 241 | ## GuidedDecisionGraph 242 | **Kind**: global class 243 | 244 | * [GuidedDecisionGraph](#GuidedDecisionGraph) 245 | * [new GuidedDecisionGraph(dg, start)](#new_GuidedDecisionGraph_new) 246 | * [.construction()](#GuidedDecisionGraph+construction) ⇒ Array.<string> 247 | * [.isComplete()](#GuidedDecisionGraph+isComplete) ⇒ boolean 248 | * [.choose(terminal)](#GuidedDecisionGraph+choose) 249 | * [.constructs()](#GuidedDecisionGraph+constructs) ⇒ Array.<string> 250 | * [.pop()](#GuidedDecisionGraph+pop) ⇒ string 251 | * [.choices([nDeep])](#GuidedDecisionGraph+choices) ⇒ Array.<string> | [Array.<TreeNode>](#TreeNode) 252 | 253 | 254 | ### new GuidedDecisionGraph(dg, start) 255 | step-by-step construction of a language from a decision graph 256 | 257 | 258 | | Param | Type | Description | 259 | | --- | --- | --- | 260 | | dg | [DecisionGraph](#DecisionGraph) | a Decision Graph that defines a grammar | 261 | | start | string | the name of a vertex in the decision graph from which to start the guided expansion | 262 | 263 | 264 | ### guidedDecisionGraph.construction() ⇒ Array.<string> 265 | the current construction 266 | 267 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 268 | **Returns**: Array.<string> - a terminal symbol chain 269 | 270 | ### guidedDecisionGraph.isComplete() ⇒ boolean 271 | is the current construction a valid, complete construction from the starting 272 | nonterminal? ie, could the construction be haulted at this point? Depending 273 | on the grammar, this may be true even if there are more choices at this point. 274 | 275 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 276 | **Returns**: boolean - is the construction complete 277 | 278 | ### guidedDecisionGraph.choose(terminal) 279 | adds the given terminal to the construction 280 | 281 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 282 | 283 | | Param | Type | Description | 284 | | --- | --- | --- | 285 | | terminal | string | Array.<string> | the name of a terminal vertex in the Decision Graph which is in the current set of possible choices. Or a valid sequence of terminal symbols as an array. | 286 | 287 | 288 | ### guidedDecisionGraph.constructs() ⇒ Array.<string> 289 | get a sorted array of possible construction strings from the current state, 290 | possibly including nonterminals after the next terminal 291 | 292 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 293 | **Returns**: Array.<string> - a list of possible constructions 294 | **Example** 295 | ```js 296 | // guide is an in-progress GuidedDecisionGraph 297 | guide.construction() => ['the', 'dog', 'ate'] 298 | guide.choices() => ['', 'the'] 299 | guide.constructs() 300 | => [ 'the dog ate', 301 | 'the dog ate the Noun' 302 | 'the dog ate the Noun RelativeClause' ] 303 | ``` 304 | 305 | ### guidedDecisionGraph.pop() ⇒ string 306 | pop the last choice off the construction 307 | 308 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 309 | **Returns**: string - the last element of the construction that was 310 | submitted through [choose](#GuidedDecisionGraph+choose) 311 | **Throws**: 312 | 313 | - throws an error if called when construction is empty 314 | 315 | 316 | ### guidedDecisionGraph.choices([nDeep]) ⇒ Array.<string> | [Array.<TreeNode>](#TreeNode) 317 | returns all possible next terminals, or an array of nDeep [TreeNodes](#TreeNode) 318 | 319 | **Kind**: instance method of [GuidedDecisionGraph](#GuidedDecisionGraph) 320 | **Returns**: Array.<string> | [Array.<TreeNode>](#TreeNode) - if nDeep=1, an array of terminal symbols (strings), 321 | else an array of [TreeNodes](#TreeNode) 322 | 323 | | Param | Type | Default | Description | 324 | | --- | --- | --- | --- | 325 | | [nDeep] | number | 1 | will search for nDeep possible choices | 326 | 327 | **Example** 328 | ```js 329 | // guide is an in-progress GuidedDecisionGraph 330 | guide.construction() => ['the', 'dog', 'ate'] 331 | guide.choices() => ['', 'the'] 332 | guide.choices(3) => 333 | [ { val: '', 334 | next: [] }, 335 | { val: 'the', 336 | next: [ { val: 'squirrel', 337 | next: [ { val: 'that', next: [] }, 338 | { val: '', next: [] } ] 339 | }, 340 | { val: 'bird', 341 | next: [ { val: 'that', next: [] }, 342 | { val: '', next: [] } ] 343 | }, 344 | { val: 'cat', 345 | next: [ { val: 'that', next: [] }, 346 | { val: '', next: [] } ] 347 | }, 348 | { val: 'dog', 349 | next: [ { val: 'that', next: [] }, 350 | { val: '', next: [] } ] 351 | } 352 | ] 353 | } 354 | ] 355 | ``` 356 | 357 | ## Recognizer 358 | **Kind**: global class 359 | 360 | * [Recognizer](#Recognizer) 361 | * [new Recognizer(dg, start, [seperator])](#new_Recognizer_new) 362 | * [.isValid(text)](#Recognizer+isValid) ⇒ boolean 363 | * [.isComplete(text)](#Recognizer+isComplete) ⇒ boolean 364 | 365 | 366 | ### new Recognizer(dg, start, [seperator]) 367 | create a Recognizer that can test if text is a valid sentence in a grammar 368 | 369 | 370 | | Param | Type | Default | Description | 371 | | --- | --- | --- | --- | 372 | | dg | [DecisionGraph](#DecisionGraph) | | a Decision Graph that defines a grammar | 373 | | start | string | | the name of a vertex in the decision graph from which to start the test | 374 | | [seperator] | string | RegExp | "/\\s+/" | how tokens will be divided in given text | 375 | 376 | 377 | ### recognizer.isValid(text) ⇒ boolean 378 | is the text a valid in progress sentence in the grammar? Will return true 379 | even if the text is not complete. 380 | 381 | **Kind**: instance method of [Recognizer](#Recognizer) 382 | **Returns**: boolean - is the text valid? 383 | 384 | | Param | Type | Description | 385 | | --- | --- | --- | 386 | | text | string | the text to check | 387 | 388 | 389 | ### recognizer.isComplete(text) ⇒ boolean 390 | is the text a valid and complete text in the grammar? Will return true 391 | only if the text is complete. 392 | 393 | **Kind**: instance method of [Recognizer](#Recognizer) 394 | **Returns**: boolean - is the text valid and complete? 395 | 396 | | Param | Type | Description | 397 | | --- | --- | --- | 398 | | text | string | the text to check | 399 | 400 | 401 | ## noSelfDefinitions(grammar, returns) 402 | check a grammar for direct self-loops 403 | 404 | **Kind**: global function 405 | **Throws**: 406 | 407 | - an error if one definition of a nonterminal is exactly the nonterminal itself 408 | 409 | 410 | | Param | Type | Description | 411 | | --- | --- | --- | 412 | | grammar | object | the grammar to check | 413 | | returns | true | true if no errors | 414 | 415 | 416 | ## parseGrammar(grammar, [seperator]) ⇒ [DecisionGraph](#DecisionGraph) 417 | parse a grammar given as an object and compile it into a decision graph 418 | 419 | **Kind**: global function 420 | **Returns**: [DecisionGraph](#DecisionGraph) - the grammar converted into a decision graph 421 | 422 | | Param | Type | Default | Description | 423 | | --- | --- | --- | --- | 424 | | grammar | object | | an object representing a grammar | 425 | | [seperator] | string | RegExp | "/\\s+/" | how tokens will be divided in rules | 426 | 427 | 428 | ## reduceGrammar(grammar, [seperator]) ⇒ object 429 | reduces the rules of a grammar into a one to one form by assigning a name 430 | to all non-terminals. The end result is that each option on a rule with 431 | more than one choice will either be a single AND-rule or a single terminal. 432 | 433 | **Kind**: global function 434 | **Returns**: object - the modified grammar object with newly created rules 435 | as needed. New rules will be given the name of their parent rule 436 | surrounded by underscores and followed by a number. 437 | 438 | | Param | Type | Default | Description | 439 | | --- | --- | --- | --- | 440 | | grammar | object | | an object representing a grammar | 441 | | [seperator] | string | RegExp | "/\\s+/" | how tokens will be divided in rules | 442 | 443 | **Example** 444 | ```js 445 | var grammar = { 446 | NounPhrase: ['the Noun', 'the Noun RelativeClause'], 447 | RelativeClause: ['that VerbPhrase'], 448 | Noun: ['dog', 'cat', 'bird'] 449 | } 450 | 451 | reduceGrammar(grammar) => 452 | { 453 | NounPhrase: ['_NounPhrase_1', '_NounPhrase_2'], 454 | _NounPhrase_1: ['the Noun'], 455 | _NounPhrase_2: ['the Noun RelativeClause'], 456 | RelativeClause: ['that VerbPhrase'], 457 | Noun: ['dog', 'cat', 'bird'] 458 | } 459 | ``` 460 | 461 | ## clone(obj) ⇒ object | array 462 | helper function to clone a simple object/array made up of primitives. 463 | Will not work if the object or array contains non-primitives. 464 | 465 | **Kind**: global function 466 | **Returns**: object | array - a new clone of the provided object or array 467 | 468 | | Param | Type | Description | 469 | | --- | --- | --- | 470 | | obj | object | array | an object array made up only of primitives | 471 | 472 | 473 | ## SymbolChain : string 474 | a string of one or more symbol names seperated by whitespace or 475 | another user defined seperator (see: seperator param for [GrammarGraph](#GrammarGraph)) 476 | 477 | **Kind**: global typedef 478 | **See**: a SymbolChain is used as definitions in [Grammar](#Grammar) 479 | **Example** 480 | ```js 481 | 'dog' // just a single symbol, the word 'dog' 482 | 'the Noun RelativeClause' // three symbols 483 | ``` 484 | 485 | ## Grammar : Object 486 | a user defined context-free grammar formatted as an object consisting of key-value pairs, 487 | with each [non-terminal symbol](https://github.com/jrleszcz/grammar-graph#non-terminal-symbols) 488 | pointing to an array of one or more [symbol chains](https://github.com/jrleszcz/grammar-graph#symbol-chains) 489 | choices for this non-terminal. 490 | 491 | **Kind**: global typedef 492 | **Properties** 493 | 494 | | Name | Type | Description | 495 | | --- | --- | --- | 496 | | symbol | [Array.<SymbolChain>](#SymbolChain) | each element of the array is a possible definition for this symbol. | 497 | 498 | **Example** 499 | ```js 500 | var grammar = { 501 | Sentence: ['NounPhrase VerbPhrase'], // only one definition of 'Sentence' 502 | NounPhrase: ['the Noun', 'the Noun RelativeClause'], // two possible definitions of 'NounPhrase' 503 | VerbPhrase: ['Verb', 'Verb NounPhrase'], 504 | RelativeClause: ['that VerbPhrase'], 505 | Noun: ['dog', 'cat', 'bird', 'squirrel'], // four possible definitions of 'Noun' 506 | Verb: ['befriended', 'loved', 'ate', 'attacked'] 507 | } 508 | // non-terminals: Sentence, NounPhrase, VerbPhrase, RelativeClause, Noun, Verb 509 | // terminals: the, that, dog, cat, bird, squirrel, befriended, loved, ate, attacked 510 | ``` 511 | 512 | ## TreeNode : object 513 | **Kind**: global typedef 514 | **Properties** 515 | 516 | | Name | Type | Description | 517 | | --- | --- | --- | 518 | | val | string | a terminal string | 519 | | next | [Array.<TreeNode>](#TreeNode) | a list of TreeNodes this node links to | 520 | 521 | -------------------------------------------------------------------------------- /lib/assert-no-self-loops.js: -------------------------------------------------------------------------------- 1 | /** 2 | * check a grammar for direct self-loops 3 | * @param {object} grammar - the grammar to check 4 | * @param {true} returns true if no errors 5 | * @throws an error if one definition of a nonterminal is exactly the nonterminal itself 6 | */ 7 | function noSelfDefinitions (grammar) { 8 | for (var token of Object.keys(grammar)) { 9 | for (var definition of grammar[token]) { 10 | if (definition === token) { 11 | throw new Error('Nonterminal ' + token + ' has a definition defining it as itself: ' + grammar[token]) 12 | } 13 | } 14 | } 15 | return true 16 | } 17 | 18 | module.exports = noSelfDefinitions 19 | -------------------------------------------------------------------------------- /lib/decision-graph.js: -------------------------------------------------------------------------------- 1 | /** 2 | * creates a new DecisionGraph 3 | * @constructor 4 | */ 5 | var DecisionGraph = function () { 6 | var V = 0 // the number of vertices 7 | var names = {} // vertex string -> index number 8 | var keys = [] // vertex index number -> string 9 | var isAND = [] // boolean array: true = AND; false = OR 10 | var adj = [] // edge arrays 11 | 12 | /** 13 | * add AND vertex to the graph. When moving through the decision graph, an AND vertex 14 | * will require a visit down each of its outgoing edges, in the order the edges were 15 | * added. 16 | * @param {string} name - the name of this vertex 17 | * 18 | * @see {@link DecisionGraph#addVertexOR} 19 | */ 20 | this.addVertexAND = function (name) { 21 | if (names[name] !== undefined) return 22 | names[name] = V 23 | keys[V] = name 24 | isAND[V] = true 25 | adj[V] = [] 26 | V++ 27 | } 28 | 29 | /** 30 | * add OR vertex to the graph. When moving through the decision graph, an OR vertex 31 | * chooses just one of its outgoing vertices. 32 | * @param {string} name - the name of this vertex 33 | * 34 | * @see {@link DecisionGraph#addVertexAND} 35 | */ 36 | this.addVertexOR = function (name) { 37 | if (names[name] !== undefined) return 38 | names[name] = V 39 | keys[V] = name 40 | isAND[V] = false 41 | adj[V] = [] 42 | V++ 43 | } 44 | 45 | /** 46 | * add edge v->w to the graph 47 | * @param {string} v - the name of a vertex this edge points from 48 | * @param {(string|string[])} w - the name of a vertex this edge points to or an 49 | * array of vertex names. If vertex v is type AND, the order of w will be the exact order 50 | * required. 51 | */ 52 | this.addEdge = function (v, w) { 53 | if (Array.isArray(w)) { 54 | w.forEach(function (child) { 55 | if (!(child in names)) throw new Error('Not a vertex name:', child) 56 | }) 57 | Array.prototype.push.apply(adj[names[v]], w.map(function (vName) { 58 | return names[vName] 59 | })) 60 | } else { 61 | if (!(w in names)) throw new Error('Not a vertex name:', w) 62 | adj[names[v]].push(names[w]) 63 | } 64 | } 65 | 66 | /** 67 | * get an array of all the vertices this vertex points to 68 | * @param {string} v - the name of a vertex 69 | * @returns {string[]} an ordered list of all the vertices that v points to 70 | */ 71 | this.adj = function (v) { 72 | return adj[names[v]].map(function (vIndex) { 73 | return keys[vIndex] 74 | }) 75 | } 76 | 77 | /** 78 | * get the number of vertices in this graph 79 | * @returns {number} the number of vertices in this graph 80 | */ 81 | this.V = function () { 82 | return V 83 | } 84 | 85 | /** 86 | * is this a terminal vertex (does it have no outgoing edges?) 87 | * @param {string} v - the name of a vertex 88 | * @returns {boolean} is this a terminal vertex 89 | */ 90 | this.isTerminal = function (v) { 91 | return adj[names[v]].length === 0 92 | } 93 | 94 | /** 95 | * is this the name of a vertex in the graph? 96 | * @param {string} v - the name of a vertex 97 | * @returns {boolean} is this a vertex in the graph 98 | */ 99 | this.isVertex = function (v) { 100 | return v in names 101 | } 102 | 103 | /** 104 | * is this a type AND vertex (and not a type OR)? 105 | * @param {string} v - the name of a vertex 106 | * @returns {boolean} is this a type AND vertex (and not a type OR)? 107 | */ 108 | this.isTypeAND = function (v) { 109 | return isAND[names[v]] 110 | } 111 | 112 | /** 113 | * get an array of vertex names 114 | * @returns {string[]} the vertex names in this graph 115 | */ 116 | this.vertices = function () { 117 | return Object.keys(names) 118 | } 119 | } 120 | 121 | module.exports = DecisionGraph 122 | -------------------------------------------------------------------------------- /lib/grammar-graph.js: -------------------------------------------------------------------------------- 1 | var parseGrammar = require('./parse-grammar') 2 | var GuidedDecisionGraph = require('./guided-decision-graph') 3 | var Recognizer = require('./recognizer.js') 4 | 5 | /** 6 | * @typedef {string} SymbolChain - a string of one or more symbol names seperated by whitespace or 7 | * another user defined seperator (see: seperator param for {@link GrammarGraph}) 8 | * 9 | * @see a SymbolChain is used as definitions in {@link Grammar} 10 | * 11 | * @example 12 | * 'dog' // just a single symbol, the word 'dog' 13 | * 'the Noun RelativeClause' // three symbols 14 | */ 15 | 16 | /** 17 | * a user defined context-free grammar formatted as an object consisting of key-value pairs, 18 | * with each [non-terminal symbol](https://github.com/jrleszcz/grammar-graph#non-terminal-symbols) 19 | * pointing to an array of one or more [symbol chains](https://github.com/jrleszcz/grammar-graph#symbol-chains) 20 | * choices for this non-terminal. 21 | * 22 | * @typedef {Object} Grammar 23 | * @property {SymbolChain[]} symbol - each element of the array is a possible definition 24 | * for this symbol. 25 | * @example 26 | * var grammar = { 27 | * Sentence: ['NounPhrase VerbPhrase'], // only one definition of 'Sentence' 28 | * NounPhrase: ['the Noun', 'the Noun RelativeClause'], // two possible definitions of 'NounPhrase' 29 | * VerbPhrase: ['Verb', 'Verb NounPhrase'], 30 | * RelativeClause: ['that VerbPhrase'], 31 | * Noun: ['dog', 'cat', 'bird', 'squirrel'], // four possible definitions of 'Noun' 32 | * Verb: ['befriended', 'loved', 'ate', 'attacked'] 33 | * } 34 | * // non-terminals: Sentence, NounPhrase, VerbPhrase, RelativeClause, Noun, Verb 35 | * // terminals: the, that, dog, cat, bird, squirrel, befriended, loved, ate, attacked 36 | */ 37 | 38 | /** 39 | * creates a new GrammarGraph which can generate guides. 40 | * @constructor 41 | * 42 | * @param {Grammar} grammar - an object representing a grammar. 43 | * @param {string|RegExp} [seperator=/\s+/] - how tokens will be divided in rules 44 | * @param {string} [epsilonSymbol=''] - Special terminal symbol 45 | * that indicates this is an end of a construction. Defaults to the 46 | * empty string. 47 | */ 48 | var GrammarGraph = function (grammar, seperator, epsilon) { 49 | var decisionGraph = parseGrammar(grammar, seperator) 50 | 51 | /** 52 | * get an array of vertex names in the graph 53 | * @returns {string[]} the vertex names in this graph 54 | * @see {@link DecisionGraph#vertices} 55 | */ 56 | this.vertices = function () { 57 | return decisionGraph.vertices() 58 | } 59 | 60 | /** 61 | * get an array of all the vertices this vertex points to 62 | * @param {string} v - the name of a vertex 63 | * @returns {string[]} an ordered list of all the vertices that v points to 64 | * @see {@link DecisionGraph#adj} 65 | */ 66 | this.adj = function (v) { 67 | return decisionGraph.adj(v) 68 | } 69 | 70 | /** 71 | * is this a type AND vertex (and not a type OR)? 72 | * @param {string} v - the name of a vertex 73 | * @returns {boolean} is this a type AND vertex (and not a type OR)? 74 | * @see {@link DecisionGraph#isTypeAND} 75 | */ 76 | this.isTypeAND = function (v) { 77 | return decisionGraph.isTypeAND(v) 78 | } 79 | 80 | /** 81 | * get a new GuidedDecisionGraph using this decision graph 82 | * @param {string} start - the name of a vertex in the decision graph from which 83 | * to start the guided expansion 84 | * @returns {GuidedDecisionGraph} a new guide from the provided start point 85 | * @see {@link GuidedDecisionGraph} for the methods available on the Guide 86 | */ 87 | this.createGuide = function (start) { 88 | return new GuidedDecisionGraph(decisionGraph, start) 89 | } 90 | 91 | /** 92 | * Returns a new Recognizer from the given start vertex 93 | * @param {string} start - the name of a vertex in the decision graph 94 | * from which to start the recognizer test 95 | * @returns {Recognizer} a new Recognizer 96 | */ 97 | this.createRecognizer = function (start) { 98 | return new Recognizer(decisionGraph, start) 99 | } 100 | } 101 | 102 | module.exports = GrammarGraph 103 | -------------------------------------------------------------------------------- /lib/guided-decision-graph.js: -------------------------------------------------------------------------------- 1 | var clone = require('./utils/clone.js') 2 | 3 | /** 4 | * step-by-step construction of a language from a decision graph 5 | * @constructor 6 | * 7 | * @param {DecisionGraph} dg - a Decision Graph that defines a grammar 8 | * @param {string} start - the name of a vertex in the decision graph from which 9 | * to start the guided expansion 10 | */ 11 | var GuidedDecisionGraph = function (dg, start) { 12 | if (!(dg.isVertex(start))) throw new Error('given start is not a vertex in dg:', start) 13 | 14 | var construction = [] // chosen terminals, i.e. the current construction 15 | var expanded // dictionary of possible next terminals, + their stateStacks 16 | var acceptState = false // could the current construction stand complete as-is? 17 | var history = [] // stack of previous expanded objects (see previous line) 18 | var acceptStateHistory = [] // stack of booleans remembering old acceptStates 19 | var guide = this // scope to be used in recursive functions: choose & choices 20 | 21 | // expands all stateStacks in unexpanded until they begin in a terminal symbol 22 | // and adds them to expanded 23 | function expand (unexpanded) { 24 | acceptState = false // reset for this expansion 25 | var expandedChoices = {} // dictionary of possible next terminals, + their stateStacks 26 | while (unexpanded.length !== 0) { 27 | var stateStack = unexpanded.pop() 28 | if (stateStack.length === 0) { // if true, the current construction could be complete 29 | acceptState = true 30 | } else { 31 | var node = stateStack.pop() 32 | 33 | if (dg.isTerminal(node)) { // terminal, add to expandedChoices dictionary 34 | if (!(node in expandedChoices)) expandedChoices[node] = [] 35 | expandedChoices[node].push(stateStack) 36 | } else if (dg.isTypeAND(node)) { // type AND, expand and put back on unexpanded 37 | var children = dg.adj(node) 38 | // add to stateStack in reverse order 39 | for (var i = children.length - 1; i >= 0; i--) { 40 | stateStack.push(children[i]) 41 | } 42 | unexpanded.push(stateStack) 43 | } else { // type OR, add all possible stacks to unexpanded 44 | var orChildren = dg.adj(node) 45 | orChildren.forEach(function (child) { 46 | var stackCopy = clone(stateStack) 47 | stackCopy.push(child) 48 | unexpanded.push(stackCopy) 49 | }) 50 | } 51 | } 52 | } 53 | return expandedChoices 54 | } 55 | 56 | // initialize the guided decision graph with an array of possible state stacks, 57 | // in this case, just a single state stack consisting of the start symbol 58 | expanded = expand([[start]]) 59 | 60 | /** 61 | * the current construction 62 | * @returns {string[]} a terminal symbol chain 63 | */ 64 | this.construction = function () { 65 | return clone(construction) 66 | } 67 | 68 | /** 69 | * is the current construction a valid, complete construction from the starting 70 | * nonterminal? ie, could the construction be haulted at this point? Depending 71 | * on the grammar, this may be true even if there are more choices at this point. 72 | * @returns {boolean} is the construction complete 73 | */ 74 | this.isComplete = function () { 75 | return acceptState 76 | } 77 | 78 | /** 79 | * adds the given terminal to the construction 80 | * @param {string|string[]} terminal - the name of a terminal vertex in the 81 | * Decision Graph which is in the current set of possible choices. Or a valid 82 | * sequence of terminal symbols as an array. 83 | */ 84 | this.choose = function (terminal) { 85 | if (Array.isArray(terminal)) { 86 | terminal.forEach(function (t) { 87 | guide.choose(t) 88 | }) 89 | } else { 90 | if (!(terminal in expanded)) throw new Error('Not a valid next terminal:', terminal) 91 | 92 | construction.push(terminal) // add terminal to construction 93 | history.push(clone(expanded)) // add expanded dictionary to history 94 | acceptStateHistory.push(acceptState) // add acceptState to history 95 | expanded = expand(expanded[terminal]) // expand the stateStacks of the choice 96 | } 97 | } 98 | 99 | /** 100 | * get a sorted array of possible construction strings from the current state, 101 | * possibly including nonterminals after the next terminal 102 | * @returns {string[]} a list of possible constructions 103 | * @example 104 | * // guide is an in-progress GuidedDecisionGraph 105 | * guide.construction() => ['the', 'dog', 'ate'] 106 | * guide.choices() => ['', 'the'] 107 | * guide.constructs() 108 | * => [ 'the dog ate', 109 | * 'the dog ate the Noun' 110 | * 'the dog ate the Noun RelativeClause' ] 111 | */ 112 | this.constructs = function () { 113 | var states = [] 114 | for (var terminal in expanded) { 115 | expanded[terminal].forEach(function (stack) { 116 | var choice = clone(stack) 117 | choice.push(terminal) 118 | choice.reverse() 119 | var cur = construction.length > 0 ? construction.join(' ') 120 | : '' 121 | var next = choice.join(' ') 122 | var sentence = (cur && next) ? cur + ' ' + next 123 | : cur + next 124 | states.push(sentence) 125 | }) 126 | } 127 | if (guide.isComplete()) { // also add the current construction as a seperate choice 128 | states.push(guide.construction().join(' ')) 129 | } 130 | return states.sort() 131 | } 132 | 133 | /** 134 | * pop the last choice off the construction 135 | * @throws throws an error if called when construction is empty 136 | * @returns {string} the last element of the construction that was 137 | * submitted through {@link GuidedDecisionGraph#choose} 138 | */ 139 | this.pop = function () { 140 | if (construction.length === 0) throw new Error('Cannot pop empty construction.') 141 | expanded = history.pop() 142 | acceptState = acceptStateHistory.pop() 143 | return construction.pop() 144 | } 145 | 146 | /** 147 | * @typedef {object} TreeNode 148 | * @property {string} val - a terminal string 149 | * @property {TreeNode[]} next - a list of TreeNodes this node links to 150 | */ 151 | 152 | /** 153 | * returns all possible next terminals, or an array of nDeep [TreeNodes]{@link TreeNode} 154 | * 155 | * @param {number} [nDeep=1] - will search for nDeep possible choices 156 | * @returns {string[]|TreeNode[]} if nDeep=1, an array of terminal symbols (strings), 157 | * else an array of [TreeNodes]{@link TreeNode} 158 | * @example 159 | * // guide is an in-progress GuidedDecisionGraph 160 | * guide.construction() => ['the', 'dog', 'ate'] 161 | * guide.choices() => ['', 'the'] 162 | * guide.choices(3) => 163 | * [ { val: '', 164 | * next: [] }, 165 | * { val: 'the', 166 | * next: [ { val: 'squirrel', 167 | * next: [ { val: 'that', next: [] }, 168 | * { val: '', next: [] } ] 169 | * }, 170 | * { val: 'bird', 171 | * next: [ { val: 'that', next: [] }, 172 | * { val: '', next: [] } ] 173 | * }, 174 | * { val: 'cat', 175 | * next: [ { val: 'that', next: [] }, 176 | * { val: '', next: [] } ] 177 | * }, 178 | * { val: 'dog', 179 | * next: [ { val: 'that', next: [] }, 180 | * { val: '', next: [] } ] 181 | * } 182 | * ] 183 | * } 184 | * ] 185 | */ 186 | this.choices = function (nDeep) { 187 | if (nDeep === undefined || nDeep === 1) { 188 | return Object.keys(expanded) 189 | } 190 | // if nDeep > 1, map to TreeNodes and recursively expand to nDeep choices 191 | return Object.keys(expanded).map(function (choice) { 192 | var node = new TreeNode(choice) 193 | guide.choose(choice) 194 | node.next = (nDeep - 1 > 1) ? guide.choices(nDeep - 1) 195 | : guide.choices(nDeep - 1).map(TreeNode) 196 | guide.pop() 197 | return node 198 | }) 199 | } 200 | } 201 | 202 | /* 203 | * Tree nodes to return decision trees 204 | * 205 | * @param {string} val - a terminal string 206 | * @property {string} val - a terminal string 207 | * @property {TreeNode[]} next - a list of TreeNodes this node links to 208 | * @see TreeNodes are returned from [GuidedDecisionGraph.choices]{@link GuidedDecisionGraph#choices} 209 | */ 210 | var TreeNode = function (val) { 211 | if (!(this instanceof TreeNode)) return new TreeNode(val) 212 | this.val = val 213 | this.next = [] 214 | } 215 | 216 | module.exports = GuidedDecisionGraph 217 | -------------------------------------------------------------------------------- /lib/parse-grammar.js: -------------------------------------------------------------------------------- 1 | var reduceGrammar = require('./reduce-grammar.js') 2 | var DecisionGraph = require('./decision-graph.js') 3 | var assertNoSelfLoops = require('./assert-no-self-loops.js') 4 | 5 | /** 6 | * parse a grammar given as an object and compile it into a decision graph 7 | * 8 | * @param {object} grammar - an object representing a grammar 9 | * @param {string|RegExp} [seperator=/\s+/] - how tokens will be divided in rules 10 | * @returns {DecisionGraph} the grammar converted into a decision graph 11 | * 12 | */ 13 | var parseGrammar = function (grammar, seperator) { 14 | seperator = (seperator === undefined) ? /\s+/ 15 | : seperator 16 | // make sure the rules are in a one-to-one relationship 17 | grammar = reduceGrammar(grammar, seperator) 18 | assertNoSelfLoops(grammar) 19 | 20 | var dg = new DecisionGraph() 21 | 22 | // add all nontermintal vertices 23 | var rules = Object.keys(grammar) 24 | rules.forEach(function (rule) { 25 | if (grammar[rule].length > 1) { 26 | dg.addVertexOR(rule) 27 | } else { 28 | dg.addVertexAND(rule) 29 | } 30 | }) 31 | // add all edges as well as terminal vertices as they are encountered 32 | rules.forEach(function (rule) { 33 | grammar[rule].forEach(function (definition) { 34 | var tokens = definition.split(seperator) 35 | tokens.forEach(function (token) { 36 | if (!(token in grammar)) { 37 | dg.addVertexAND(token) 38 | } 39 | }) 40 | dg.addEdge(rule, tokens) 41 | }) 42 | }) 43 | return dg 44 | } 45 | 46 | module.exports = parseGrammar 47 | -------------------------------------------------------------------------------- /lib/recognizer.js: -------------------------------------------------------------------------------- 1 | var GuidedDecisionGraph = require('./guided-decision-graph.js') 2 | 3 | /** 4 | * create a Recognizer that can test if text is a valid sentence in a grammar 5 | * @constructor 6 | * 7 | * @param {DecisionGraph} dg - a Decision Graph that defines a grammar 8 | * @param {string} start - the name of a vertex in the decision graph from which 9 | * to start the test 10 | * @param {string|RegExp} [seperator=/\s+/] - how tokens will be divided in 11 | * given text 12 | */ 13 | var Recognizer = function (dg, start, seperator) { 14 | seperator = seperator || /\s+/ 15 | 16 | /* 17 | * tries to add all tokens and return true if successful, else false 18 | * 19 | * @param {GuidedDecisionGraph} gdg - the guide to add tokens to 20 | * @param {string[]} tokens - an array of strings to be tested as 21 | * a terminal chain in the grammar 22 | * 23 | * @returns {boolean} were all tokens succesfully added to the gdg? 24 | */ 25 | var addedTokens = function (gdg, tokens) { 26 | for (var i = 0; i < tokens.length; i++) { 27 | if (gdg.choices().indexOf(tokens[i]) === -1) { 28 | return false // next token cannot be added to guide 29 | } 30 | gdg.choose(tokens[i]) 31 | } 32 | return true // all tokens were added to guide 33 | } 34 | 35 | /** 36 | * is the text a valid in progress sentence in the grammar? Will return true 37 | * even if the text is not complete. 38 | * @param {string} text - the text to check 39 | * @returns {boolean} is the text valid? 40 | */ 41 | this.isValid = function (text) { 42 | // by definition, an empty string is considered a valid start 43 | if (text.length === 0) { 44 | return true 45 | } 46 | var gdg = new GuidedDecisionGraph(dg, start) 47 | return addedTokens(gdg, text.split(seperator)) 48 | } 49 | 50 | /** 51 | * is the text a valid and complete text in the grammar? Will return true 52 | * only if the text is complete. 53 | * @param {string} text - the text to check 54 | * @returns {boolean} is the text valid and complete? 55 | */ 56 | this.isComplete = function (text) { 57 | var gdg = new GuidedDecisionGraph(dg, start) 58 | if (!addedTokens(gdg, text.split(seperator))) { 59 | return false // guide is not valid, so no need to check completeness 60 | } 61 | return gdg.isComplete() // is this a complete construction? 62 | } 63 | } 64 | 65 | module.exports = Recognizer 66 | -------------------------------------------------------------------------------- /lib/reduce-grammar.js: -------------------------------------------------------------------------------- 1 | var clone = require('./utils/clone.js') 2 | 3 | /** 4 | * reduces the rules of a grammar into a one to one form by assigning a name 5 | * to all non-terminals. The end result is that each option on a rule with 6 | * more than one choice will either be a single AND-rule or a single terminal. 7 | * 8 | * @param {object} grammar - an object representing a grammar 9 | * @param {string|RegExp} [seperator=/\s+/] - how tokens will be divided in rules 10 | * @returns {object} the modified grammar object with newly created rules 11 | * as needed. New rules will be given the name of their parent rule 12 | * surrounded by underscores and followed by a number. 13 | * 14 | * @example 15 | * var grammar = { 16 | * NounPhrase: ['the Noun', 'the Noun RelativeClause'], 17 | * RelativeClause: ['that VerbPhrase'], 18 | * Noun: ['dog', 'cat', 'bird'] 19 | * } 20 | * 21 | * reduceGrammar(grammar) => 22 | * { 23 | * NounPhrase: ['_NounPhrase_1', '_NounPhrase_2'], 24 | * _NounPhrase_1: ['the Noun'], 25 | * _NounPhrase_2: ['the Noun RelativeClause'], 26 | * RelativeClause: ['that VerbPhrase'], 27 | * Noun: ['dog', 'cat', 'bird'] 28 | * } 29 | * 30 | */ 31 | var reduceGrammar = function (grammar, seperator) { 32 | grammar = clone(grammar) // clone to avoid mutating the argument 33 | seperator = (seperator === undefined) ? /\s+/ 34 | : seperator 35 | var rules = Object.keys(grammar) 36 | rules.forEach(function (rule) { 37 | if (grammar[rule].length > 1) { 38 | var id = 1 39 | grammar[rule].forEach(function (definition, i) { 40 | var tokens = definition.split(seperator) 41 | // create a new rule if length greater than 1 42 | if (tokens.length > 1) { 43 | var ruleName = '_' + rule + '_' + id 44 | grammar[ruleName] = [definition] 45 | grammar[rule][i] = ruleName 46 | id++ 47 | } 48 | }) 49 | } 50 | }) 51 | return grammar 52 | } 53 | 54 | module.exports = reduceGrammar 55 | -------------------------------------------------------------------------------- /lib/utils/clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * helper function to clone a simple object/array made up of primitives. 3 | * Will not work if the object or array contains non-primitives. 4 | * @param {object|array} obj - an object array made up only of primitives 5 | * @returns {object|array} a new clone of the provided object or array 6 | */ 7 | function clone (obj) { 8 | return JSON.parse(JSON.stringify(obj)) 9 | } 10 | 11 | module.exports = clone 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grammar-graph", 3 | "version": "3.1.1", 4 | "description": "Takes a context-free grammar and converts it into a decision-making graph. Can produce interactive Guides which generate valid sentences from the grammar.", 5 | "main": "./lib/grammar-graph.js", 6 | "directories": { 7 | "lib": "./lib", 8 | "test": "./test" 9 | }, 10 | "scripts": { 11 | "test": "eslint lib test && tape \"test/**/*.test.js\" | faucet", 12 | "docs": "jsdoc2md lib/grammar-graph.js lib/guided-decision-graph.js lib/recognizer.js > api.md && jsdoc2md \"lib/**/*.js\" > internal-api.md", 13 | "preversion": "npm test", 14 | "postversion": "git push" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/jrleszcz/grammar-graph.git" 19 | }, 20 | "keywords": [ 21 | "graph", 22 | "state machine", 23 | "automaton", 24 | "non-deterministic finite state machine", 25 | "grammar graph", 26 | "generator", 27 | "language generator", 28 | "digraph", 29 | "directed graph", 30 | "decision-making graph", 31 | "decision graph", 32 | "decision tree", 33 | "grammar", 34 | "context-free grammar", 35 | "nondeterministic", 36 | "nfa", 37 | "formal language" 38 | ], 39 | "author": { 40 | "name": "John Leszczynski", 41 | "email": "jrleszczynski@gmail.com", 42 | "url": "http://johnleszczynski.com" 43 | }, 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/jrleszcz/grammar-graph/issues" 47 | }, 48 | "homepage": "https://github.com/jrleszcz/grammar-graph#readme", 49 | "devDependencies": { 50 | "faucet": "0.0.1", 51 | "jsdoc-to-markdown": "^1.1.1", 52 | "tape": "^4.2.0", 53 | "eslint": "^3.14.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/assert-no-self-loops.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var assertNoSelfLoops = require('../lib/assert-no-self-loops.js') 3 | var reduceGrammar = require('../lib/reduce-grammar.js') 4 | 5 | test('assertNoSelfLoops', function (t) { 6 | var g = { 7 | Sentence : ['NounPhrase VerbPhrase'], 8 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 9 | VerbPhrase : ['Verb', 'Verb NounPhrase'], 10 | RelativeClause : ['that VerbPhrase'], 11 | Noun : ['dog', 'cat', 'bird', 'squirrel'], 12 | Verb : ['befriended', 'loved', 'ate', 'attacked'] 13 | } 14 | var grammar = reduceGrammar(g) 15 | 16 | t.true(assertNoSelfLoops(grammar)) 17 | 18 | var g2 = { 19 | grammar: ['something else', 'grammar', 'another thing'] 20 | } 21 | 22 | t.throws(function () { 23 | assertNoSelfLoops(g2) 24 | }) 25 | 26 | var g3 = { 27 | grammar : ['something else', '2342', 'another thing'], 28 | lottery : ['winner', 'loser', 'lottery winner'] 29 | } 30 | 31 | t.true(assertNoSelfLoops(g3)) 32 | 33 | g3.paper = ['water', 'scissors', 'paper', 'rock'] 34 | 35 | t.throws(function () { 36 | assertNoSelfLoops(g3) 37 | }) 38 | 39 | t.end() 40 | }) 41 | -------------------------------------------------------------------------------- /test/decision-graph.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var DecisionGraph = require('../lib/decision-graph.js') 3 | 4 | test('DecisionGraph methods', function (t) { 5 | var dg = new DecisionGraph() 6 | 7 | t.true(dg instanceof DecisionGraph) 8 | t.equal(dg.V(), 0) 9 | 10 | dg.addVertexAND('Sentence') 11 | t.equal(dg.V(), 1) 12 | t.deepEqual(dg.adj('Sentence'), []) 13 | t.true(dg.isTerminal('Sentence')) 14 | t.true(dg.isTypeAND('Sentence')) 15 | 16 | dg.addVertexOR('NounPhrase') 17 | t.equal(dg.V(), 2) 18 | t.deepEqual(dg.adj('NounPhrase'), []) 19 | t.true(dg.isTerminal('Sentence')) 20 | t.false(dg.isTypeAND('NounPhrase')) 21 | 22 | var ANDs = ['_NounPhrase1', '_NounPhrase2', '_VerbPhrase1', 23 | 'RelativeClause', 'the', 'that', 'dog', 'cat', 'bird', 'squirrel', 24 | 'befriended', 'loved', 'ate', 'attacked'] 25 | ANDs.forEach(function (andVertex) { 26 | dg.addVertexAND(andVertex) 27 | }) 28 | t.equal(dg.V(), 16) 29 | 30 | var ORs = ['VerbPhrase', 'Noun', 'Verb'] 31 | ORs.forEach(function (orVertex) { 32 | dg.addVertexAND(orVertex) 33 | }) 34 | t.equal(dg.V(), 19) 35 | 36 | dg.addEdge('Sentence', ['NounPhrase', 'VerbPhrase']) 37 | t.deepEqual(dg.adj('Sentence'), ['NounPhrase', 'VerbPhrase']) 38 | t.false(dg.isTerminal('Sentence')) 39 | 40 | dg.addEdge('NounPhrase', '_NounPhrase1') 41 | t.deepEqual(dg.adj('NounPhrase'), ['_NounPhrase1']) 42 | t.false(dg.isTerminal('NounPhrase')) 43 | 44 | dg.addEdge('NounPhrase', '_NounPhrase2') 45 | t.deepEqual(dg.adj('NounPhrase'), ['_NounPhrase1', '_NounPhrase2'], 46 | 'add edges one by one as string') 47 | t.false(dg.isTerminal('NounPhrase')) 48 | 49 | dg.addEdge('VerbPhrase', ['Verb', '_VerbPhrase1']) 50 | t.deepEqual(dg.adj('VerbPhrase'), ['Verb', '_VerbPhrase1']) 51 | 52 | dg.addEdge('_NounPhrase1', ['the', 'Noun']) 53 | dg.addEdge('_NounPhrase2', ['the', 'Noun', 'RelativeClause']) 54 | dg.addEdge('_VerbPhrase1', ['Verb', 'NounPhrase']) 55 | dg.addEdge('RelativeClause', ['that', 'VerbPhrase']) 56 | dg.addEdge('Noun', ['dog', 'cat', 'squirrel', 'bird']) 57 | dg.addEdge('Verb', ['befriended', 'loved', 'ate', 'attacked']) 58 | 59 | t.equal(dg.V(), 19) 60 | t.true(dg.isTerminal('that')) 61 | t.false(dg.isTerminal('Verb')) 62 | 63 | // try to add nodes again and make sure V still == 19 64 | dg.addVertexAND('Sentence') 65 | t.equal(dg.V(), 19) 66 | dg.addVertexOR('VerbPhrase') 67 | t.equal(dg.V(), 19) 68 | 69 | // check that error is thrown if adding edge to vertex which is not present 70 | t.throws(function () { 71 | dg.addEdge('Sentence', 'NotAVertex') 72 | }, Error) 73 | t.throws(function () { 74 | dg.addEdge('Sentence', ['cat', 'bat']) 75 | }, Error) 76 | 77 | t.end() 78 | }) 79 | -------------------------------------------------------------------------------- /test/grammar-graph.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var GrammarGraph = require('../lib/grammar-graph.js') 3 | var GuidedDecisionGraph = require('../lib/guided-decision-graph.js') 4 | 5 | test('GrammarGraph', function (t) { 6 | var g = { 7 | Sentence : ['NounPhrase VerbPhrase'], 8 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 9 | VerbPhrase : ['Verb', 'Verb NounPhrase'], 10 | RelativeClause : ['that VerbPhrase'], 11 | Noun : ['dog', 'cat', 'bird', 'squirrel'], 12 | Verb : ['befriended', 'loved', 'ate', 'attacked'] 13 | } 14 | 15 | var graph = new GrammarGraph(g) 16 | t.deepEqual(graph.vertices(), 17 | [ 'Sentence', 'NounPhrase', 'VerbPhrase', 'RelativeClause', 'Noun', 18 | 'Verb', '_NounPhrase_1', '_NounPhrase_2', '_VerbPhrase_1', 'that', 19 | 'dog', 'cat', 'bird', 'squirrel', 'befriended', 'loved', 'ate', 20 | 'attacked', 'the' ]) 21 | 22 | t.true(graph instanceof GrammarGraph) 23 | 24 | var guide = graph.createGuide('NounPhrase') 25 | t.true(guide instanceof GuidedDecisionGraph) 26 | 27 | t.deepEqual(graph.adj('bird'), []) 28 | t.deepEqual(graph.adj('RelativeClause'), ['that', 'VerbPhrase']) 29 | 30 | t.true(graph.isTypeAND('loved')) 31 | t.false(graph.isTypeAND('NounPhrase')) 32 | 33 | var isNoun = graph.createRecognizer('Noun').isComplete 34 | t.true(isNoun('cat')) 35 | t.true(isNoun('bird')) 36 | t.false(isNoun('attacked')) 37 | 38 | var isSentence = graph.createRecognizer('Sentence').isComplete 39 | t.true(isSentence('the dog ate')) 40 | t.false(isSentence('the dog ate the')) 41 | t.true(isSentence('the dog ate the cat')) 42 | 43 | t.end() 44 | }) 45 | -------------------------------------------------------------------------------- /test/guided-decision-graph.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var DecisionGraph = require('../lib/decision-graph.js') 3 | var GuidedDecisionGraph = require('../lib/guided-decision-graph.js') 4 | var GrammarGraph = require('../lib/grammar-graph.js') 5 | 6 | test('GuidedDecisionGraph methods', function (t) { 7 | var dg = new DecisionGraph() 8 | 9 | var ANDs = ['Sentence', '_NounPhrase_1', '_NounPhrase_2', '_VerbPhrase_1', 10 | 'RelativeClause', 'the', 'that', 'dog', 'cat', 'bird', 'squirrel', 11 | 'befriended', 'loved', 'ate', 'attacked'] 12 | var ORs = ['NounPhrase', 'VerbPhrase', 'Noun', 'Verb'] 13 | 14 | ANDs.forEach(function (andVertex) { 15 | dg.addVertexAND(andVertex) 16 | }) 17 | 18 | ORs.forEach(function (orVertex) { 19 | dg.addVertexOR(orVertex) 20 | }) 21 | 22 | dg.addEdge('Sentence', ['NounPhrase', 'VerbPhrase']) 23 | dg.addEdge('NounPhrase', ['_NounPhrase_1', '_NounPhrase_2']) 24 | dg.addEdge('VerbPhrase', ['Verb', '_VerbPhrase_1']) 25 | dg.addEdge('_NounPhrase_1', ['the', 'Noun']) 26 | dg.addEdge('_NounPhrase_2', ['the', 'Noun', 'RelativeClause']) 27 | dg.addEdge('_VerbPhrase_1', ['Verb', 'NounPhrase']) 28 | dg.addEdge('RelativeClause', ['that', 'VerbPhrase']) 29 | dg.addEdge('Noun', ['dog', 'cat', 'squirrel', 'bird']) 30 | dg.addEdge('Verb', ['befriended', 'loved', 'ate', 'attacked']) 31 | 32 | t.equal(dg.V(), 19) 33 | 34 | t.throws(function () { 35 | var a = new GuidedDecisionGraph(dg) 36 | a // Standard is complaining that a is defined and not used 37 | }, Error) 38 | 39 | var guide = new GuidedDecisionGraph(dg, 'Sentence') 40 | t.deepEqual(guide.construction(), []) 41 | t.deepEqual(guide.choices(), ['the']) 42 | t.false(guide.isComplete()) 43 | t.throws(function () { 44 | guide.pop() 45 | }, Error, 'Should not be able to pop empty construction') 46 | t.deepEqual(guide.constructs(), 47 | ['the Noun RelativeClause VerbPhrase', 48 | 'the Noun VerbPhrase']) 49 | 50 | guide.choose('the') 51 | t.deepEqual(guide.construction(), ['the']) 52 | t.deepEqual(guide.choices().sort(), ['dog', 'cat', 'squirrel', 'bird'].sort()) 53 | t.false(guide.isComplete()) 54 | t.deepEqual(guide.constructs(), 55 | [ 'the bird RelativeClause VerbPhrase', 56 | 'the bird VerbPhrase', 57 | 'the cat RelativeClause VerbPhrase', 58 | 'the cat VerbPhrase', 59 | 'the dog RelativeClause VerbPhrase', 60 | 'the dog VerbPhrase', 61 | 'the squirrel RelativeClause VerbPhrase', 62 | 'the squirrel VerbPhrase' ]) 63 | 64 | guide.choose('dog') 65 | t.deepEqual(guide.construction(), ['the', 'dog']) 66 | t.deepEqual(guide.choices().sort(), 67 | ['befriended', 'loved', 'attacked', 'ate', 'that'].sort()) 68 | t.deepEqual(guide.constructs(), 69 | [ 'the dog ate', 70 | 'the dog ate NounPhrase', 71 | 'the dog attacked', 72 | 'the dog attacked NounPhrase', 73 | 'the dog befriended', 74 | 'the dog befriended NounPhrase', 75 | 'the dog loved', 76 | 'the dog loved NounPhrase', 77 | 'the dog that VerbPhrase VerbPhrase' ]) 78 | 79 | guide.choose('ate') 80 | t.deepEqual(guide.construction(), ['the', 'dog', 'ate']) 81 | t.true(guide.isComplete()) 82 | t.deepEqual(guide.choices().sort(), 83 | ['the'].sort()) 84 | t.deepEqual(guide.constructs(), 85 | [ 'the dog ate', 86 | 'the dog ate the Noun', 87 | 'the dog ate the Noun RelativeClause' ]) 88 | 89 | t.equal(guide.pop(), 'ate') 90 | t.false(guide.isComplete()) 91 | t.deepEqual(guide.construction(), ['the', 'dog']) 92 | t.deepEqual(guide.choices().sort(), 93 | ['befriended', 'loved', 'attacked', 'ate', 'that'].sort()) 94 | 95 | guide.choose('ate') 96 | t.deepEqual(guide.construction(), ['the', 'dog', 'ate']) 97 | t.true(guide.isComplete()) 98 | t.deepEqual(guide.choices().sort(), 99 | ['the'].sort()) 100 | 101 | guide.choose('the') 102 | t.false(guide.isComplete()) 103 | t.deepEqual(guide.construction(), ['the', 'dog', 'ate', 'the']) 104 | t.deepEqual(guide.choices().sort(), 105 | ['dog', 'cat', 'squirrel', 'bird'].sort()) 106 | 107 | ;'cat that ate the bird that attacked the squirrel'.split(' ').forEach(function (t) { 108 | guide.choose(t) 109 | }) 110 | 111 | t.equal(guide.construction().join(' '), 112 | 'the dog ate the cat that ate the bird that attacked the squirrel') 113 | 114 | // throws errors when given a terminal which is not a valid next choice 115 | t.throws(function () { 116 | guide.choose('the') 117 | }, Error) 118 | t.throws(function () { 119 | guide.choose(' ') 120 | }, Error) 121 | 122 | t.equal(guide.pop(), 'squirrel') 123 | t.equal(guide.pop(), 'the') 124 | t.deepEqual(guide.choices().sort(), 125 | ['the'].sort()) 126 | t.true(guide.isComplete()) 127 | 128 | t.deepEqual(guide.constructs().sort(), 129 | ['the dog ate the cat that ate the bird that attacked', 130 | 'the dog ate the cat that ate the bird that attacked the Noun', 131 | 'the dog ate the cat that ate the bird that attacked the Noun RelativeClause'].sort()) 132 | 133 | t.end() 134 | }) 135 | 136 | test('GuidedDecisionGraph nDeep choices and multiple .choose()', function (t) { 137 | var g = { 138 | Sentence : ['NounPhrase VerbPhrase'], 139 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 140 | VerbPhrase : ['Verb', 'Verb NounPhrase'], 141 | RelativeClause : ['that VerbPhrase'], 142 | Noun : ['dog', 'cat', 'bird', 'squirrel'], 143 | Verb : ['befriended', 'loved', 'ate', 'attacked'] 144 | } 145 | var graph = new GrammarGraph(g) 146 | var guide = graph.createGuide('Sentence') 147 | 148 | t.deepEqual(guide.choices(), ['the']) 149 | t.deepEqual(guide.choices(1), ['the']) 150 | t.deepEqual(guide.choices(2), 151 | [ 152 | { val : 'the', 153 | next : [ { val: 'squirrel', next: [] }, 154 | { val: 'bird', next: [] }, 155 | { val: 'cat', next: [] }, 156 | { val: 'dog', next: [] } 157 | ] 158 | } 159 | ]) 160 | 161 | guide.choose(['the', 'dog', 'ate']) 162 | t.deepEqual(guide.construction(), ['the', 'dog', 'ate']) 163 | 164 | t.deepEqual(guide.choices(3), 165 | [ 166 | { val : 'the', 167 | next : [ 168 | { val : 'squirrel', 169 | next : [ { val: 'that', next: [] } ] 170 | }, 171 | { val : 'bird', 172 | next : [ { val: 'that', next: [] } ] 173 | }, 174 | { val : 'cat', 175 | next : [ { val: 'that', next: [] } ] 176 | }, 177 | { val : 'dog', 178 | next : [ { val: 'that', next: [] } ] 179 | } 180 | ] 181 | } 182 | ]) 183 | 184 | guide.choose([ 'the', 'squirrel', 'that', 'loved', 'the' ]) 185 | t.deepEqual(guide.construction(), 186 | [ 'the', 'dog', 'ate', 'the', 'squirrel', 'that', 'loved', 'the' ]) 187 | 188 | t.end() 189 | }) 190 | -------------------------------------------------------------------------------- /test/parse-grammar.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var parseGrammar = require('../lib/parse-grammar.js') 3 | var DecisionGraph = require('../lib/decision-graph.js') 4 | 5 | test('parseGrammar', function (t) { 6 | var g = { 7 | Sentence : ['NounPhrase VerbPhrase'], 8 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 9 | VerbPhrase : ['Verb', 'Verb NounPhrase'], 10 | RelativeClause : ['that VerbPhrase'], 11 | Noun : ['dog', 'cat', 'bird', 'squirrel'], 12 | Verb : ['befriended', 'loved', 'ate', 'attacked'] 13 | } 14 | 15 | var dg = parseGrammar(g) 16 | 17 | t.true(dg instanceof DecisionGraph) 18 | t.true(dg.isVertex('dog')) 19 | t.true(dg.isVertex('VerbPhrase')) 20 | t.true(dg.isTerminal('bird')) 21 | t.true(dg.isVertex('dog')) 22 | t.true(dg.isTypeAND('Sentence')) 23 | t.false(dg.isTypeAND('NounPhrase')) 24 | 25 | t.end() 26 | }) 27 | -------------------------------------------------------------------------------- /test/recognizer.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var Recognizer = require('../lib/recognizer.js') 3 | var DecisionGraph = require('../lib/decision-graph.js') 4 | 5 | test('Recognizer', function (t) { 6 | // set up decision graph 7 | var dg = new DecisionGraph() 8 | 9 | var ANDs = ['Sentence', '_NounPhrase_1', '_NounPhrase_2', '_VerbPhrase_1', 10 | 'RelativeClause', 'the', 'that', 'dog', 'cat', 'bird', 'squirrel', 11 | 'befriended', 'loved', 'ate', 'attacked'] 12 | var ORs = ['NounPhrase', 'VerbPhrase', 'Noun', 'Verb'] 13 | 14 | ANDs.forEach(function (andVertex) { 15 | dg.addVertexAND(andVertex) 16 | }) 17 | 18 | ORs.forEach(function (orVertex) { 19 | dg.addVertexOR(orVertex) 20 | }) 21 | 22 | dg.addEdge('Sentence', ['NounPhrase', 'VerbPhrase']) 23 | dg.addEdge('NounPhrase', ['_NounPhrase_1', '_NounPhrase_2']) 24 | dg.addEdge('VerbPhrase', ['Verb', '_VerbPhrase_1']) 25 | dg.addEdge('_NounPhrase_1', ['the', 'Noun']) 26 | dg.addEdge('_NounPhrase_2', ['the', 'Noun', 'RelativeClause']) 27 | dg.addEdge('_VerbPhrase_1', ['Verb', 'NounPhrase']) 28 | dg.addEdge('RelativeClause', ['that', 'VerbPhrase']) 29 | dg.addEdge('Noun', ['dog', 'cat', 'squirrel', 'bird']) 30 | dg.addEdge('Verb', ['befriended', 'loved', 'ate', 'attacked']) 31 | 32 | var recognizer = new Recognizer(dg, 'Sentence') 33 | t.true(recognizer instanceof Recognizer) 34 | 35 | t.true(recognizer.isValid('')) 36 | t.false(recognizer.isComplete('')) 37 | 38 | t.true(recognizer.isValid('the')) 39 | t.false(recognizer.isComplete('the')) 40 | 41 | t.true(recognizer.isValid('the dog')) 42 | t.false(recognizer.isComplete('the dog')) 43 | 44 | t.true(recognizer.isValid('the dog ate')) 45 | t.true(recognizer.isComplete('the dog ate')) 46 | 47 | t.false(recognizer.isValid('the dog ate the cat that is my friend')) 48 | t.false(recognizer.isComplete('the dog ate the cat that is my friend')) 49 | 50 | t.true(recognizer.isValid('the dog ate the cat that attacked the bird')) 51 | t.true(recognizer.isComplete('the dog ate the cat that attacked the bird')) 52 | t.end() 53 | }) 54 | -------------------------------------------------------------------------------- /test/reduce-grammar.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var reduceGrammar = require('../lib/reduce-grammar.js') 3 | 4 | test('reduceGrammar', function (t) { 5 | var g = { 6 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 7 | RelativeClause : ['that VerbPhrase'], 8 | Noun : ['dog', 'cat', 'bird'] 9 | } 10 | 11 | t.deepEqual(g, 12 | { 13 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 14 | RelativeClause : ['that VerbPhrase'], 15 | Noun : ['dog', 'cat', 'bird'] 16 | }, 'does not mutate argument') 17 | 18 | t.notEqual(reduceGrammar(g), g) 19 | 20 | t.deepEqual(reduceGrammar(g), { 21 | NounPhrase : ['_NounPhrase_1', '_NounPhrase_2'], 22 | _NounPhrase_1 : ['the Noun'], 23 | _NounPhrase_2 : ['the Noun RelativeClause'], 24 | RelativeClause : ['that VerbPhrase'], 25 | Noun : ['dog', 'cat', 'bird'] 26 | }) 27 | 28 | g = { 29 | Sentence : ['NounPhrase VerbPhrase'], 30 | NounPhrase : ['the Noun', 'the Noun RelativeClause'], 31 | VerbPhrase : ['Verb', 'Verb NounPhrase'], 32 | RelativeClause : ['that VerbPhrase'], 33 | Noun : ['dog', 'cat', 'bird', 'squirrel'], 34 | Verb : ['befriended', 'loved', 'ate', 'attacked'] 35 | } 36 | 37 | t.deepEqual(reduceGrammar(g), { 38 | Sentence : [ 'NounPhrase VerbPhrase' ], 39 | NounPhrase : [ '_NounPhrase_1', '_NounPhrase_2' ], 40 | VerbPhrase : [ 'Verb', '_VerbPhrase_1' ], 41 | RelativeClause : [ 'that VerbPhrase' ], 42 | Noun : [ 'dog', 'cat', 'bird', 'squirrel' ], 43 | Verb : [ 'befriended', 'loved', 'ate', 'attacked' ], 44 | _NounPhrase_1 : [ 'the Noun' ], 45 | _NounPhrase_2 : [ 'the Noun RelativeClause' ], 46 | _VerbPhrase_1 : [ 'Verb NounPhrase' ] 47 | }) 48 | 49 | g = { 50 | NounPhrase : ['the+Noun', 'the+Noun+RelativeClause'], 51 | RelativeClause : ['that+VerbPhrase'], 52 | Noun : ['dog', 'cat', 'bird'] 53 | } 54 | 55 | t.deepEqual(reduceGrammar(g, '+'), { 56 | NounPhrase : ['_NounPhrase_1', '_NounPhrase_2'], 57 | _NounPhrase_1 : ['the+Noun'], 58 | _NounPhrase_2 : ['the+Noun+RelativeClause'], 59 | RelativeClause : ['that+VerbPhrase'], 60 | Noun : ['dog', 'cat', 'bird'] 61 | }) 62 | 63 | t.end() 64 | }) 65 | -------------------------------------------------------------------------------- /test/utils/clone.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var clone = require('../../lib/utils/clone.js') 3 | 4 | test('clone()', function (t) { 5 | var sample = [1, 'a', 3, '4', 22, true, null, 0] 6 | t.deepEqual(clone(sample), sample) 7 | t.notEqual(clone(sample), sample) 8 | 9 | var sample2 = {tree: 'acorn', test: true, state: 42, nothing: null} 10 | t.deepEqual(clone(sample2), sample2) 11 | t.notEqual(clone(sample2), sample2) 12 | 13 | t.end() 14 | }) 15 | --------------------------------------------------------------------------------