elliptical
Version:
Interactive natural-language interfaces
590 lines (474 loc) • 16.4 kB
Markdown
# 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.