UNPKG

elliptical

Version:

Interactive natural-language interfaces

590 lines (474 loc) 16.4 kB
# Built-in Phrases There is nothing magical about any of these phrases. All of them are implemented using the same public API available to custom phrases. All phrases can take the following props: - `score: Number` - set to the `score` property of the output `Option`. Higher numbers sort higher. Defaults to `1`. - `value: Any` - set to the `result` property of the output `Option`. - `qualifiers: Array<Any> | qualifier: Any` - set to the `qualifiers` property of output `Option`. - `categories: Array<Any> | category: Any` - set to the `categories` property of output `Option`. - `annotations: Array<Any> | annotation: Any` - set to the `annotations` property of output `Option`. - `arguments: Array<Any> | argument: Any` - set to the `arguments` property of output `Option`. ## `literal` Matches or suggests a single literal string. ### Props - `text: String` - The string to accept as input. - `strategy: String ('start'|'contain'|'fuzzy')` - the matching strategy to use for this element. Note that fuzzy matching should rarely be used for literals - if you want to fuzzy match many items, use a [`list`](#list). - `decorate: Boolean` - if `true`, then suggest `text` even if it does not match the input. Useful for displaying implicit information. - `allowInput: Boolean` - if `true`, then force decoration, and do not consume any input even if exists. Only applies with `decorate`. ### Example ```js const parse = compile( <literal text='Lacona' value='http://lacona.io' /> ) parse('Lac') /* [ { words: [ {text: 'Lac', input: true}, {text: 'ona', input: false} ], score: 1, result: 'http://lacona.io' } ] */ ``` ## `choice` Branches the parsing logic. Consumes no characters. ## Result `Any` - One of the following items: - If the child has an `id` prop, the result will be an `Object` of the form `{[childId]: childResult}` - Otherwise, the `result` of the child for this branch ## Score The score of the child for this branch. ### Props - children - The `choice` can contain any number of child elements. Each child represents a separate branch that the parse can flow through. - `limit: Integer` - If `limit` children are parsed successfully (all the way to the end of the parse chain), then stop attempting to parse further children. This is very useful in situations where some children are synonymous, and there is no need suggest multiples. ### Example ```js const parse = compile( <choice limit={2}> <literal text='Google' value='http://google.com' /> <literal text='Gmail' value='http://mail.google.com' /> <literal text='Google Maps' value='http://maps.google.com' /> <literal text='Google Drive' value='http://drive.google.com' /> </choice> ) parse('Goog') /* [ { words: [ {text: 'Goo', input: true}, {text: 'gle', input: false} ], score: 1, result: 'http://google.com' }, { words: [ {text: 'Goo', input: true}, {text: 'gle Maps', input: false} ], score: 1, result: 'http://maps.google.com' } ] */ ``` ## `sequence` Parses its children in order, one after the other. ### Optional Every child of a `sequence` can set a property called `optional`. If it is `true`, It results in an implicit branch in the parse chain. If `optional` is set, the child can choose to set the props `limited` and `preferred`, which effect the output. All of these props are simply shorthand expressions, which map to `choice`s under the covers. All 3 props default to `false`. Here is how they are mapped: ##### `optional` ```js <sequence> <literal text='Google' /> <literal text=' Maps' optional /> </sequence> // becomes <sequence> <literal text='Google' /> <choice> <literal text='' /> <literal text=' Maps' /> </choice> </sequence> ``` ##### `optional preferred` Note the orders of the resulting `choice`'s children. ```js <sequence> <literal text='Google' /> <literal text=' Maps' optional preferred /> </sequence> // becomes <sequence> <literal text='Google' /> <choice> <literal text=' Maps' /> <literal text='' /> </choice> </sequence> ``` ##### `optional limited` Note the `limit` of the resulting `choice`. This is an easy way to say "accept this child if it's there, but don't suggest it." ```js <sequence> <literal text='Google' /> <literal text=' Maps' optional limited /> </sequence> // becomes <sequence> <literal text='Google' /> <choice limit={1}> <literal text='' /> <literal text=' Maps' /> </choice> </sequence> ``` ##### `optional preferred limited` This is an easy way to say "accept this child if it's there, but don't suggest its absence." ```js <sequence> <literal text='Google' /> <literal text=' Maps' optional preferred limited /> </sequence> // becomes <sequence> <literal text='Google' /> <choice limit={1}> <literal text=' Maps' /> <literal text='' /> </choice> </sequence> ``` There is another piece of functionality known as the `ellipsis`. If you sent a `<sequence />`s child to have `ellipsis={true}`, then it means "it is OK to stop here - make the rest of the sequence optional". This limits the options displayed to the user and can substantially improve understanding and performance. These two descriptions are precisely equivalent: ```js <sequence> <literal text='The ' /> <literal text='Batman' ellipsis /> <literal text=' and Robin' /> </literal> //becomes <sequence> <literal text='The ' /> <literal text='Batman' /> <sequence optional limited merge> <literal text=' and Robin' /> </sequence> </literal> ``` ### Result `Any` - One of the following items - the contents of the `value` prop - if there is no `value` prop, an `Object` that is composed of the results of the children: - If the child has an `id` prop, the resulting `Object` will have a key `{[childId]: childResult}` - If the child has the prop `merge`, the child's result will be merged into the resulting `Object` using `_.merge`. ### Score The score of all parsed children, multiplied together. ### Props - `unique: Boolean` - If `true`, the sequence will check the `id` prop of each child before parsing. If the child's `id` prop is already in the `result` object (from an child earlier in the chain), it will be skipped. Useful with `optional` children. ### Example ```js const parse = compile( <sequence> <literal text='Google' value='google' id='base' /> <literal text=' Maps' value='maps' id='sub' score={0.5} optional /> <literal text=' rocks!' /> </sequence> ) parse('Goog') /* [ { words: [ {text: 'Goo', input: true}, {text: 'gle', input: false}, {text: ' rocks!', input: false} ], score: 1, result: {base: 'google'} }, { words: [ {text: 'Goo', input: true}, {text: 'gle', input: false}, {text: ' Maps', input: false}, {text: ' rocks!', input: false} ], score: 0.5, result: {base: 'google', sub: 'maps'} } ] */ ``` ## `repeat` Allows an element to be repeated multiple times, with an optional separator. ### Result `Array` - An array of the values of each child, in the order they were parsed ### Score `1` ### Props - `separator: Element` - A single element, which will be placed in sequence between every repetition. Its result is ignored. - `unique: Boolean` - If `true`, the `repeat` will not allow repetitions that have the same `result`. That is to say, the `repeat`'s `result` is guaranteed to be `unique`. - `max: Number` - the maximum number of repetitions to allow. Defaults to unlimited. - `min: Number` - the minimum number of repetitions to allow. Defaults to 1. ### Example ```js const parse = compile( <repeat separator={<literal text=' '/>} max={3}> <literal text='wort' value='go' /> </repeat> ) parse('wort wort') /* [ { words: [ {text: 'wort', input: true}, {text: ' ', input: true}, {text: 'wort', input: true} ], score: 1, result: ['go', 'go'] }, { words: [ {text: 'wort', input: true}, {text: ' ', input: true}, {text: 'wort', input: true}, {text: ' ', input: false}, {text: 'wort', input: false} ], score: 1, result: ['go', 'go', 'go'] } ] */ ``` ## `placeholder` Parses that have consumed the entire input string will not continue into this placeholder's `child`. Instead, it will output a word with `{placeholder: true, label: label}` This improves performance and usability, as it limits the number of suggestions that are output for an incomplete input. ### Score `placeholder`s that suppress input have a very low score, to ensure that completed suggestions appear before incomplete ones. ### Props - `label: Any` - An object describing the placeholder. If the placeholder suppresses parsing, it will output this label in its `Word`. - `suppressEmpty: Boolean` - defaults to `true`. If `true`, this `placeholder` will also suppress inputs that are an empty string. That is to say, if the preceding elements consume the entire input string but have not yet made any suggestions, this placeholder will still suppress the input. - `suppressWhen: (input: String) => Boolean` - When this placeholder is parsed, it will call this function. If it returns `true`, this placeholder will suppress the input (returning a `placeholder`), even if the input is non-null. This is useful to describe incomplete but non-suggestable input. - For example, imagine a phrase is designed to accept a 3-digit integer, but the user has only entered a single digit. The input is not yet valid, and it does not make sense to suggest a completion, but it also needs to show the user that they are on their way to a valid input. In this case, it makes sense to use something like `suppressWhen={(input) => /^\d{1,2}$/.test(input)}`. ## `list` Logically, a `<list>` can be thought of as a `<choice>` of `<literal>` elements. However, it has enhancements that allow it to work better for large lists, especially when limiting and fuzzy matching is used. It also performs better. In general, everytime you have a `<choice>` containing only `<literal>` elements, you should use a `<list>` instead. ### Result `Any` - The `value` of the `item`, or `undefined` ### Props - `items: Array<{String | Object}>` - An array valid items. The parse will branch for each one. If `item` is a `String`, it is equivalent to `{text: item}`. Each `item` is an `Object` with these properties: - `text: String` - The text to parse - `value: Any` - The `list`'s result in this parse branch - `qualifiers: Array<Any> | qualifier: Any` - `arguments: Array<Any> | argument: Any` - `annotations: Array<Any> | annotation: Any` - `categories: Array<Any> | category: Any` - `strategy: String ('start'|'contain'|'fuzzy')` - Matching strategy to use for these items. - `limit: Integer` - If `<limit>` `items` are parsed successfully (all the way to the end of the parse chain), then stop attempting to parse further children. Note that the outputs have a score, sorting is applied *before* limiting, so the best matches will not be limited. ### Example ```js const parse = compile( <list limit={2} items={[ {text: 'Google', value: 'http://google.com'}, {text: 'Gmail', value: 'http://mail.google.com'}, {text: 'Google Maps', value: 'http://maps.google.com'}, {text: 'Google Drive', value: 'http://drive.google.com'} ]} /> ) parse('gm') /* [ { words: [ {text: 'Gm', input: true}, {text: 'ail', input: false} ], score: 1, result: 'http://mail.google.com' }, { words: [ {text: 'Goo', input: true}, {text: 'gle Maps', input: false} ], score: 1, result: 'http://maps.google.com' } ] */ ``` ## `freetext` Accept arbitrary input. Note that this phrase contains significant implicit branching, as it will attempt to validate and parse every substring between `substr(0, 1)` and `substr(0)`. Use the `splitOn` and `limit` properties to improve performance. ### Result `String` - the substring that was validated and parsed. ### Score Higher scores are given to shorter substrings. Therefore, the highest-scored parse chain will be the one in which the freetext consumed the fewest characters. ### Props - `splitOn: String | RegExp` - Argument to `String::split` to determine which substrings to attempt parsing. - `consumeAll: Boolean` - Do not attempt to parse substrings, only parse the entire remaining string. Improves performance if the `freetext` is the final phrase in a command - `filter: Function(input: String) => Boolean` - Better-performing shorthand for `<filter function={filter}><freetext ...otherProps /></filter>`. - `limit: Integer` - If `<limit>` substrings are parsed successfully (all the way to the end of the parse chain), then stop attempting to parse further substrings. - `greedy: Boolean` - Used alongside limiting. Which order should input substrings be attempted in ## `dynamic` Generate grammars dynamically based upon input. ### Props - `describe: Function(input:String, option:Option)` - This function is called with each input substring. It is also passed the current `option`, which contains the current `result`. If it returns an `Element`, that element will be parsed with that input in place of this element. - `splitOn: String | RegExp` - Argument to `String::split` to determine which substrings to attempt parsing. - `consumeAll: Boolean` - Do not attempt to parse substrings, only parse the entire remaining string. Improves performance if the `freetext` is the final phrase in a command - `limit: Integer` - If `<limit>` substrings are parsed successfully (all the way to the end of the parse chain), then stop attempting to parse further substrings. - `greedy: Boolean` - Used alongside limiting. Which order should input substrings be attempted in ## `filter` Filter options with an arbitrary function. ### Props - `inbound: (option: Option) => Boolean` - If it returns false, children will not be parsed - `outbound: (option: Option) => Boolean` - Called for each child output option. If it returns true, it will not output that option. - `skipIncomplete: Boolean` - if true, `outbound` will not be called if the output option contains a placeholder ### Example ```js const parse = compile( <filter outbound={(option) => _.isString(option.result)}> <list items={[ {text: 'some string', value: 'string'}, {text: 'some object', value: {key: 'value'}} ]} /> </filter> ) parse('some ') /* [{ words: [ {text: 'some ', input: true}, {text: 'string', input: false} ], score: 1, result: 'string' }] */ ``` ## `map` Modify options with an arbitrary function. ### Props - `inbound: (option: Option) => Option` - Changes an option before parsing children - `outbound: (option: Option) => (Option | Iterable<Option>)` - Changes the output options after parsing children. If it returns an Iterable, all options will be output - `limit: Number` - Limits output if `outbound` returns an Iterable - `skipIncomplete: Boolean` - if true, `outbound` will not be called if the output option contains a placeholder ### Example ```js const parse = compile( <map outbound={(option) => _.merge({}, option, {result: 'test'})}> <literal text='lacona' value='lacona' /> </repeat> ) parse('lac') /* [{ words: [ {text: 'lac', input: true}, {text: 'ona', input: false} ], score: 1, result: 'test' }] */ ``` ## `tap` Does not effect parsing at all. Calls a function if the parse reaches it, and continues parsing. Useful for debugging, or interacting with the outside world based upon parsing. ### Props - `inbound` - `Function(input: String)` - called everytime this element is visited - `outbound` - `Function(input: String)` - called everytime this element's child outputs an option ### Example ```js const parse = compile( <tap outbound={console.log}> <literal text='lacona' /> </repeat> ) parse('lac') /* logs: {text: null, words: [...], ...} */ ``` ## `raw` The lowest-level phrase, which allows completely arbitrary manipulation of outputs.