From eba72708a3c785ae24a65dab0877bd858526465e Mon Sep 17 00:00:00 2001 From: Andreas Holstenson Date: Sun, 29 Sep 2019 17:34:58 +0200 Subject: [PATCH] refactor: Splitting Matcher into Graph and GraphMatcher --- jest.config.js | 2 +- src/ActionsBuilder.ts | 36 +++--- src/IntentsBuilder.ts | 10 +- src/graph/Graph.ts | 25 ++++ src/graph/GraphBuilder.ts | 94 ++++---------- src/graph/GraphOptions.ts | 5 + src/graph/KnownGraphs.ts | 48 ++++++++ src/graph/SubNode.ts | 11 +- src/graph/matching/DefaultMatcher.ts | 99 --------------- src/graph/matching/Encounter.ts | 4 +- src/graph/matching/EncounterOptions.ts | 3 +- src/graph/matching/GraphMatcher.ts | 70 +++++++++++ src/graph/matching/MatchReductionEncounter.ts | 17 +++ src/graph/matching/index.ts | 2 +- src/language/AbstractLanguage.ts | 38 ++---- src/language/Language.ts | 29 ++--- src/language/LanguageGraphFactory.ts | 20 +++ .../en/{boolean.ts => booleanGraph.ts} | 11 +- ...{date-duration.ts => dateDurationGraph.ts} | 16 +-- src/language/en/{date.ts => dateGraph.ts} | 43 +++---- ...{date-interval.ts => dateIntervalGraph.ts} | 19 ++- ...e-duration.ts => dateTimeDurationGraph.ts} | 18 ++- .../en/{date-time.ts => dateTimeGraph.ts} | 20 ++- .../en/{day-of-week.ts => dayOfWeekGraph.ts} | 11 +- src/language/en/index.ts | 83 ++++++------- .../en/{integer.ts => integerGraph.ts} | 16 +-- src/language/en/{month.ts => monthGraph.ts} | 30 ++--- src/language/en/{number.ts => numberGraph.ts} | 22 ++-- .../en/{ordinal.ts => ordinalGraph.ts} | 22 ++-- .../en/{quarter.ts => quarterGraph.ts} | 17 ++- src/language/en/repeating.ts | 20 ++- ...{time-duration.ts => timeDurationGraph.ts} | 16 ++- src/language/en/{time.ts => timeGraph.ts} | 23 ++-- src/language/en/{week.ts => weekGraph.ts} | 16 ++- src/language/en/{year.ts => yearGraph.ts} | 18 ++- src/numbers/numbers.ts | 2 +- src/numbers/ordinals.ts | 5 +- src/resolver/ResolvedIntent.ts | 8 +- src/resolver/ResolvedIntents.ts | 6 +- src/resolver/ResolverBuilder.ts | 52 ++++---- src/resolver/ResolverParser.ts | 22 ++-- src/resolver/ValueNode.ts | 2 +- src/resolver/ValueParserNode.ts | 21 ++-- src/resolver/expressions.ts | 4 +- src/time/date-intervals.ts | 4 +- src/time/date-times.ts | 6 +- src/time/dates.ts | 4 +- src/time/durations.ts | 2 +- src/time/months.ts | 2 +- src/time/quarters.ts | 6 +- src/time/times.ts | 2 +- src/time/weeks.ts | 6 +- src/time/years.ts | 2 +- src/types/index.ts | 1 + src/values/any.ts | 4 +- src/values/base.ts | 56 ++++++--- src/values/boolean.ts | 8 +- src/values/custom.ts | 4 +- src/values/date-duration.ts | 9 +- src/values/date-interval.ts | 9 +- src/values/date-time-duration.ts | 9 +- src/values/date-time.ts | 9 +- src/values/date.ts | 9 +- src/values/enumeration.ts | 12 +- src/values/integer.ts | 9 +- src/values/number.ts | 9 +- src/values/options.ts | 104 ++++++++++++---- src/values/ordinal.ts | 9 +- src/values/time-duration.ts | 9 +- src/values/time.ts | 9 +- test/builder.test.ts | 84 +++++++------ test/intents.test.ts | 6 +- test/language/en/date-duration.test.ts | 7 +- test/language/en/date-interval.test.ts | 4 +- test/language/en/date-time-duration.test.ts | 7 +- test/language/en/date-time.test.ts | 7 +- test/language/en/date.test.ts | 4 +- test/language/en/day-of-week.test.ts | 6 +- test/language/en/integer.test.ts | 7 +- test/language/en/month.test.ts | 7 +- test/language/en/number.test.ts | 7 +- test/language/en/ordinal.test.ts | 7 +- test/language/en/repeating.test.ts | 11 +- test/language/en/time-duration.test.ts | 7 +- test/language/en/time.test.ts | 4 +- test/language/en/year.test.ts | 7 +- test/language/helpers.ts | 36 ++++-- test/resolver.test.ts | 52 ++++---- test/time/dates.test.ts | 54 ++++---- test/time/months.test.ts | 14 +-- test/time/times.test.ts | 26 ++-- test/time/years.test.ts | 10 +- test/value-custom.test.ts | 16 +-- test/value-options.test.ts | 115 +++++++++--------- test/value.enum.test.ts | 19 ++- 95 files changed, 1047 insertions(+), 846 deletions(-) create mode 100644 src/graph/Graph.ts create mode 100644 src/graph/GraphOptions.ts create mode 100644 src/graph/KnownGraphs.ts create mode 100644 src/graph/matching/GraphMatcher.ts create mode 100644 src/graph/matching/MatchReductionEncounter.ts create mode 100644 src/language/LanguageGraphFactory.ts rename src/language/en/{boolean.ts => booleanGraph.ts} (55%) rename src/language/en/{date-duration.ts => dateDurationGraph.ts} (77%) rename src/language/en/{date.ts => dateGraph.ts} (83%) rename src/language/en/{date-interval.ts => dateIntervalGraph.ts} (77%) rename src/language/en/{date-time-duration.ts => dateTimeDurationGraph.ts} (54%) rename src/language/en/{date-time.ts => dateTimeGraph.ts} (59%) rename src/language/en/{day-of-week.ts => dayOfWeekGraph.ts} (71%) rename src/language/en/{integer.ts => integerGraph.ts} (82%) rename src/language/en/{month.ts => monthGraph.ts} (76%) rename src/language/en/{number.ts => numberGraph.ts} (67%) rename src/language/en/{ordinal.ts => ordinalGraph.ts} (56%) rename src/language/en/{quarter.ts => quarterGraph.ts} (62%) rename src/language/en/{time-duration.ts => timeDurationGraph.ts} (79%) rename src/language/en/{time.ts => timeGraph.ts} (88%) rename src/language/en/{week.ts => weekGraph.ts} (61%) rename src/language/en/{year.ts => yearGraph.ts} (66%) diff --git a/jest.config.js b/jest.config.js index 1a20160..f96cfdd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,7 +19,7 @@ module.exports = { globals: { "ts-jest": { "diagnostics": { - "ignoreCodes": "TS2531" + "ignoreCodes": [ "TS2531", "TS2532" ] } } } diff --git a/src/ActionsBuilder.ts b/src/ActionsBuilder.ts index 93b1b81..fb10860 100644 --- a/src/ActionsBuilder.ts +++ b/src/ActionsBuilder.ts @@ -5,20 +5,22 @@ import { Matcher, EncounterOptions } from './graph/matching'; import { ResolvedIntent } from './resolver/ResolvedIntent'; import { ResolvedIntents } from './resolver/ResolvedIntents'; -export type Action = (item: ResolvedIntent) => void; +export type Action = (item: ResolvedIntent) => void; export class ActionsBuilder { + private language: Language; private builder: IntentsBuilder; private handlers: Map; private id: number; constructor(lang: Language) { + this.language = lang; this.builder = new IntentsBuilder(lang); this.handlers = new Map(); this.id = 0; } - public action(id?: string): ActionBuilder { + public action(id?: string): ActionBuilder<{}> { // Auto assign an id const actualId = id ? id : id = ('__auto__' + ++this.id); @@ -28,7 +30,7 @@ export class ActionsBuilder { return { value(valueId, type) { builder.value(valueId, type); - return this; + return this as any; }, add(...args) { @@ -49,35 +51,37 @@ export class ActionsBuilder { } public build() { - return new Actions(this.builder.build(), this.handlers); + return new Actions(this.language, this.builder.build(), this.handlers); } } -export interface ActionBuilder { - value(id: string, type: Value): this; +export interface ActionBuilder { + value(id: I, type: Value): ActionBuilder; add(...args: string[]): this; - handler(func: Action): this; + handler(func: Action): this; done(): ActionsBuilder; } export class Actions { + public readonly language: Language; private handlers: Map; - private matcher: Matcher; - - constructor(matcher: Matcher, handlers: Map) { + private matcher: Matcher>; + + constructor( + language: Language, + matcher: Matcher>, + handlers: Map + ) { + this.language = language; this.matcher = matcher; this.handlers = handlers; } - get language() { - return this.matcher.language; - } - public match(expression: string, options: EncounterOptions): Promise { - const map = (item: ResolvedIntent): ResolvedAction => { + const map = (item: ResolvedIntent): ResolvedAction => { const result = item as any; result.activate = (...args: any[]) => { const handler = this.handlers.get(item.intent); @@ -98,7 +102,7 @@ export class Actions { } } -export interface ResolvedAction extends ResolvedIntent { +export interface ResolvedAction extends ResolvedIntent { activate: () => void; } diff --git a/src/IntentsBuilder.ts b/src/IntentsBuilder.ts index 72e0593..78fe006 100644 --- a/src/IntentsBuilder.ts +++ b/src/IntentsBuilder.ts @@ -1,6 +1,8 @@ import { ResolverBuilder } from './resolver/ResolverBuilder'; import { Language } from './language/Language'; import { Value } from './values/base'; +import { Matcher } from './graph/matching'; +import { ResolvedIntents } from './resolver/ResolvedIntents'; export class IntentsBuilder { private language: Language; @@ -24,7 +26,7 @@ export class IntentsBuilder { const self = this; const instance = new ResolverBuilder(this.language, id); return { - value(valueId: string, type: Value) { + value(valueId: string, type: Value) { instance.value(valueId, type); return this; }, @@ -41,13 +43,13 @@ export class IntentsBuilder { }; } - public build() { - return this.builder.build(); + public build(): Matcher> { + return this.builder.toMatcher(); } } export interface IntentBuilder { - value(id: string, type: Value): this; + value(id: string, type: Value): this; add(...args: string[]): this; diff --git a/src/graph/Graph.ts b/src/graph/Graph.ts new file mode 100644 index 0000000..c67cfd2 --- /dev/null +++ b/src/graph/Graph.ts @@ -0,0 +1,25 @@ +import { Node } from './Node'; +import { MatchingState } from './matching'; +import { GraphOptions } from './GraphOptions'; + +/** + * Graph that has been built via GraphBuilder. Graphs are a collection of + * outgoing nodes that can parse an expression. + */ +export interface Graph { + /** + * The outgoing nodes of this graph. + */ + nodes: Node[]; + + /** + * Options to apply during matching of this graph. + */ + options: GraphOptions; + + /** + * Internal state of this matcher that is accessed if it is used as a + * sub graph. + */ + matchingState: MatchingState; +} diff --git a/src/graph/GraphBuilder.ts b/src/graph/GraphBuilder.ts index 4f7f3f8..5aa07fb 100644 --- a/src/graph/GraphBuilder.ts +++ b/src/graph/GraphBuilder.ts @@ -5,11 +5,11 @@ import { SubNode } from './SubNode'; import { CollectorNode, Collectable } from './CollectorNode'; import { CustomNode, TokenValidator } from './CustomNode'; -import { Matcher } from './matching/Matcher'; -import { DefaultMatcher, MatcherOptions, MatchReductionEncounter } from './matching/DefaultMatcher'; import { Language } from '../language/Language'; -import { Encounter, Match } from './matching'; +import { emptyState } from './matching'; import { Predicate } from '../utils/predicates'; +import { Graph } from './Graph'; +import { GraphOptions } from './GraphOptions'; /** * Object that can be mapped into another one. @@ -18,7 +18,7 @@ export interface MappableObject { [x: string]: V; } -export type GraphBuildable = string | RegExp | Node | Matcher | ((builder: GraphBuilder) => Node); +export type GraphBuildable = string | RegExp | Node | Graph | ((builder: GraphBuilder) => Node); export type GraphBuildableArray = GraphBuildable | GraphBuildable[]; @@ -27,10 +27,10 @@ export type GraphBuildableArray = GraphBuildable | GraphBuildable[]; * be used within the graph or standalone to match expressions. * */ -export class GraphBuilder { +export class GraphBuilder { protected language: Language; private nodes: Node[]; - private options: MatcherOptions; + protected options: GraphOptions; constructor(language: Language) { this.language = language; @@ -152,12 +152,12 @@ export class GraphBuilder { return push(result); } else if(n instanceof RegExp) { return push(new RegExpNode(n)); - } else if(n instanceof DefaultMatcher) { - return push(new SubNode(n, n.options)); } else if(n instanceof Node) { return push(n); } else if(typeof n === 'string') { return push(this.parse(n)); + } else if('nodes' in n) { + return push(new SubNode(n, n.options)); } else { throw new Error('Invalid node'); } @@ -204,77 +204,37 @@ export class GraphBuilder { return this; } - /** - * Setup a mapper that turns the intermediate representation into the - * public facing value type. - */ - public mapResults(mapper: (result: V, encounter: Encounter) => N): GraphBuilder { - const self = this as unknown as GraphBuilder; - self.options.mapper = mapper; - return self; - } - - /** - * Reduce the results down to a new object. This can be used to perform - * a transformation on all of the results at once. - * - * @param func - */ - public reducer(func: (results: MatchReductionEncounter) => NewR): GraphBuilder { - const self = this as unknown as GraphBuilder; - self.options.reducer = func; - return self; - } - - /** - * Reduce the results down so only the best match is returned when this - * matcher is queried. - */ - public onlyBest(): GraphBuilder { - return this.reducer(({ results, map }) => { - const match = results.first(); - if(match) { - return map(match.data); - } else { - return null; - } - }); - } - - /** - * Build this graph and turn it into a matcher. - */ - public toMatcher(): Matcher { - return this.createMatcher(this.language, this.nodes, this.options); - } - - protected createMatcher(lang: Language, nodes: Node[], options: MatcherOptions): Matcher { - return new DefaultMatcher(lang, nodes, options); + public build(): Graph { + return { + nodes: this.nodes, + options: this.options, + matchingState: emptyState() + }; } - public static result(matcher?: Matcher | Predicate, validator?: Predicate): (builder: GraphBuilder) => Node { + public static result(graph?: Graph | Predicate, validator?: Predicate): (builder: GraphBuilder) => Node { if(typeof validator === 'undefined') { - if(typeof matcher === 'function') { - validator = matcher; - matcher = undefined; - } else if(matcher) { - throw new Error('Expected graph or a validation function, got ' + matcher); + if(typeof graph === 'function') { + validator = graph; + graph = undefined; + } else if(graph) { + throw new Error('Expected graph or a validation function, got ' + graph); } } - if(matcher && ! (matcher instanceof DefaultMatcher)) { - throw new Error('matcher is not actually an instance of Matcher'); + if(graph && ! ('nodes' in graph)) { + throw new Error('Given graph is not valid'); } return function(builder: GraphBuilder) { - const sub = matcher instanceof DefaultMatcher - ? new SubNode(matcher, matcher.options, validator) + const sub = graph && 'nodes' in graph + ? new SubNode(graph, graph.options, validator) : new SubNode(builder.nodes, builder.options as any, validator); - sub.recursive = ! matcher; + sub.recursive = ! graph; if(validator) { - let name = matcher instanceof DefaultMatcher ? matcher.options.name : builder.options.name; + let name = (graph && 'nodes' in graph) ? graph.options.name : builder.options.name; if(validator.name) { if(name) { name += ':' + validator.name; @@ -283,7 +243,7 @@ export class GraphBuilder { } } sub.name = name || 'unknown'; - } else if(! matcher) { + } else if(! graph) { sub.name = builder.options.name + ':self'; } diff --git a/src/graph/GraphOptions.ts b/src/graph/GraphOptions.ts new file mode 100644 index 0000000..b3fa575 --- /dev/null +++ b/src/graph/GraphOptions.ts @@ -0,0 +1,5 @@ +import { EncounterOptions } from './matching'; + +export interface GraphOptions extends EncounterOptions { + name?: string; +} diff --git a/src/graph/KnownGraphs.ts b/src/graph/KnownGraphs.ts new file mode 100644 index 0000000..640d767 --- /dev/null +++ b/src/graph/KnownGraphs.ts @@ -0,0 +1,48 @@ +import { NumberData } from '../numbers/NumberData'; +import { DateTimeData } from '../time/DateTimeData'; +import { OrdinalData } from '../numbers/OrdinalData'; + +/** + * Graphs that are known to be available for a language. This enum used when + * fetching a graph from a language and to lookup the type the graph uses. + * + * This allows for type-safety within the values. + */ +export enum KnownGraphs { + Boolean = 'boolean', + DateDuration = 'date-duration', + Date = 'date', + DateInterval = 'date-interval', + DateTimeDuration = 'date-time-duration', + DateTime = 'date-time', + Integer = 'integer', + Month = 'month', + Number = 'number', + Ordinal = 'ordinal', + Quarter = 'quarter', + TimeDuration = 'time-duration', + Time = 'time', + Week = 'week', + Year = 'year' +} + +/** + * Types for `KnownGraphs`. + */ +export interface KnownGraphsDataTypes { + [KnownGraphs.Boolean]: boolean; + [KnownGraphs.DateDuration]: DateTimeData; + [KnownGraphs.Date]: DateTimeData; + [KnownGraphs.DateInterval]: DateTimeData; + [KnownGraphs.DateTimeDuration]: DateTimeData; + [KnownGraphs.DateTime]: DateTimeData; + [KnownGraphs.Integer]: NumberData; + [KnownGraphs.Month]: DateTimeData; + [KnownGraphs.Number]: NumberData; + [KnownGraphs.Ordinal]: OrdinalData; + [KnownGraphs.Quarter]: DateTimeData; + [KnownGraphs.TimeDuration]: DateTimeData; + [KnownGraphs.Time]: DateTimeData; + [KnownGraphs.Week]: DateTimeData; + [KnownGraphs.Year]: DateTimeData; +} diff --git a/src/graph/SubNode.ts b/src/graph/SubNode.ts index 949c03e..8546444 100644 --- a/src/graph/SubNode.ts +++ b/src/graph/SubNode.ts @@ -1,7 +1,10 @@ import { Node } from './Node'; -import { DefaultMatcher, Matcher, MatcherOptions, Encounter, Match, MatchingState, emptyState } from './matching'; +import { Encounter, Match, MatchingState, emptyState } from './matching'; import { Predicate, alwaysTruePredicate } from '../utils/predicates'; +import { Graph } from './Graph'; +import { GraphOptions } from './GraphOptions'; + /* * Small penalty applied when a SubNode matches. This helps the algorithm * prefer less parser matches. So if a parser P1 can match (A | A B) and @@ -41,7 +44,7 @@ export class SubNode extends Node { private supportsFuzzy: boolean; private filter: Predicate; - public mapper: ((result: any, encounter: Encounter) => any) | undefined; + public mapper: ((result: V, encounter: Encounter) => any) | undefined; /** * Fallback value to apply in case this is a partial match and the graph @@ -49,13 +52,13 @@ export class SubNode extends Node { */ public partialFallback?: any; - constructor(roots: DefaultMatcher | Node[], options: MatcherOptions, filter?: Predicate) { + constructor(roots: Graph | Node[], options: GraphOptions, filter?: Predicate) { super(); this.recursive = false; this.filter = filter || alwaysTruePredicate; - if(roots instanceof DefaultMatcher) { + if('nodes' in roots) { // Roots is actually a matcher, copy the graph from the matcher this.roots = roots.nodes; this.state = roots.matchingState; diff --git a/src/graph/matching/DefaultMatcher.ts b/src/graph/matching/DefaultMatcher.ts index 0f727f6..e69de29 100644 --- a/src/graph/matching/DefaultMatcher.ts +++ b/src/graph/matching/DefaultMatcher.ts @@ -1,99 +0,0 @@ -import { Encounter } from './Encounter'; -import { Language } from '../../language/Language'; -import { Node } from '../Node'; - -import { MatchingState, emptyState } from './MatchingState'; -import { MatchSet } from './MatchSet'; -import { MatchOptions } from './MatchOptions'; -import { EncounterOptions } from './EncounterOptions'; - -export interface MatchReductionEncounter { - encounter: Encounter; - - results: MatchSet; - - map: (object: RawData) => MappedData; -} - -export interface MatcherOptions extends EncounterOptions { - name?: string; - - reducer?: (reduction: MatchReductionEncounter) => V; - - mapper?: (result: any, encounter: Encounter) => any; -} - -/** - * Matcher that can match expressions against a graph. - */ -export class DefaultMatcher { - public readonly language: Language; - public readonly nodes: Node[]; - public readonly options: MatcherOptions; - - /** - * Internal state of this matcher that is accessed if it is used as a - * sub graph. - */ - public matchingState: MatchingState; - - constructor(language: Language, nodes: Node[], options: MatcherOptions) { - this.language = language; - this.nodes = nodes; - - this.options = options; - this.matchingState = emptyState(); - } - - /** - * Match against the given expression. - * - * @param {string} expression - * @param {object} options - * @return {Promise} - */ - public match(expression: string, options: MatchOptions={}): Promise { - if(typeof expression !== 'string') { - throw new Error('Can only match against string expressions'); - } - - const resolvedOptions = Object.assign({ - onlyComplete: true - }, this.options, options); - - const tokens = this.language.tokenize(expression); - const encounter = new Encounter(tokens, resolvedOptions); - encounter.outgoing = this.nodes; - - const promise = encounter.next(0, 0) - .then(() => { - return encounter.matches; - }); - - if(this.options.reducer) { - const reducer = this.options.reducer; - return promise.then((results: MatchSet) => reducer({ - results, - encounter, - map: (object: any) => this.options.mapper - ? this.options.mapper(object, encounter) - : object - } - )); - } else { - return promise.then((results: MatchSet) => { - const asArray = results.toArray(); - let mapped; - if(this.options.mapper) { - const mapper = this.options.mapper; - mapped = asArray.map(match => mapper(match.data, encounter)); - } else { - mapped = asArray.map(match => match.data); - } - - // Forcefully convert into V type - return mapped as unknown as V; - }); - } - } -} diff --git a/src/graph/matching/Encounter.ts b/src/graph/matching/Encounter.ts index 001ac9e..a6e2320 100644 --- a/src/graph/matching/Encounter.ts +++ b/src/graph/matching/Encounter.ts @@ -53,7 +53,7 @@ export class Encounter { this.currentData = []; this.currentDataDepth = 0; this.matches = new MatchSet({ - isEqual: options.matchIsEqual + isEqual: options.matchIsEqual && options.matchIsEqual(options) }); this.maxDepth = 0; @@ -306,7 +306,7 @@ export class Encounter { * a graph. This is used by sub-nodes to cache their results based on the * start index. * - * @param {number} index + * @param index */ public cache(index=this.currentIndex): Map { let map = this._cache[index]; diff --git a/src/graph/matching/EncounterOptions.ts b/src/graph/matching/EncounterOptions.ts index bfc2f37..cc579ea 100644 --- a/src/graph/matching/EncounterOptions.ts +++ b/src/graph/matching/EncounterOptions.ts @@ -1,5 +1,6 @@ import { Match } from './Match'; import { DateTimeOptions } from '../../time/DateTimeOptions'; +import { Encounter } from './Encounter'; /** * Options that can be passed to an Encounter. @@ -38,7 +39,7 @@ export interface EncounterOptions extends DateTimeOptions { /** * Method used to determine if two matches are equal. */ - matchIsEqual?: (a: any, b: any) => boolean; + matchIsEqual?: (options: EncounterOptions) => (a: any, b: any) => boolean; /** * Function called when a match is found. diff --git a/src/graph/matching/GraphMatcher.ts b/src/graph/matching/GraphMatcher.ts new file mode 100644 index 0000000..320249d --- /dev/null +++ b/src/graph/matching/GraphMatcher.ts @@ -0,0 +1,70 @@ +import { Encounter } from './Encounter'; +import { Language } from '../../language/Language'; +import { Node } from '../Node'; + +import { MatchingState, emptyState } from './MatchingState'; +import { MatchSet } from './MatchSet'; +import { MatchOptions } from './MatchOptions'; +import { EncounterOptions } from './EncounterOptions'; +import { Matcher } from './Matcher'; +import { Graph } from '../Graph'; +import { MatchReductionEncounter } from './MatchReductionEncounter'; + +export interface GraphMatcherOptions extends EncounterOptions { + reducer: (reduction: MatchReductionEncounter) => V; +} + +/** + * Matcher that can match expressions against a graph. + */ +export class GraphMatcher implements Matcher { + public readonly language: Language; + public readonly graph: Graph; + public readonly options: GraphMatcherOptions; + + /** + * Internal state of this matcher that is accessed if it is used as a + * sub graph. + */ + public matchingState: MatchingState; + + constructor(language: Language, graph: Graph, options: GraphMatcherOptions) { + this.language = language; + this.graph = graph; + + this.options = Object.assign({}, graph.options, options); + this.matchingState = emptyState(); + } + + /** + * Match against the given expression. + * + * @param {string} expression + * @param {object} options + * @return {Promise} + */ + public match(expression: string, options: MatchOptions={}): Promise { + if(typeof expression !== 'string') { + throw new Error('Can only match against string expressions'); + } + + const resolvedOptions = Object.assign({ + onlyComplete: true + }, this.options, options); + + const tokens = this.language.tokenize(expression); + const encounter = new Encounter(tokens, resolvedOptions); + encounter.outgoing = this.graph.nodes; + + const promise = encounter.next(0, 0) + .then(() => { + return encounter.matches; + }); + + const reducer = this.options.reducer; + return promise.then((results: MatchSet) => reducer({ + results, + encounter + })); + } +} diff --git a/src/graph/matching/MatchReductionEncounter.ts b/src/graph/matching/MatchReductionEncounter.ts new file mode 100644 index 0000000..58b274a --- /dev/null +++ b/src/graph/matching/MatchReductionEncounter.ts @@ -0,0 +1,17 @@ +import { Encounter } from './Encounter'; +import { MatchSet } from './MatchSet'; + +/** + * Encounter used to reduce matches + */ +export interface MatchReductionEncounter { + /** + * The current encounter. + */ + encounter: Encounter; + + /** + * Results that are matching. + */ + results: MatchSet; +} diff --git a/src/graph/matching/index.ts b/src/graph/matching/index.ts index e29c55d..3336e98 100644 --- a/src/graph/matching/index.ts +++ b/src/graph/matching/index.ts @@ -1,6 +1,6 @@ export * from './Encounter'; export * from './EncounterOptions'; -export * from './DefaultMatcher'; +export * from './GraphMatcher'; export * from './Match'; export * from './Matcher'; export * from './MatchHandler'; diff --git a/src/language/AbstractLanguage.ts b/src/language/AbstractLanguage.ts index 5418610..587da7c 100644 --- a/src/language/AbstractLanguage.ts +++ b/src/language/AbstractLanguage.ts @@ -3,13 +3,16 @@ import { Tokens, Token } from './tokens'; import { ValueMatcherFactory } from './ValueMatcherFactory'; import { Matcher } from '../graph/matching'; import { GraphBuilder } from '../graph/GraphBuilder'; +import { Graph } from '../graph/Graph'; +import { LanguageGraphFactory } from './LanguageGraphFactory'; +import { KnownGraphs, KnownGraphsDataTypes } from '../graph/KnownGraphs'; /** * Abstract implementation of Language. This is the root that languages should * extend. */ export abstract class AbstractLanguage implements Language { - private cachedMatchers: Map> = new Map(); + private cachedGraphs: Map> = new Map(); /** * The identifier of the language. @@ -44,45 +47,30 @@ export abstract class AbstractLanguage implements Language { * * @param matcher */ - public abstract repeating(matcher: Matcher): GraphBuilder; + public abstract repeating(graph: Graph): GraphBuilder; /** * Create and return a matcher for the given factory. * * @param factory */ - public matcher(factory: ValueMatcherFactory): Matcher { - const result = this.cachedMatchers.get(factory.id); + public graph(factory: LanguageGraphFactory): Graph { + const result = this.cachedGraphs.get(factory.id); if(result) { return result; } const created = factory.create(this); - this.cachedMatchers.set(factory.id, created); + this.cachedGraphs.set(factory.id, created); return created; } - /** - * Find a matcher based on its identifier. The matcher must be available - * for the language. - * - * @param id - */ - public findMatcher(id: string): Matcher | null { - return this.cachedMatchers.get(id) || null; - } - - /** - * Get a matcher, throwing an error if it is not available. - * - * @param id - */ - public getMatcher(id: string): Matcher { - const result = this.cachedMatchers.get(id); - if(! result) { - throw new Error('No matcher with id `' + id + '` available'); + public findGraph(id: K): Graph { + const cached = this.cachedGraphs.get(id); + if(! cached) { + throw new Error('Graph with id `' + id + '` not available for language'); } - return result; + return cached as any; } } diff --git a/src/language/Language.ts b/src/language/Language.ts index cdc1b76..a32c24d 100644 --- a/src/language/Language.ts +++ b/src/language/Language.ts @@ -1,8 +1,11 @@ import { Token, Tokens } from './tokens'; -import { Matcher } from '../graph/matching'; -import { ValueMatcherFactory } from './ValueMatcherFactory'; + +import { Graph } from '../graph/Graph'; import { GraphBuilder } from '../graph/GraphBuilder'; +import { LanguageGraphFactory } from './LanguageGraphFactory'; +import { KnownGraphs, KnownGraphsDataTypes } from '../graph/KnownGraphs'; + /** * Language usable with Ecolect. */ @@ -36,31 +39,23 @@ export interface Language { comparePartialTokens(a: Token, b: Token): number; /** - * Create and return a matcher for the given factory. + * Get or create a graph. * * @param factory */ - matcher(factory: ValueMatcherFactory): Matcher; + graph(factory: LanguageGraphFactory): Graph; /** - * Find a matcher based on its identifier. The matcher must be available - * for the language. + * Create a repeating statement for the given graph. * - * @param id + * @param matcher */ - findMatcher(id: string): Matcher | null; + repeating(graph: Graph): GraphBuilder; /** - * Get a matcher, throwing an error if it is not available. + * Get a known graph from the language. * * @param id */ - getMatcher(id: string): Matcher; - - /** - * Create a repeating statement for the given matcher. - * - * @param matcher - */ - repeating(matcher: Matcher): GraphBuilder; + findGraph(id: K): Graph; } diff --git a/src/language/LanguageGraphFactory.ts b/src/language/LanguageGraphFactory.ts new file mode 100644 index 0000000..ed98392 --- /dev/null +++ b/src/language/LanguageGraphFactory.ts @@ -0,0 +1,20 @@ +import { Language } from './Language'; +import { Graph } from '../graph/Graph'; + +/** + * Factory that creates a graph tied to a certain language. + */ +export interface LanguageGraphFactory { + /** + * Unique identifier of the graph. + */ + readonly id: string; + + /** + * Create an instance of this graph. + * + * @param language + * language to construct graph for + */ + create(language: Language): Graph; +} diff --git a/src/language/en/boolean.ts b/src/language/en/booleanGraph.ts similarity index 55% rename from src/language/en/boolean.ts rename to src/language/en/booleanGraph.ts index 646d3cf..cdbfe33 100644 --- a/src/language/en/boolean.ts +++ b/src/language/en/booleanGraph.ts @@ -1,11 +1,10 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { Language } from '../Language'; -export const booleanMatcher: ValueMatcherFactory = { +export const booleanGraph: LanguageGraphFactory = { id: 'boolean', - create(language: Language) { + create(language) { return new GraphBuilder(language) .name('boolean') @@ -17,8 +16,6 @@ export const booleanMatcher: ValueMatcherFactory = { .add('off', false) .add('no', false) - .onlyBest() - - .toMatcher(); + .build(); } }; diff --git a/src/language/en/date-duration.ts b/src/language/en/dateDurationGraph.ts similarity index 77% rename from src/language/en/date-duration.ts rename to src/language/en/dateDurationGraph.ts index 643ae8d..748d3d2 100644 --- a/src/language/en/date-duration.ts +++ b/src/language/en/dateDurationGraph.ts @@ -1,18 +1,16 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { Language } from '../Language'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { integerMatcher } from './integer'; +import { integerGraph } from './integerGraph'; import { DateTimeData } from '../../time/DateTimeData'; import { combine } from '../../time/matching'; -import { map, Duration } from '../../time/durations'; -export const dateDurationMatcher: ValueMatcherFactory = { +export const dateDurationGraph: LanguageGraphFactory = { id: 'date-duration', - create(language: Language) { - const integer = language.matcher(integerMatcher); + create(language) { + const integer = language.graph(integerGraph); return new GraphBuilder(language) .name('date-duration') @@ -37,8 +35,6 @@ export const dateDurationMatcher: ValueMatcherFactory = { .add([ GraphBuilder.result(), GraphBuilder.result() ], v => combine(v[0], v[1])) .add([ GraphBuilder.result(), 'and', GraphBuilder.result() ], v => combine(v[0], v[1])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/date.ts b/src/language/en/dateGraph.ts similarity index 83% rename from src/language/en/date.ts rename to src/language/en/dateGraph.ts index 0b83390..772a8ef 100644 --- a/src/language/en/date.ts +++ b/src/language/en/dateGraph.ts @@ -1,6 +1,5 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { DateValue } from '../../time/date-value'; import { combine, @@ -17,31 +16,31 @@ import { IntervalEdge } from '../../time/IntervalEdge'; import { DateTimeData } from '../../time/DateTimeData'; import { thisWeek } from '../../time/weeks'; import { thisQuarter } from '../../time/quarters'; -import { map, today, yesterday, tomorrow, dayAfterTomorrow, nextDayOfWeek, withDay, withYear } from '../../time/dates'; +import { today, yesterday, tomorrow, dayAfterTomorrow, nextDayOfWeek, withDay, withYear } from '../../time/dates'; -import { OrdinalValue } from '../../numbers/OrdinalValue'; +import { OrdinalData } from '../../numbers/OrdinalData'; -import { ordinalMatcher } from './ordinal'; -import { dayOfWeekMatcher } from './day-of-week'; -import { yearMatcher } from './year'; -import { quarterMatcher } from './quarter'; -import { weekMatcher } from './week'; -import { monthMatcher } from './month'; -import { dateDurationMatcher } from './date-duration'; +import { ordinalGraph } from './ordinalGraph'; +import { dayOfWeekGraph } from './dayOfWeekGraph'; +import { yearGraph } from './yearGraph'; +import { quarterGraph } from './quarterGraph'; +import { weekGraph } from './weekGraph'; +import { monthGraph } from './monthGraph'; +import { dateDurationGraph } from './dateDurationGraph'; -export const dateMatcher: ValueMatcherFactory = { +export const dateGraph: LanguageGraphFactory = { id: 'date', create(language) { - const ordinal = language.matcher(ordinalMatcher); - const dayOfWeek = language.matcher(dayOfWeekMatcher); - const year = language.matcher(yearMatcher); - const quarter = language.matcher(quarterMatcher); - const week = language.matcher(weekMatcher); - const month = language.matcher(monthMatcher); - const dateDuration = language.matcher(dateDurationMatcher); + const ordinal = language.graph(ordinalGraph); + const dayOfWeek = language.graph(dayOfWeekGraph); + const year = language.graph(yearGraph); + const quarter = language.graph(quarterGraph); + const week = language.graph(weekGraph); + const month = language.graph(monthGraph); + const dateDuration = language.graph(dateDurationGraph); - const day = GraphBuilder.result(ordinal, (v: OrdinalValue) => v.value >= 0 && v.value < 31); + const day = GraphBuilder.result(ordinal, (v: OrdinalData) => v.value >= 0 && v.value < 31); return new GraphBuilder(language) .name('date') @@ -223,8 +222,6 @@ export const dateMatcher: ValueMatcherFactory = { .add([ 'beginning of', GraphBuilder.result() ], v => startOf(v[0])) .add([ 'end of', GraphBuilder.result() ], v => endOf(v[0])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/date-interval.ts b/src/language/en/dateIntervalGraph.ts similarity index 77% rename from src/language/en/date-interval.ts rename to src/language/en/dateIntervalGraph.ts index 935dc2f..afae787 100644 --- a/src/language/en/date-interval.ts +++ b/src/language/en/dateIntervalGraph.ts @@ -1,17 +1,18 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { between, until, before, from, after } from '../../time/matching'; -import { hasSingle, map, inThePast, inTheFuture, anyTime } from '../../time/date-intervals'; -import { IntervalValue } from '../../time/interval-value'; -import { dateMatcher } from './date'; import { IntervalData } from '../../time/IntervalData'; -export const dateIntervalMatcher: ValueMatcherFactory = { +import { between, until, before, from, after } from '../../time/matching'; +import { hasSingle, inThePast, inTheFuture, anyTime } from '../../time/date-intervals'; + +import { dateGraph } from './dateGraph'; + +export const dateIntervalGraph: LanguageGraphFactory = { id: 'date-interval', create(language) { - const date = language.matcher(dateMatcher); + const date = language.graph(dateGraph); return new GraphBuilder(language) .name('date-interval') @@ -47,8 +48,6 @@ export const dateIntervalMatcher: ValueMatcherFactory = { .add([ 'from', GraphBuilder.result() ], v => v[0]) .add([ 'between', GraphBuilder.result() ], v => v[0]) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/date-time-duration.ts b/src/language/en/dateTimeDurationGraph.ts similarity index 54% rename from src/language/en/date-time-duration.ts rename to src/language/en/dateTimeDurationGraph.ts index df18751..50533d8 100644 --- a/src/language/en/date-time-duration.ts +++ b/src/language/en/dateTimeDurationGraph.ts @@ -1,18 +1,18 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; import { combine } from '../../time/matching'; -import { map, Duration } from '../../time/durations'; -import { timeDurationMatcher } from './time-duration'; -import { dateDurationMatcher } from './date-duration'; import { DateTimeData } from '../../time/DateTimeData'; -export const dateTimeDurationMatcher: ValueMatcherFactory = { +import { timeDurationGraph } from './timeDurationGraph'; +import { dateDurationGraph } from './dateDurationGraph'; + +export const dateTimeDurationGraph: LanguageGraphFactory = { id: 'date-time-duration', create(language) { - const timeDuration = language.matcher(timeDurationMatcher); - const dateDuration = language.matcher(dateDurationMatcher); + const timeDuration = language.graph(timeDurationGraph); + const dateDuration = language.graph(dateDurationGraph); return new GraphBuilder(language) .name('date-time-duration') @@ -25,8 +25,6 @@ export const dateTimeDurationMatcher: ValueMatcherFactory = { .add([ GraphBuilder.result(), GraphBuilder.result() ], v => combine(v[0], v[1])) .add([ GraphBuilder.result(), 'and', GraphBuilder.result() ], v => combine(v[0], v[1])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/date-time.ts b/src/language/en/dateTimeGraph.ts similarity index 59% rename from src/language/en/date-time.ts rename to src/language/en/dateTimeGraph.ts index 8e9776b..d530e6b 100644 --- a/src/language/en/date-time.ts +++ b/src/language/en/dateTimeGraph.ts @@ -1,19 +1,19 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { DateValue } from '../../time/date-value'; + import { DateTimeData } from '../../time/DateTimeData'; import { combine, isRelative } from '../../time/matching'; -import { map } from '../../time/date-times'; -import { dateMatcher } from './date'; -import { timeMatcher } from './time'; -export const dateTimeMatcher: ValueMatcherFactory = { +import { dateGraph } from './dateGraph'; +import { timeGraph } from './timeGraph'; + +export const dateTimeGraph: LanguageGraphFactory = { id: 'date-time', create(language) { - const time = language.matcher(timeMatcher); - const date = language.matcher(dateMatcher); + const time = language.graph(timeGraph); + const date = language.graph(dateGraph); return new GraphBuilder(language) .name('date-time') @@ -31,8 +31,6 @@ export const dateTimeMatcher: ValueMatcherFactory = { .add(GraphBuilder.result(date, isRelative), (v, e) => v[0]) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/day-of-week.ts b/src/language/en/dayOfWeekGraph.ts similarity index 71% rename from src/language/en/day-of-week.ts rename to src/language/en/dayOfWeekGraph.ts index 6a20749..f6bfaad 100644 --- a/src/language/en/day-of-week.ts +++ b/src/language/en/dayOfWeekGraph.ts @@ -1,10 +1,9 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { Language } from '../Language'; -import { GraphBuilder } from '../../graph/GraphBuilder'; -import { Weekday } from '../../time/Weekday'; +import { GraphBuilder } from '../../graph/GraphBuilder'; -export const dayOfWeekMatcher: ValueMatcherFactory = { +export const dayOfWeekGraph: LanguageGraphFactory = { id: 'day-of-week', create(language: Language) { @@ -41,8 +40,6 @@ export const dayOfWeekMatcher: ValueMatcherFactory = { .add([ 'on', GraphBuilder.result() ], v => v[0]) - .mapResults(m => m as Weekday) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/index.ts b/src/language/en/index.ts index 57e8212..dc051ad 100644 --- a/src/language/en/index.ts +++ b/src/language/en/index.ts @@ -4,29 +4,30 @@ import stemmer from 'talisman/stemmers/porter'; import { similarity } from 'talisman/metrics/distance/jaro-winkler'; import treebank from 'talisman/tokenizers/words/treebank'; -import { Matcher } from '../../graph/matching'; import { GraphBuilder } from '../../graph/GraphBuilder'; +import { Graph } from '../../graph/Graph'; import { AbstractLanguage } from '../AbstractLanguage'; import { Token } from '../tokens'; -import { integerMatcher } from './integer'; -import { numberMatcher } from './number'; -import { ordinalMatcher } from './ordinal'; -import { booleanMatcher } from './boolean'; -import { dayOfWeekMatcher } from './day-of-week'; -import { yearMatcher } from './year'; -import { quarterMatcher } from './quarter'; -import { monthMatcher } from './month'; -import { weekMatcher } from './week'; -import { dateDurationMatcher } from './date-duration'; -import { dateMatcher } from './date'; -import { timeDurationMatcher } from './time-duration'; -import { timeMatcher } from './time'; -import { dateTimeDurationMatcher } from './date-time-duration'; -import { dateTimeMatcher } from './date-time'; -import { dateIntervalMatcher } from './date-interval'; import { createRepeating } from './repeating'; +import { integerGraph } from './integerGraph'; +import { numberGraph } from './numberGraph'; +import { ordinalGraph } from './ordinalGraph'; +import { booleanGraph } from './booleanGraph'; +import { dayOfWeekGraph } from './dayOfWeekGraph'; +import { yearGraph } from './yearGraph'; +import { quarterGraph } from './quarterGraph'; +import { monthGraph } from './monthGraph'; +import { weekGraph } from './weekGraph'; +import { dateDurationGraph } from './dateDurationGraph'; +import { dateGraph } from './dateGraph'; +import { timeDurationGraph } from './timeDurationGraph'; +import { timeGraph } from './timeGraph'; +import { dateTimeDurationGraph } from './dateTimeDurationGraph'; +import { dateTimeGraph } from './dateTimeGraph'; +import { dateIntervalGraph } from './dateIntervalGraph'; + function normalize(word: string, next?: string) { word = word.toLowerCase(); @@ -111,33 +112,29 @@ export class EnglishLanguage extends AbstractLanguage { return 0; } - public repeating(matcher: Matcher): GraphBuilder { - return createRepeating(this)(matcher); + public repeating(graph: Graph): GraphBuilder { + return createRepeating(this)(graph); } } export const en = new EnglishLanguage(); -en.matcher(integerMatcher); -en.matcher(numberMatcher); -en.matcher(ordinalMatcher); -en.matcher(booleanMatcher); - -en.matcher(dayOfWeekMatcher); -en.matcher(yearMatcher); -en.matcher(quarterMatcher); -en.matcher(monthMatcher); -en.matcher(weekMatcher); -en.matcher(dateDurationMatcher); -en.matcher(dateMatcher); - -en.matcher(timeDurationMatcher); -en.matcher(timeMatcher); - -en.matcher(dateTimeDurationMatcher); -en.matcher(dateTimeMatcher); - -en.matcher(dateIntervalMatcher); - -//language.temperature = temperature(language); - -//language.repeating = repeating(language); +en.graph(integerGraph); +en.graph(numberGraph); +en.graph(ordinalGraph); +en.graph(booleanGraph); + +en.graph(dayOfWeekGraph); +en.graph(yearGraph); +en.graph(quarterGraph); +en.graph(monthGraph); +en.graph(weekGraph); +en.graph(dateDurationGraph); +en.graph(dateGraph); + +en.graph(timeDurationGraph); +en.graph(timeGraph); + +en.graph(dateTimeDurationGraph); +en.graph(dateTimeGraph); + +en.graph(dateIntervalGraph); diff --git a/src/language/en/integer.ts b/src/language/en/integerGraph.ts similarity index 82% rename from src/language/en/integer.ts rename to src/language/en/integerGraph.ts index c113540..e1c7589 100644 --- a/src/language/en/integer.ts +++ b/src/language/en/integerGraph.ts @@ -1,3 +1,6 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; +import { Language } from '../Language'; + import { GraphBuilder } from '../../graph/GraphBuilder'; import { @@ -5,23 +8,18 @@ import { digitNumber, combine, - map, isDigits, isDigitsCompatible, isLiteral, } from '../../numbers/numbers'; -import { Language } from '../Language'; -import { Matcher } from '../../graph/matching'; import { NumberData } from '../../numbers/NumberData'; -import { NumberValue } from '../../numbers/NumberValue'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -export const integerMatcher: ValueMatcherFactory = { +export const integerGraph: LanguageGraphFactory = { id: 'integer', - create(language: Language): Matcher { + create(language: Language) { return new GraphBuilder(language) .name('integer') @@ -85,8 +83,6 @@ export const integerMatcher: ValueMatcherFactory = { // Thousands separator .add([ GraphBuilder.result(isDigits), ',', GraphBuilder.result(isDigits) ], v => combine(v[0], v[1])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/month.ts b/src/language/en/monthGraph.ts similarity index 76% rename from src/language/en/month.ts rename to src/language/en/monthGraph.ts index b9a36f7..31d0f5d 100644 --- a/src/language/en/month.ts +++ b/src/language/en/monthGraph.ts @@ -1,23 +1,25 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; +import { Language } from '../Language'; + import { GraphBuilder } from '../../graph/GraphBuilder'; -import { ordinalMatcher } from './ordinal'; -import { integerMatcher } from './integer'; +import { ordinalGraph } from './ordinalGraph'; +import { integerGraph } from './integerGraph'; -import { reverse } from '../../time/matching'; -import { map, thisMonth, nextMonth, previousMonth } from '../../time/months'; -import { Language } from '../Language'; import { DateTimeData } from '../../time/DateTimeData'; -import { isSpecific } from '../../numbers/ordinals'; import { OrdinalData } from '../../numbers/OrdinalData'; -import { DateValue } from '../../time/date-value'; -export const monthMatcher: ValueMatcherFactory = { +import { reverse } from '../../time/matching'; +import { thisMonth, nextMonth, previousMonth } from '../../time/months'; + +import { isSpecific } from '../../numbers/ordinals'; + +export const monthGraph: LanguageGraphFactory = { id: 'month', create(language: Language) { - const integer = language.matcher(integerMatcher); - const ordinal = language.matcher(ordinalMatcher); + const integer = language.graph(integerGraph); + const ordinal = language.graph(ordinalGraph); const ordinalMonth = GraphBuilder.result(ordinal, v => isSpecific(v) && v.value >= 1 && v.value <= 12); const relative = new GraphBuilder(language) @@ -25,7 +27,7 @@ export const monthMatcher: ValueMatcherFactory = { .add([ integer, 'months' ], v => ({ relativeMonths: v[0].value })) - .toMatcher(); + .build(); return new GraphBuilder(language) .name('month') @@ -89,8 +91,6 @@ export const monthMatcher: ValueMatcherFactory = { .add([ 'in', GraphBuilder.result() ], v => v[0]) .add([ relative, 'ago' ], v => reverse(v[0])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/number.ts b/src/language/en/numberGraph.ts similarity index 67% rename from src/language/en/number.ts rename to src/language/en/numberGraph.ts index 9fa8be4..787b0f4 100644 --- a/src/language/en/number.ts +++ b/src/language/en/numberGraph.ts @@ -1,22 +1,22 @@ import { GraphBuilder } from '../../graph/GraphBuilder'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; -import { float, combine, negative, map, digitNumber, isNegative } from '../../numbers/numbers'; -import { NumberData } from '../../numbers/NumberData'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { NumberValue } from '../../numbers/NumberValue'; import { Language } from '../Language'; -import { Matcher } from '../../graph/matching'; -import { integerMatcher } from './integer'; + +import { float, combine, negative, digitNumber, isNegative } from '../../numbers/numbers'; +import { NumberData } from '../../numbers/NumberData'; + +import { integerGraph } from './integerGraph'; function isNumber(o: NumberData) { return typeof o.value !== 'undefined'; } -export const numberMatcher: ValueMatcherFactory = { +export const numberGraph: LanguageGraphFactory = { id: 'number', - create(language: Language): Matcher { - const integer = language.matcher(integerMatcher); + create(language: Language) { + const integer = language.graph(integerGraph); return new GraphBuilder(language) .name('number') @@ -40,8 +40,6 @@ export const numberMatcher: ValueMatcherFactory = { .add([ 'minus', GraphBuilder.result(isNumber) ], v => negative(v[0])) .add([ 'negative', GraphBuilder.result(isNumber) ], v => negative(v[0])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/ordinal.ts b/src/language/en/ordinalGraph.ts similarity index 56% rename from src/language/en/ordinal.ts rename to src/language/en/ordinalGraph.ts index b720520..696ac7d 100644 --- a/src/language/en/ordinal.ts +++ b/src/language/en/ordinalGraph.ts @@ -1,26 +1,26 @@ import { Language } from '../Language'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; + +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { Matcher } from '../../graph/matching'; +import { Graph } from '../../graph/Graph'; -import { integerMatcher } from './integer'; +import { integerGraph } from './integerGraph'; -import { OrdinalValue } from '../../numbers/OrdinalValue'; import { OrdinalData } from '../../numbers/OrdinalData'; -import { map, specificOrdinal, ambigiousOrdinal } from '../../numbers/ordinals'; +import { specificOrdinal, ambiguousOrdinal } from '../../numbers/ordinals'; const specific = (v: any) => specificOrdinal(v[0].value); -export const ordinalMatcher: ValueMatcherFactory = { +export const ordinalGraph: LanguageGraphFactory = { id: 'ordinal', - create(language: Language): Matcher { - const integer = language.matcher(integerMatcher); + create(language: Language): Graph { + const integer = language.graph(integerGraph); return new GraphBuilder(language) .name('ordinal') - .add(integer, v => ambigiousOrdinal(v[0].value)) + .add(integer, v => ambiguousOrdinal(v[0].value)) .add([ integer, 'st' ], specific) .add([ integer, 'nd' ], specific) .add([ integer, 'rd' ], specific) @@ -44,8 +44,6 @@ export const ordinalMatcher: ValueMatcherFactory = { .add([ 'the', GraphBuilder.result() ], v => v[0]) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/quarter.ts b/src/language/en/quarterGraph.ts similarity index 62% rename from src/language/en/quarter.ts rename to src/language/en/quarterGraph.ts index d8b4fe8..860063b 100644 --- a/src/language/en/quarter.ts +++ b/src/language/en/quarterGraph.ts @@ -1,16 +1,17 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; + import { GraphBuilder } from '../../graph/GraphBuilder'; -import { map, thisQuarter, nextQuarter, previousQuarter } from '../../time/quarters'; -import { ordinalMatcher } from './ordinal'; -import { IntervalValue } from '../../time/interval-value'; +import { ordinalGraph } from './ordinalGraph'; + +import { thisQuarter, nextQuarter, previousQuarter } from '../../time/quarters'; import { DateTimeData } from '../../time/DateTimeData'; -export const quarterMatcher: ValueMatcherFactory = { +export const quarterGraph: LanguageGraphFactory = { id: 'quarter', create(language) { - const ordinal = language.matcher(ordinalMatcher); + const ordinal = language.graph(ordinalGraph); return new GraphBuilder(language) .name('quarter') @@ -28,8 +29,6 @@ export const quarterMatcher: ValueMatcherFactory = { .add([ ordinal, 'quarter' ], v => ({ quarter: v[0].value })) .add([ ordinal, 'q' ], v => ({ quarter: v[0].value })) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/repeating.ts b/src/language/en/repeating.ts index 8d60d12..31b2483 100644 --- a/src/language/en/repeating.ts +++ b/src/language/en/repeating.ts @@ -1,25 +1,19 @@ -import { GraphBuilder } from '../../graph/GraphBuilder'; -import { Matcher } from '../../graph/matching'; import { Language } from '../Language'; -export function createRepeating(language: Language) { - return function(graph: Matcher) { - const builder = new GraphBuilder(language) - .name('repeating[' + graph.options.name + ']') +import { GraphBuilder } from '../../graph/GraphBuilder'; +import { Graph } from '../../graph/Graph'; - .skipPunctuation(); +export function createRepeating(language: Language) { + return function(graph: Graph) { + const builder: GraphBuilder = new GraphBuilder(language) + .skipPunctuation() + .name('repeating[' + graph.options.name + ']'); // If the value supports partial matching so does this repeating value if(graph.options.supportsPartial) { builder.allowPartial(); } - if(graph.options.mapper) { - // If the graph has requested that matches are mapped - map each match - const mapper = graph.options.mapper; - builder.mapResults((values, encounter) => values.map(v => mapper(v, encounter))); - } - // Add the value and how it can be repeated return builder .add(graph, v => [ v[0] ]) diff --git a/src/language/en/time-duration.ts b/src/language/en/timeDurationGraph.ts similarity index 79% rename from src/language/en/time-duration.ts rename to src/language/en/timeDurationGraph.ts index 1be7363..4fa43ba 100644 --- a/src/language/en/time-duration.ts +++ b/src/language/en/timeDurationGraph.ts @@ -1,16 +1,16 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; -import { DateTimeData } from '../../time/DateTimeData'; -import { integerMatcher } from './integer'; +import { integerGraph } from './integerGraph'; + +import { DateTimeData } from '../../time/DateTimeData'; import { combine } from '../../time/matching'; -import { map, Duration } from '../../time/durations'; -export const timeDurationMatcher: ValueMatcherFactory = { +export const timeDurationGraph: LanguageGraphFactory = { id: 'time-duration', create(language) { - const integer = language.matcher(integerMatcher); + const integer = language.graph(integerGraph); return new GraphBuilder(language) .name('time-duration') @@ -32,8 +32,6 @@ export const timeDurationMatcher: ValueMatcherFactory = { .add([ GraphBuilder.result(), GraphBuilder.result() ], v => combine(v[0], v[1])) .add([ GraphBuilder.result(), 'and', GraphBuilder.result() ], v => combine(v[0], v[1])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/time.ts b/src/language/en/timeGraph.ts similarity index 88% rename from src/language/en/time.ts rename to src/language/en/timeGraph.ts index 6c7cfa3..2121de8 100644 --- a/src/language/en/time.ts +++ b/src/language/en/timeGraph.ts @@ -1,28 +1,27 @@ +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; + import { DateTimeData } from '../../time/DateTimeData'; -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { integerMatcher } from './integer'; -import { timeDurationMatcher } from './time-duration'; +import { integerGraph } from './integerGraph'; +import { timeDurationGraph } from './timeDurationGraph'; import { combine, reverse, hasHour, isHour } from '../../time/matching'; -import { map, time12h, time24h, toAM, toPM } from '../../time/times'; -import { DateValue } from '../../time/date-value'; +import { time12h, time24h, toAM, toPM } from '../../time/times'; import { Precision } from '../../time/Precision'; - function adjustMinutes(time: DateTimeData, minutes: number) { return combine(time, { relativeMinutes: minutes }); } -export const timeMatcher: ValueMatcherFactory = { +export const timeGraph: LanguageGraphFactory = { id: 'time', create(language) { - const integer = language.matcher(integerMatcher); - const timeDuration = language.matcher(timeDurationMatcher); + const integer = language.graph(integerGraph); + const timeDuration = language.graph(timeDurationGraph); const relativeMinutes = new GraphBuilder(language) .name('relativeMinutes') @@ -33,7 +32,7 @@ export const timeMatcher: ValueMatcherFactory = { .add(integer, v => v[0].value) .add([ integer, 'minutes' ], v => v[0].value) - .toMatcher(); + .build(); return new GraphBuilder(language) .name('time') @@ -106,8 +105,6 @@ export const timeMatcher: ValueMatcherFactory = { .add([ timeDuration, 'ago' ], v => reverse(v[0])) .add([ 'at', GraphBuilder.result() ], v => v[0]) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/week.ts b/src/language/en/weekGraph.ts similarity index 61% rename from src/language/en/week.ts rename to src/language/en/weekGraph.ts index f8731e7..28277b7 100644 --- a/src/language/en/week.ts +++ b/src/language/en/weekGraph.ts @@ -1,17 +1,17 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; -import { IntervalValue } from '../../time/interval-value'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; + import { DateTimeData } from '../../time/DateTimeData'; -import { ordinalMatcher } from './ordinal'; -import { map, thisWeek, nextWeek, previousWeek } from '../../time/weeks'; +import { ordinalGraph } from './ordinalGraph'; +import { thisWeek, nextWeek, previousWeek } from '../../time/weeks'; import { isSpecific } from '../../numbers/ordinals'; -export const weekMatcher: ValueMatcherFactory = { +export const weekGraph: LanguageGraphFactory = { id: 'week', create(language) { - const ordinal = language.matcher(ordinalMatcher); + const ordinal = language.graph(ordinalGraph); return new GraphBuilder(language) .name('week') @@ -27,8 +27,6 @@ export const weekMatcher: ValueMatcherFactory = { .add([ 'week', ordinal ], v => ({ week: v[0].value })) .add([ GraphBuilder.result(ordinal, isSpecific), 'week' ], v => ({ week: v[0].value })) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/language/en/year.ts b/src/language/en/yearGraph.ts similarity index 66% rename from src/language/en/year.ts rename to src/language/en/yearGraph.ts index ea72ab8..5efe139 100644 --- a/src/language/en/year.ts +++ b/src/language/en/yearGraph.ts @@ -1,25 +1,25 @@ -import { ValueMatcherFactory } from '../ValueMatcherFactory'; +import { LanguageGraphFactory } from '../LanguageGraphFactory'; import { GraphBuilder } from '../../graph/GraphBuilder'; + import { DateTimeData } from '../../time/DateTimeData'; -import { integerMatcher } from './integer'; +import { integerGraph } from './integerGraph'; import { reverse } from '../../time/matching'; -import { map, thisYear, nextYear, previousYear } from '../../time/years'; -import { DateValue } from '../../time/date-value'; +import { thisYear, nextYear, previousYear } from '../../time/years'; -export const yearMatcher: ValueMatcherFactory = { +export const yearGraph: LanguageGraphFactory = { id: 'year', create(language) { - const integer = language.matcher(integerMatcher); + const integer = language.graph(integerGraph); const relative = new GraphBuilder(language) .name('relativeYears') .add([ integer, 'years' ], v => ({ relativeYears: v[0].value })) - .toMatcher(); + .build(); return new GraphBuilder(language) .name('year') @@ -36,8 +36,6 @@ export const yearMatcher: ValueMatcherFactory = { .add([ 'of', GraphBuilder.result() ], v => v[0]) .add([ relative, 'ago' ], v => reverse(v[0])) - .mapResults(map) - .onlyBest() - .toMatcher(); + .build(); } }; diff --git a/src/numbers/numbers.ts b/src/numbers/numbers.ts index e216e74..19b0364 100644 --- a/src/numbers/numbers.ts +++ b/src/numbers/numbers.ts @@ -96,6 +96,6 @@ export function float(a: NumberData, b: NumberData): NumberData { }; } -export function map(data: NumberData) { +export function mapNumber(data: NumberData) { return new NumberValue(data); } diff --git a/src/numbers/ordinals.ts b/src/numbers/ordinals.ts index 97822bb..f50df5e 100644 --- a/src/numbers/ordinals.ts +++ b/src/numbers/ordinals.ts @@ -1,7 +1,6 @@ import { OrdinalData } from './OrdinalData'; import { OrdinalValue } from './OrdinalValue'; import { OrdinalPrecision } from './OrdinalPrecision'; -import { Precision } from '../time/Precision'; export function specificOrdinal(value: number): OrdinalData { return { @@ -10,7 +9,7 @@ export function specificOrdinal(value: number): OrdinalData { }; } -export function ambigiousOrdinal(value: number): OrdinalData { +export function ambiguousOrdinal(value: number): OrdinalData { return { value: value, precision: OrdinalPrecision.Ambiguous @@ -22,7 +21,7 @@ export function ambigiousOrdinal(value: number): OrdinalData { * * @param r */ -export function map(r: OrdinalData): OrdinalValue | null { +export function mapOrdinal(r: OrdinalData): OrdinalValue | null { if(typeof r.value === 'undefined') return null; return { diff --git a/src/resolver/ResolvedIntent.ts b/src/resolver/ResolvedIntent.ts index 27e4468..5fea892 100644 --- a/src/resolver/ResolvedIntent.ts +++ b/src/resolver/ResolvedIntent.ts @@ -3,15 +3,15 @@ import { Encounter } from '../graph/matching'; import { ExpressionPart } from './expression/ExpressionPart'; import { clone } from '../utils/cloning'; -export class ResolvedIntent { +export class ResolvedIntent { public intent: string; - public values: Map; + public values: Values; public score: number; public expression: ExpressionPart[]; constructor(intent: string) { this.intent = intent; - this.values = new Map(); + this.values = {} as Values; this.score = 0; this.expression = []; @@ -31,7 +31,7 @@ export class ResolvedIntent { public clone() { const r = new ResolvedIntent(this.intent); - r.values = new Map(this.values); + r.values = clone(this.values); r.expression = clone(this.expression); return r; } diff --git a/src/resolver/ResolvedIntents.ts b/src/resolver/ResolvedIntents.ts index ac88d06..fb8059f 100644 --- a/src/resolver/ResolvedIntents.ts +++ b/src/resolver/ResolvedIntents.ts @@ -3,14 +3,14 @@ import { ResolvedIntent } from './ResolvedIntent'; /** * */ -export interface ResolvedIntents { +export interface ResolvedIntents { /** * The best intent that matches. */ - readonly best: ResolvedIntent | null; + readonly best: ResolvedIntent | null; /** * All of the matches. */ - readonly matches: ReadonlyArray; + readonly matches: ReadonlyArray>; } diff --git a/src/resolver/ResolverBuilder.ts b/src/resolver/ResolverBuilder.ts index d5b196f..190f09f 100644 --- a/src/resolver/ResolverBuilder.ts +++ b/src/resolver/ResolverBuilder.ts @@ -2,9 +2,9 @@ import { Matcher } from '../graph/matching/Matcher'; import { ResolverParser } from './ResolverParser'; import { ResolvedIntent } from './ResolvedIntent'; import { Language } from '../language/Language'; -import { LanguageSpecificValue, NodeConvertable } from '../values/base'; +import { Value } from '../values/base'; import { Collectable } from '../graph/CollectorNode'; -import { Match, DefaultMatcher } from '../graph/matching'; +import { Match, GraphMatcher } from '../graph/matching'; import { ResolvedIntents } from './ResolvedIntents'; import { GraphBuildable } from '../graph/GraphBuilder'; @@ -12,12 +12,12 @@ import { GraphBuildable } from '../graph/GraphBuilder'; * This is a basic naive builder for instances of Resolver on top of the * parser. */ -export class ResolverBuilder { +export class ResolverBuilder { private language: Language; private id: string; - private parser: ResolverParser; - private resultHandler: Collectable; + private parser: ResolverParser>; + private resultHandler: Collectable>; constructor(language: Language, id?: string) { this.language = language; @@ -26,14 +26,14 @@ export class ResolverBuilder { this.id = id || 'unknown'; this.resultHandler = (values, encounter) => { - const result = new ResolvedIntent(this.id); + const result = new ResolvedIntent(this.id); // Transfer any values that have been pushed by other parsers const data = encounter.data(); for(let i=0; i | NodeConvertable) { + public value(id: I, type: Value): ResolverBuilder { this.parser.value(id, type); - return this; + return this as any; } - public add(...args: GraphBuildable[]) { - if(args[0] instanceof DefaultMatcher) { + public add(...args: GraphBuildable[]): this { + if(typeof args[0] === 'object' && 'nodes' in args[0]) { /** * If adding another parser for resolving intent just copy all * of its nodes as they should work just fine with our own parser. @@ -68,21 +68,27 @@ export class ResolverBuilder { return this; } - public build(): Matcher { - return this.parser.reducer(({ results }) => { - // Reduce the results down to our intended structure - const actualResults = results.toArray() - .map(finalizeIntent); - - return { - best: actualResults[0] || null, - matches: actualResults - }; - }).toMatcher(); + public build() { + return this.parser.build(); + } + + public toMatcher(): Matcher> { + return new GraphMatcher(this.language, this.parser.build(), { + reducer: ({ results }) => { + // Reduce the results down to our intended structure + const actualResults = results.toArray() + .map(finalizeIntent); + + return { + best: actualResults[0] || null, + matches: actualResults + }; + } + }); } } -function finalizeIntent(result: Match): ResolvedIntent { +function finalizeIntent(result: Match>): ResolvedIntent { result.data.score = result.score; result.data.refreshExpression(); return result.data; diff --git a/src/resolver/ResolverParser.ts b/src/resolver/ResolverParser.ts index abd1d4f..70495f7 100644 --- a/src/resolver/ResolverParser.ts +++ b/src/resolver/ResolverParser.ts @@ -1,5 +1,5 @@ import { GraphBuilder } from '../graph/GraphBuilder'; -import { MatcherOptions, EncounterOptions, DefaultMatcher } from '../graph/matching'; +import { EncounterOptions } from '../graph/matching'; import { TokenNode } from '../graph/TokenNode'; import { ValueNode } from './ValueNode'; @@ -17,8 +17,8 @@ const VALUE = /{([a-zA-Z0-9]+)}/g; * * TODO: Extended grammar for optional tokens and OR between tokens? */ -export class ResolverParser extends GraphBuilder { - private values: Map; +export class ResolverParser extends GraphBuilder { + private values: Map>; constructor(language: Language) { super(language); @@ -29,7 +29,7 @@ export class ResolverParser extends GraphBuilder { this.allowPartial(); } - public value(id: string, type: Value): this { + public value(id: string, type: Value): this { let factory = type; if(factory instanceof LanguageSpecificValue) { factory = factory.create(this.language); @@ -102,19 +102,11 @@ export class ResolverParser extends GraphBuilder { return firstNode; } - protected createMatcher(language: Language, nodes: Node[], options: MatcherOptions) { - return new ResolvingMatcher(language, nodes, options); - } -} - -class ResolvingMatcher extends DefaultMatcher { - - public match(expression: string, options: EncounterOptions={}) { - options.matchIsEqual = options.partial + public build() { + this.options.matchIsEqual = (e) => e.partial ? (a, b) => a.intent === b.intent && isDeepEqual(a.values, b.values) : (a, b) => a.intent === b.intent; - return super.match(expression, options); + return super.build(); } - } diff --git a/src/resolver/ValueNode.ts b/src/resolver/ValueNode.ts index bdd30b8..1e0fcc5 100644 --- a/src/resolver/ValueNode.ts +++ b/src/resolver/ValueNode.ts @@ -97,7 +97,7 @@ export class ValueNode extends Node { for(const v of valueEncounter.matches) { const matchCopy = m.copy(); - matchCopy.data.values.set(this.id, v.value); + matchCopy.data.values[this.id] = v.value; matchCopy.scoreData.score += 0.9 * v.score; results.push(matchCopy); } diff --git a/src/resolver/ValueParserNode.ts b/src/resolver/ValueParserNode.ts index 202cf91..e52e421 100644 --- a/src/resolver/ValueParserNode.ts +++ b/src/resolver/ValueParserNode.ts @@ -2,28 +2,31 @@ import { isDeepEqual } from '../utils/equality'; import { Node } from '../graph/Node'; import { SubNode } from '../graph/SubNode'; -import { Matcher, Encounter } from '../graph/matching'; +import { Encounter } from '../graph/matching'; +import { Graph } from '../graph/Graph'; -export interface ValueParserOptions { +export interface ValueParserOptions { /** * If the partial should be presented as a blank value if it is being * matched and no more tokens are available. */ partialBlankWhenNoToken?: boolean; + + mapper: (data: V, encounter: Encounter) => O; } export class ValueParserNode extends Node { public readonly id: string; private node: SubNode; - private options: ValueParserOptions; + private options: ValueParserOptions; private partialBlankWhenNoToken: boolean; - constructor(id: string, matcher: Matcher, options: ValueParserOptions={}) { + constructor(id: string, graph: Graph, options: ValueParserOptions) { super(); this.id = id; - this.node = new SubNode(matcher, matcher.options); + this.node = new SubNode(graph, graph.options); this.options = options; this.partialBlankWhenNoToken = ! this.node.supportsPartial || options.partialBlankWhenNoToken || false; @@ -33,20 +36,14 @@ export class ValueParserNode extends Node { * using the same mapper as would be used if graph is directly matched * on. */ - const mapper = matcher.options.mapper; this.node.partialFallback = { id: this.id, }; this.node.mapper = (r, encounter) => { - if(mapper) { - // Perform the mapping using the graphs mapper - r = mapper(r, encounter); - } - // Map it into a value format return { id: id, - value: r//options.mapper ? options.mapper(r) : r + value: options.mapper(r, encounter) }; }; } diff --git a/src/resolver/expressions.ts b/src/resolver/expressions.ts index e79ea81..78d3a8f 100644 --- a/src/resolver/expressions.ts +++ b/src/resolver/expressions.ts @@ -13,11 +13,11 @@ import { ResolvedIntent } from './ResolvedIntent'; * Refresh the expression by copying back values from the matches into the * expression and sub-expressions. */ -export function refresh(v: ResolvedIntent) { +export function refresh(v: ResolvedIntent) { for(const e of v.expression) { if(e.type === ExpressionPartType.Value) { const part: ValuePart = e as ValuePart; - part.value = v.values.get(part.id); + part.value = v.values[part.id]; if(part.value && part.value.expression) { // The value has an expression - refresh it diff --git a/src/time/date-intervals.ts b/src/time/date-intervals.ts index 6521ef4..9703196 100644 --- a/src/time/date-intervals.ts +++ b/src/time/date-intervals.ts @@ -1,4 +1,4 @@ -import { map as mapDate, today } from './dates'; +import { mapDate, today } from './dates'; import { cloneObject } from '../utils/cloning'; import { IntervalValue } from './interval-value'; import { adjusted } from './matching'; @@ -48,7 +48,7 @@ function applyRelationAndEdge(r: DateTimeData, edge: IntervalEdge) { return r; } -export function map(r: IntervalData, e: DateTimeEncounter): IntervalValue { +export function mapDateInterval(r: IntervalData, e: DateTimeEncounter): IntervalValue { let start = null; let end = null; diff --git a/src/time/date-times.ts b/src/time/date-times.ts index 479bab0..06bef06 100644 --- a/src/time/date-times.ts +++ b/src/time/date-times.ts @@ -1,10 +1,10 @@ import { DateTimeData } from './DateTimeData'; import { DateTimeEncounter } from './DateTimeEncounter'; -import { map as mapDate } from './dates'; -import { map as mapTime } from './times'; +import { mapDate } from './dates'; +import { mapTime } from './times'; -export function map(r: DateTimeData, e: DateTimeEncounter) { +export function mapDateTime(r: DateTimeData, e: DateTimeEncounter) { const result = mapDate(r, e); // If the date doesn't map, skip time mapping diff --git a/src/time/dates.ts b/src/time/dates.ts index 2f220fe..30a16ee 100644 --- a/src/time/dates.ts +++ b/src/time/dates.ts @@ -185,7 +185,7 @@ function adjust(r: DateTimeData, options: DateTimeOptions, time: Date, def: Adju return time; } -export function map(r: DateTimeData, e: DateTimeEncounter, options: DateTimeOptions={}): MutableDateValue | null { +export function mapDate(r: DateTimeData, e: DateTimeEncounter, options: DateTimeOptions={}): MutableDateValue | null { if(! r.relationToCurrent) { r.relationToCurrent = TimeRelationship.Auto; } @@ -198,7 +198,7 @@ export function map(r: DateTimeData, e: DateTimeEncounter, options: DateTimeOpti sub.relationToCurrent = r.relationToCurrent; sub.intervalEdge = r.intervalEdge; - const resolvedTime = map(sub, e, options); + const resolvedTime = mapDate(sub, e, options); if(! resolvedTime) return null; time = resolvedTime.toDate(); diff --git a/src/time/durations.ts b/src/time/durations.ts index 44c2bf5..c7aa562 100644 --- a/src/time/durations.ts +++ b/src/time/durations.ts @@ -15,7 +15,7 @@ import { DateTimeData } from './DateTimeData'; /** * Map a duration of time into a usable object. */ -export function map(r: DateTimeData): Duration { +export function mapDuration(r: DateTimeData): Duration { const result = new MutableDuration(); result.years = r.relativeYears; result.quarters = r.relativeQuarters; diff --git a/src/time/months.ts b/src/time/months.ts index 5dd8e00..82801c9 100644 --- a/src/time/months.ts +++ b/src/time/months.ts @@ -34,7 +34,7 @@ export function previousMonth(r: any, e: DateTimeEncounter): DateTimeData { }; } -export function map(r: DateTimeData, e: DateTimeEncounter): MutableDateValue | null { +export function mapMonth(r: DateTimeData, e: DateTimeEncounter): MutableDateValue | null { let time = currentTime(e.options); if(typeof r.year !== 'undefined') { diff --git a/src/time/quarters.ts b/src/time/quarters.ts index f447cab..f77afba 100644 --- a/src/time/quarters.ts +++ b/src/time/quarters.ts @@ -7,7 +7,7 @@ import { DateTimeEncounter } from './DateTimeEncounter'; import { DateTimeData } from './DateTimeData'; import { currentTime } from './currentTime'; -import { map as mapInterval } from './date-intervals'; +import { mapDateInterval } from './date-intervals'; export function thisQuarter(r: any, e: DateTimeEncounter): DateTimeData { const time = currentTime(e.options); @@ -35,6 +35,6 @@ export function previousQuarter(r: any, e: DateTimeEncounter): DateTimeData { /** * Map quarters as intervals. */ -export function map(r: DateTimeData, e: DateTimeEncounter) { - return mapInterval({ start: r, end: r }, e); +export function mapQuarter(r: DateTimeData, e: DateTimeEncounter) { + return mapDateInterval({ start: r, end: r }, e); } diff --git a/src/time/times.ts b/src/time/times.ts index 1cba107..8a4f3c8 100644 --- a/src/time/times.ts +++ b/src/time/times.ts @@ -70,7 +70,7 @@ export function toAM(time: DateTimeData) { return time; } -export function map(r: DateTimeData, e: DateTimeEncounter, options?: DateTimeOptions, result?: MutableDateValue) { +export function mapTime(r: DateTimeData, e: DateTimeEncounter, options?: DateTimeOptions, result?: MutableDateValue) { result = result || new MutableDateValue(); const current = currentTime(e.options); diff --git a/src/time/weeks.ts b/src/time/weeks.ts index b2b22b4..8b95725 100644 --- a/src/time/weeks.ts +++ b/src/time/weeks.ts @@ -6,7 +6,7 @@ import { import { DateTimeEncounter } from './DateTimeEncounter'; import { DateTimeData } from './DateTimeData'; -import { map as mapInterval } from './date-intervals'; +import { mapDateInterval } from './date-intervals'; import { currentTime } from './currentTime'; export function thisWeek(r: any, e: DateTimeEncounter): DateTimeData { @@ -35,6 +35,6 @@ export function previousWeek(r: any, e: DateTimeEncounter): DateTimeData { /** * Map weeks as intervals. */ -export function map(r: DateTimeData, e: DateTimeEncounter) { - return mapInterval({ start: r, end: r }, e); +export function mapWeek(r: DateTimeData, e: DateTimeEncounter) { + return mapDateInterval({ start: r, end: r }, e); } diff --git a/src/time/years.ts b/src/time/years.ts index e2c3101..153b0d9 100644 --- a/src/time/years.ts +++ b/src/time/years.ts @@ -28,7 +28,7 @@ export function previousYear(r: any, e: DateTimeEncounter): DateTimeData { }; } -export function map(r: DateTimeData, e: DateTimeEncounter): MutableDateValue | null { +export function mapYear(r: DateTimeData, e: DateTimeEncounter): MutableDateValue | null { const now = currentTime(e.options); let time; diff --git a/src/types/index.ts b/src/types/index.ts index 0aae279..58d7fb0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,3 @@ export { IntervalValue as DateTimeIntervalValue } from '../time/interval-value'; export * from '../time/date-value'; +export { Duration } from '../time/durations'; diff --git a/src/values/any.ts b/src/values/any.ts index e2aa3b4..b57fc7e 100644 --- a/src/values/any.ts +++ b/src/values/any.ts @@ -1,4 +1,4 @@ -import { ValueMatcher } from './base'; +import { ValueMatcher, NodeConvertable } from './base'; import { ValueNodeOptions } from '../resolver/ValueNode'; import { ValueEncounter } from '../resolver/ValueEncounter'; @@ -8,6 +8,6 @@ const instance: ValueNodeOptions = { } }; -export function anyStringValue(options?: Omit, 'match'>) { +export function anyStringValue(options?: Omit, 'match'>): NodeConvertable { return new ValueMatcher(Object.assign({}, options, instance)); } diff --git a/src/values/base.ts b/src/values/base.ts index 6775d44..03a3e80 100644 --- a/src/values/base.ts +++ b/src/values/base.ts @@ -2,30 +2,32 @@ import { ValueParserNode, ValueParserOptions } from '../resolver/ValueParserNode import { ValueNode, ValueNodeOptions } from '../resolver/ValueNode'; import { Language } from '../language/Language'; import { Node } from '../graph/Node'; -import { Matcher, EncounterOptions } from '../graph/matching'; +import { Matcher, GraphMatcher, GraphMatcherOptions, Encounter } from '../graph/matching'; +import { Graph } from '../graph/Graph'; +import { MatchReductionEncounter } from '../graph/matching/MatchReductionEncounter'; /** * Object that can be converted into a node within a graph. */ -export interface NodeConvertable { +export interface NodeConvertable { toNode(id: string): Node; } /** * Function that creates a convertable item for the given language. */ -export type LanguageSpecificFactory = (language: Language) => ParsingValue; +export type LanguageSpecificFactory = (language: Language) => ParsingValue; -export type Value = LanguageSpecificValue | NodeConvertable; +export type Value = LanguageSpecificValue | NodeConvertable; -export class LanguageSpecificValue { - private factory: LanguageSpecificFactory; +export class LanguageSpecificValue { + private factory: LanguageSpecificFactory; - constructor(factory: LanguageSpecificFactory) { + constructor(factory: LanguageSpecificFactory) { this.factory = factory; } - public create(language: Language): NodeConvertable { + public create(language: Language): NodeConvertable { return this.factory(language); } @@ -34,27 +36,45 @@ export class LanguageSpecificValue { * * @param language */ - public matcher(language: Language): Matcher { + public matcher(language: Language): Matcher { const value = this.factory(language); - return value.matcher; + return new GraphMatcher(language, value.graph, { + reducer: value.options.reducer || (({ encounter, results }: MatchReductionEncounter) => { + const first = results.first(); + if(first === null || typeof first === 'undefined') { + return null; + } else { + return value.options.mapper(first.data, encounter); + } + }) as any + }); } } -export class ParsingValue { - public matcher: Matcher; - private options: ValueParserOptions; +export interface ParsingValueOptions { + mapper: (data: RawData, encounter: Encounter) => Mapped; - constructor(parser: Matcher, options?: ValueParserOptions) { - this.matcher = parser; - this.options = options || {}; + reducer?: (encounter: MatchReductionEncounter) => MatcherResult; + + partialBlankWhenNoToken?: boolean; +} + +export class ParsingValue { + public graph: Graph; + public options: ParsingValueOptions; + + constructor(graph: Graph, options: ParsingValueOptions) { + this.graph = graph; + + this.options = options; } public toNode(id: string) { - return new ValueParserNode(id, this.matcher, this.options); + return new ValueParserNode(id, this.graph, this.options); } } -export class ValueMatcher { +export class ValueMatcher implements NodeConvertable { private options: ValueNodeOptions; constructor(options: ValueNodeOptions) { diff --git a/src/values/boolean.ts b/src/values/boolean.ts index bc0fd3b..b70be39 100644 --- a/src/values/boolean.ts +++ b/src/values/boolean.ts @@ -1,5 +1,11 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; export function booleanValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('boolean'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.Boolean), + { + mapper: o => o + } + )); } diff --git a/src/values/custom.ts b/src/values/custom.ts index 4627f1e..3b9741c 100644 --- a/src/values/custom.ts +++ b/src/values/custom.ts @@ -1,4 +1,4 @@ -import { ValueMatcher } from './base'; +import { ValueMatcher, NodeConvertable } from './base'; import { ValueNodeOptions } from '../resolver/ValueNode'; import { ValueEncounter } from '../resolver/ValueEncounter'; @@ -7,7 +7,7 @@ import { ValueEncounter } from '../resolver/ValueEncounter'; * * @param options */ -export function customValue(options: ValueNodeOptions | ((encounter: ValueEncounter) => Promise)) { +export function customValue(options: ValueNodeOptions | ((encounter: ValueEncounter) => Promise)): NodeConvertable { if(typeof options === 'undefined') { throw new Error('Value matcher must be specified'); } diff --git a/src/values/date-duration.ts b/src/values/date-duration.ts index 310f9fa..4bd87bc 100644 --- a/src/values/date-duration.ts +++ b/src/values/date-duration.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { mapDuration } from '../time/durations'; +import { KnownGraphs } from '../graph/KnownGraphs'; export function dateDurationValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('date-duration'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.DateDuration), + { + mapper: mapDuration + } + )); } diff --git a/src/values/date-interval.ts b/src/values/date-interval.ts index 09a97c6..66d0f65 100644 --- a/src/values/date-interval.ts +++ b/src/values/date-interval.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapDateInterval } from '../time/date-intervals'; export function dateIntervalValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('date-interval'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.DateInterval), + { + mapper: mapDateInterval + } + )); } diff --git a/src/values/date-time-duration.ts b/src/values/date-time-duration.ts index eeb3244..0889d11 100644 --- a/src/values/date-time-duration.ts +++ b/src/values/date-time-duration.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapDuration } from '../time/durations'; export function dateTimeDurationValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('date-time-duration'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.DateTimeDuration), + { + mapper: mapDuration + } + )); } diff --git a/src/values/date-time.ts b/src/values/date-time.ts index e474c79..20b2c02 100644 --- a/src/values/date-time.ts +++ b/src/values/date-time.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapDateTime } from '../time/date-times'; export function dateTime() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('date-time'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.DateTime), + { + mapper: mapDateTime + } + )); } diff --git a/src/values/date.ts b/src/values/date.ts index cd1601d..b2dc445 100644 --- a/src/values/date.ts +++ b/src/values/date.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapDate } from '../time/dates'; export function dateValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('date'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.Date), + { + mapper: mapDate + } + )); } diff --git a/src/values/enumeration.ts b/src/values/enumeration.ts index 309fcb5..5292862 100644 --- a/src/values/enumeration.ts +++ b/src/values/enumeration.ts @@ -7,15 +7,17 @@ export function enumerationValue(values: V[], textMapper?: (value: V) => stri const mapper = textMapper ? textMapper : DEFAULT_MAPPER; return new LanguageSpecificValue(language => { - const builder = new GraphBuilder(language) + let builder = new GraphBuilder(language) .allowPartial(); - values.forEach(value => { - builder.add(mapper(value), value); + values.forEach(value => { + builder = builder.add(mapper(value), value); }); - return new ParsingValue(builder.toMatcher(), { - partialBlankWhenNoToken: true + return new ParsingValue(builder.build(), { + partialBlankWhenNoToken: true, + + mapper: value => value }); }); } diff --git a/src/values/integer.ts b/src/values/integer.ts index 0269508..0a44271 100644 --- a/src/values/integer.ts +++ b/src/values/integer.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapNumber } from '../numbers/numbers'; export function integerValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('integer'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.Integer), + { + mapper: mapNumber + } + )); } diff --git a/src/values/number.ts b/src/values/number.ts index c09c646..c982675 100644 --- a/src/values/number.ts +++ b/src/values/number.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapNumber } from '../numbers/numbers'; export function numberValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('number'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.Number), + { + mapper: mapNumber + } + )); } diff --git a/src/values/options.ts b/src/values/options.ts index 4c79ecc..be061f7 100644 --- a/src/values/options.ts +++ b/src/values/options.ts @@ -1,18 +1,17 @@ import { GraphBuilder } from '../graph/GraphBuilder'; import { ResolverBuilder } from '../resolver/ResolverBuilder'; -import { LanguageSpecificValue, ParsingValue, NodeConvertable, Value } from './base'; -import { ValueParserOptions } from '../resolver/ValueParserNode'; +import { LanguageSpecificValue, ParsingValue, Value } from './base'; import { ResolvedIntent } from '../resolver/ResolvedIntent'; import { ExpressionPart } from '../resolver/expression/ExpressionPart'; -export interface OptionBuilderOptions extends ValueParserOptions { +export interface OptionBuilderOptions { name?: string; } -export class OptionsBuilder { +export class OptionsBuilder { private name: string; - private options: ValueParserOptions; + private options: OptionBuilderOptions; private data: Record; constructor(options: OptionBuilderOptions) { @@ -21,7 +20,7 @@ export class OptionsBuilder { this.data = {}; } - public option(id: string): OptionBuilder { + public option(id: Id): OptionBuilder { if(typeof id !== 'string') { throw new Error('Options require identifiers that are strings'); } @@ -35,7 +34,7 @@ export class OptionsBuilder { return { value(valueId, type) { result.values[valueId] = type; - return this; + return this as any; }, add(...args) { @@ -45,14 +44,14 @@ export class OptionsBuilder { done() { self.data[id] = result; - return self; + return self as any; } }; } public build() { return new LanguageSpecificValue(language => { - const parent = new GraphBuilder(language) + const parent = new GraphBuilder>(language) .name(this.name) .allowPartial(); @@ -77,46 +76,101 @@ export class OptionsBuilder { parent.add(parser, v => v[0]); } - const matcher = parent.mapResults(v => new Option(v.intent, v.values, v.expression)) - .toMatcher(); + const graph = parent.build(); + + const repeating = language.repeating(graph).build(); - const repeating = language.repeating(matcher) - .onlyBest() - .toMatcher(); + return new ParsingValue(repeating, { + mapper: value => { + const allResults: Option[] = []; + for(const option of value) { + allResults.push(new Option(option.intent, option.values, option.expression)); + } - return new ParsingValue(repeating, Object.assign({ - supportsPartial: true - }, this.options)); + return new OptionsSet(allResults); + }, + ...this.options + }); }); } } export function optionsValue(options: OptionBuilderOptions={}) { - return new OptionsBuilder(options); + return new OptionsBuilder<{}>(options); } -export interface OptionBuilder { - value(id: string, type: Value): this; +export interface OptionBuilder { + value(id: I, type: Value): OptionBuilder; add(...args: string[]): this; - done(): OptionsBuilder; + done(): OptionsBuilder; } interface OptionData { - values: Record; + values: Record>; phrases: any[]; } +/** + * Options interface, used to access options as they are found in the matched + * expression. + */ +export interface Options { + /** + * Get the first option with the given id. + * + * @param option + * the option to fetch + */ + get(option: Id): Option | null; + + /** + * Get all options with the given id. + */ + getAll(option: Id): Option[]; + + /** + * Get all of the options as an array. + */ + toArray(): ReadonlyArray>; +} + +class OptionsSet implements Options { + private readonly matches: Option[]; + + constructor(matches: Option[]) { + this.matches = matches; + } + + public get(option: Id): Option | null { + for(const match of this.matches) { + if(match.option === option) { + return match as any; + } + } + + return null; + } + + public getAll(option: Id): Option[] { + return this.matches.filter(match => match.option === option); + } + + public toArray() { + return this.matches; + } +} + /** * Custom option value to hide enumeration for equality checks. */ -export class Option { +export class Option { public readonly option: string; - public readonly values: Map; + public readonly values: Values; public readonly expression: ExpressionPart[]; - constructor(option: string, values: Map, expression: ExpressionPart[]) { + constructor(option: string, values: Values, expression: ExpressionPart[]) { this.option = option; this.values = values; diff --git a/src/values/ordinal.ts b/src/values/ordinal.ts index 5c43c52..8e2382f 100644 --- a/src/values/ordinal.ts +++ b/src/values/ordinal.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapOrdinal } from '../numbers/ordinals'; export function ordinalValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('ordinal'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.Ordinal), + { + mapper: mapOrdinal + } + )); } diff --git a/src/values/time-duration.ts b/src/values/time-duration.ts index 84ae536..6c74017 100644 --- a/src/values/time-duration.ts +++ b/src/values/time-duration.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapDuration } from '../time/durations'; export function timeDurationValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('time-duration'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.TimeDuration), + { + mapper: mapDuration + } + )); } diff --git a/src/values/time.ts b/src/values/time.ts index c2d1214..a7e78d8 100644 --- a/src/values/time.ts +++ b/src/values/time.ts @@ -1,5 +1,12 @@ import { LanguageSpecificValue, ParsingValue } from './base'; +import { KnownGraphs } from '../graph/KnownGraphs'; +import { mapTime } from '../time/times'; export function timeValue() { - return new LanguageSpecificValue(language => new ParsingValue(language.getMatcher('time'))); + return new LanguageSpecificValue(language => new ParsingValue( + language.findGraph(KnownGraphs.Time), + { + mapper: mapTime + } + )); } diff --git a/test/builder.test.ts b/test/builder.test.ts index a6f00f2..73e2c9d 100644 --- a/test/builder.test.ts +++ b/test/builder.test.ts @@ -1,35 +1,42 @@ import { en } from '../src/language/en'; import { GraphBuilder } from '../src/graph/GraphBuilder'; +import { GraphMatcher, GraphMatcherOptions } from '../src/graph/matching'; + +const options: GraphMatcherOptions = { + reducer: ({ results }) => results.toArray().map(r => r.data) +}; describe('Graph Builder', function() { describe('Linear', function() { - const parser = new GraphBuilder(en) + const graph = new GraphBuilder(en) .add('hello world', true) - .toMatcher(); + .build(); + + const matcher = new GraphMatcher(en, graph, options); it('All tokens', function() { - return parser.match('hello world') + return matcher.match('hello world') .then(results => { expect(results).toEqual([ true ]); }); }); it('Single token', function() { - return parser.match('hello') + return matcher.match('hello') .then(results => { expect(results).toEqual([ ]); }); }); it('Extra tokens', function() { - return parser.match('hello world and more') + return matcher.match('hello world and more') .then(results => { expect(results).toEqual([]); }); }); it('Extra tokens - partial, no match', function() { - return parser.match('hello world and more', { partial: true }) + return matcher.match('hello world and more', { partial: true }) .then(results => { expect(results).toEqual([ ]); }); @@ -37,54 +44,57 @@ describe('Graph Builder', function() { }); describe('Multiple linear', function() { - const parser = new GraphBuilder(en) + const graph = new GraphBuilder(en) .add('hello world', 1) .add('cookies', 2) .add('hello', 3) - .toMatcher(); + .build(); + const matcher = new GraphMatcher(en, graph, options); it('Match longest - partial', function() { - return parser.match('hello world', { partial: true }) + return matcher.match('hello world', { partial: true }) .then(results => { expect(results).toEqual([ 1 ]); }); }); it('Match standalone', function() { - return parser.match('cookies') + return matcher.match('cookies') .then(results => { expect(results).toEqual([ 2 ]); }); }); it('Match shortest', function() { - return parser.match('hello') + return matcher.match('hello') .then(results => { expect(results).toEqual([ 3 ]); }); }); }); - describe('Parser within parser', function() { + describe('Graph within graph', function() { describe('Single token + Single token', function() { const sub = new GraphBuilder(en) .add('hello', 1) - .toMatcher(); + .build(); - const parser = new GraphBuilder(en) + const graph = new GraphBuilder(en) .add([ sub, 'world' ], 2) - .toMatcher(); + .build(); + + const matcher = new GraphMatcher(en, graph, options); it('All tokens', function() { - return parser.match('hello world') + return matcher.match('hello world') .then(results => { expect(results).toEqual([ 2 ]); }); }); it('First token', function() { - return parser.match('hello') + return matcher.match('hello') .then(results => { expect(results).toEqual([ ]); }); @@ -92,19 +102,21 @@ describe('Graph Builder', function() { }); describe('Node factory', function() { - const parser = new GraphBuilder(en) + const graph = new GraphBuilder(en) .add(GraphBuilder.custom(t => t.raw === 'one' ? true : null), 1) - .toMatcher(); + .build(); + + const matcher = new GraphMatcher(en, graph, options); it('Single token', function() { - return parser.match('one') + return matcher.match('one') .then(results => { expect(results).toEqual([ 1 ]); }); }); it('Invalid first token', function() { - return parser.match('three') + return matcher.match('three') .then(results => { expect(results).toEqual([ ]); }); @@ -112,29 +124,31 @@ describe('Graph Builder', function() { }); describe('Previous match', function() { - const parser = new GraphBuilder(en) + const graph = new GraphBuilder(en) .add('one', 1) .add('three', 3) .add([ GraphBuilder.result(v => v === 1), 'two' ], 2) .add([ GraphBuilder.result(v => v === 1), 'two', GraphBuilder.result(v => v === 3) ], 4) - .toMatcher(); + .build(); + + const matcher = new GraphMatcher(en, graph, options); it('Single token', function() { - return parser.match('one') + return matcher.match('one') .then(results => { expect(results).toEqual([ 1 ]); }); }); it('All tokens - partial', function() { - return parser.match('one two', { partial: true }) + return matcher.match('one two', { partial: true }) .then(results => { expect(results).toEqual([ 2 ]); }); }); it('Invalid first token - partial', function() { - return parser.match('three two', { partial: true }) + return matcher.match('three two', { partial: true }) .then(results => { expect(results).toEqual([ ]); }); @@ -142,51 +156,53 @@ describe('Graph Builder', function() { }); describe('Recursive match', function() { - const parser = new GraphBuilder(en) + const graph = new GraphBuilder(en) .add(/^[a-z]$/, v => v[0]) .add([ GraphBuilder.result(v => true), GraphBuilder.result(v => true) ], v => v[0] + v[1]) .add([ GraphBuilder.result(v => true), '-', GraphBuilder.result(v => true) ], v => { return v[0] + v[1]; }) - .toMatcher(); + .build(); + + const matcher = new GraphMatcher(en, graph, options); it('a', function() { - return parser.match('a') + return matcher.match('a') .then(results => { expect(results).toEqual([ 'a' ]); }); }); it('a b', function() { - return parser.match('a b') + return matcher.match('a b') .then(results => { expect(results).toEqual([ 'ab' ]); }); }); it('a b c', function() { - return parser.match('a b c') + return matcher.match('a b c') .then(results => { expect(results).toEqual([ 'abc' ]); }); }); it('a b c d', function() { - return parser.match('a b c d') + return matcher.match('a b c d') .then(results => { expect(results).toEqual([ 'abcd' ]); }); }); it('a - b c', function() { - return parser.match('a - b c') + return matcher.match('a - b c') .then(results => { expect(results).toEqual([ 'abc' ]); }); }); it('abc', function() { - return parser.match('abc') + return matcher.match('abc') .then(results => { expect(results).toEqual([ ]); }); diff --git a/test/intents.test.ts b/test/intents.test.ts index fc500f7..8598b9f 100644 --- a/test/intents.test.ts +++ b/test/intents.test.ts @@ -98,7 +98,7 @@ describe('Intents', function() { .then(results => { expect(results.matches.length).toEqual(1); expect(results.best.intent).toEqual('customer:orders'); - expect(results.best.values.get('customer')).toEqual('Test'); + expect(results.best.values.customer).toEqual('Test'); }); }); @@ -107,7 +107,7 @@ describe('Intents', function() { .then(results => { expect(results.matches.length).toEqual(1); expect(results.best.intent).toEqual('customer:orders'); - expect(results.best.values.get('customer')).toEqual('Test'); + expect(results.best.values.customer).toEqual('Test'); }); }); @@ -116,7 +116,7 @@ describe('Intents', function() { .then(results => { expect(results.matches.length).toEqual(1); expect(results.best.intent).toEqual('customer:orders'); - expect(results.best.values.get('customer')).toEqual('Test'); + expect(results.best.values.customer).toEqual('Test'); }); }); diff --git a/test/language/en/date-duration.test.ts b/test/language/en/date-duration.test.ts index 9cf02ba..fde6dfd 100644 --- a/test/language/en/date-duration.test.ts +++ b/test/language/en/date-duration.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'date-duration'); +import { en } from '../../../src/language/en'; +import { dateDurationGraph } from '../../../src/language/en/dateDurationGraph'; +import { mapDuration } from '../../../src/time/durations'; + +const test = testRunner(en, dateDurationGraph, mapDuration); describe('English', function() { describe('Date Duration', function() { diff --git a/test/language/en/date-interval.test.ts b/test/language/en/date-interval.test.ts index 31333fe..8a5b084 100644 --- a/test/language/en/date-interval.test.ts +++ b/test/language/en/date-interval.test.ts @@ -1,7 +1,9 @@ import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; +import { dateIntervalGraph } from '../../../src/language/en/dateIntervalGraph'; +import { mapDateInterval } from '../../../src/time/date-intervals'; -const test = testRunner(en, 'date-interval'); +const test = testRunner(en, dateIntervalGraph, mapDateInterval); describe('English', () => { describe('Date Interval', () => { diff --git a/test/language/en/date-time-duration.test.ts b/test/language/en/date-time-duration.test.ts index 68fae68..5021081 100644 --- a/test/language/en/date-time-duration.test.ts +++ b/test/language/en/date-time-duration.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'date-time-duration'); +import { en } from '../../../src/language/en'; +import { dateTimeDurationGraph } from '../../../src/language/en/dateTimeDurationGraph'; +import { mapDuration } from '../../../src/time/durations'; + +const test = testRunner(en, dateTimeDurationGraph, mapDuration); describe('English', function() { diff --git a/test/language/en/date-time.test.ts b/test/language/en/date-time.test.ts index 436ddfe..32604f5 100644 --- a/test/language/en/date-time.test.ts +++ b/test/language/en/date-time.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'date-time'); +import { en } from '../../../src/language/en'; +import { dateTimeGraph } from '../../../src/language/en/dateTimeGraph'; +import { mapDateTime } from '../../../src/time/date-times'; + +const test = testRunner(en, dateTimeGraph, mapDateTime); describe('English', function() { diff --git a/test/language/en/date.test.ts b/test/language/en/date.test.ts index 698b086..8b6d7ad 100644 --- a/test/language/en/date.test.ts +++ b/test/language/en/date.test.ts @@ -1,7 +1,9 @@ import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; +import { dateGraph } from '../../../src/language/en/dateGraph'; +import { mapDate } from '../../../src/time/dates'; -const test = testRunner(en, 'date'); +const test = testRunner(en, dateGraph, mapDate); describe('English', function() { describe('Date', function() { diff --git a/test/language/en/day-of-week.test.ts b/test/language/en/day-of-week.test.ts index eea8bca..fe27c7e 100644 --- a/test/language/en/day-of-week.test.ts +++ b/test/language/en/day-of-week.test.ts @@ -1,8 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; + +import { en } from '../../../src/language/en'; +import { dayOfWeekGraph } from '../../../src/language/en/dayOfWeekGraph'; import { Weekday } from '../../../src/time/Weekday'; -const test = testRunner(en, 'day-of-week'); +const test = testRunner(en, dayOfWeekGraph, d => d as Weekday); describe('English', function() { diff --git a/test/language/en/integer.test.ts b/test/language/en/integer.test.ts index 3cff023..ed00322 100644 --- a/test/language/en/integer.test.ts +++ b/test/language/en/integer.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'integer'); +import { en } from '../../../src/language/en'; +import { integerGraph } from '../../../src/language/en/integerGraph'; +import { mapNumber } from '../../../src/numbers/numbers'; + +const test = testRunner(en, integerGraph, mapNumber); describe('English', function() { diff --git a/test/language/en/month.test.ts b/test/language/en/month.test.ts index 470e1c6..9a51390 100644 --- a/test/language/en/month.test.ts +++ b/test/language/en/month.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'month'); +import { en } from '../../../src/language/en'; +import { monthGraph } from '../../../src/language/en/monthGraph'; +import { mapMonth } from '../../../src/time/months'; + +const test = testRunner(en, monthGraph, mapMonth); describe('English', function() { diff --git a/test/language/en/number.test.ts b/test/language/en/number.test.ts index 8f8187f..ee28867 100644 --- a/test/language/en/number.test.ts +++ b/test/language/en/number.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'number'); +import { en } from '../../../src/language/en'; +import { numberGraph } from '../../../src/language/en/numberGraph'; +import { mapNumber } from '../../../src/numbers/numbers'; + +const test = testRunner(en, numberGraph, mapNumber); describe('English', function() { diff --git a/test/language/en/ordinal.test.ts b/test/language/en/ordinal.test.ts index 043f5e7..dc8de04 100644 --- a/test/language/en/ordinal.test.ts +++ b/test/language/en/ordinal.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'ordinal'); +import { en } from '../../../src/language/en'; +import { ordinalGraph } from '../../../src/language/en/ordinalGraph'; +import { mapOrdinal } from '../../../src/numbers/ordinals'; + +const test = testRunner(en, ordinalGraph, mapOrdinal); describe('English', function() { diff --git a/test/language/en/repeating.test.ts b/test/language/en/repeating.test.ts index c8f7c18..32b068e 100644 --- a/test/language/en/repeating.test.ts +++ b/test/language/en/repeating.test.ts @@ -1,8 +1,8 @@ import { en } from '../../../src/language/en'; -import { testRunnerViaMatcher } from '../helpers'; +import { testRunnerViaGraph } from '../helpers'; import { GraphBuilder } from '../../../src/graph/GraphBuilder'; -const phrase = new GraphBuilder(en) +const phrase = new GraphBuilder(en) .name('phrase') .allowPartial() @@ -10,12 +10,9 @@ const phrase = new GraphBuilder(en) .add('Hello', () => 1) .add('World', () => 2) - .toMatcher(); + .build(); -const test = testRunnerViaMatcher(en.repeating(phrase) - .onlyBest() - .toMatcher() -); +const test = testRunnerViaGraph(en, en.repeating(phrase).build(), r => r); describe('English', function() { diff --git a/test/language/en/time-duration.test.ts b/test/language/en/time-duration.test.ts index 08eb7ec..d4d72d7 100644 --- a/test/language/en/time-duration.test.ts +++ b/test/language/en/time-duration.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'time-duration'); +import { en } from '../../../src/language/en'; +import { timeDurationGraph } from '../../../src/language/en/timeDurationGraph'; +import { mapDuration } from '../../../src/time/durations'; + +const test = testRunner(en, timeDurationGraph, mapDuration); describe('English', function() { diff --git a/test/language/en/time.test.ts b/test/language/en/time.test.ts index c9b5cd3..d0076aa 100644 --- a/test/language/en/time.test.ts +++ b/test/language/en/time.test.ts @@ -1,7 +1,9 @@ import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; +import { timeGraph } from '../../../src/language/en/timeGraph'; +import { mapTime } from '../../../src/time/times'; -const test = testRunner(en, 'time'); +const test = testRunner(en, timeGraph, mapTime); describe('English', function() { diff --git a/test/language/en/year.test.ts b/test/language/en/year.test.ts index 2438fb7..2f54276 100644 --- a/test/language/en/year.test.ts +++ b/test/language/en/year.test.ts @@ -1,7 +1,10 @@ -import { en } from '../../../src/language/en'; import { testRunner } from '../helpers'; -const test = testRunner(en, 'year'); +import { en } from '../../../src/language/en'; +import { yearGraph } from '../../../src/language/en/yearGraph'; +import { mapYear } from '../../../src/time/years'; + +const test = testRunner(en, yearGraph, mapYear); describe('English', function() { diff --git a/test/language/helpers.ts b/test/language/helpers.ts index 93b57a9..3524310 100644 --- a/test/language/helpers.ts +++ b/test/language/helpers.ts @@ -1,6 +1,9 @@ import { format } from 'date-fns'; -import { EncounterOptions, Matcher } from '../../src/graph/matching'; -import { Language } from '../../src/language/language'; +import { Language } from '../../src/language/Language'; + +import { EncounterOptions, Matcher, GraphMatcher, Encounter } from '../../src/graph/matching'; +import { Graph } from '../../src/graph/Graph'; +import { LanguageGraphFactory } from '../../src/language/LanguageGraphFactory'; function formatDate(d?: Date) { if(! d) return 'current time'; @@ -33,16 +36,29 @@ function createTester(f: (name: string, runner: any) => any, matcher: MatchingFu }; } -export function testRunner(lang: Language, matcherId: string): TestRunner { - const matcher = lang.findMatcher(matcherId); - if(! matcher) { - throw new Error('Matcher has not been registered on language'); - } - - return testRunnerViaMatcher(matcher); +export function testRunner( + lang: Language, + graphFactory: LanguageGraphFactory, + mapper: (value: V, encounter: Encounter) => O +): TestRunner { + const graph = lang.graph(graphFactory); + return testRunnerViaGraph(lang, graph, mapper); } -export function testRunnerViaMatcher(matcher: Matcher): TestRunner { +export function testRunnerViaGraph( + lang: Language, + graph: Graph, + mapper: (value: V, encounter: Encounter) => O +): TestRunner { + const matcher = new GraphMatcher(lang, graph, { + reducer: ({ encounter, results }) => { + const first = results.first(); + if(! first) return null; + + return mapper(first.data, encounter); + } + }); + const r = (text: string, options: EncounterOptions) => matcher.match(text, options); const func = createTester(it, r) as TestRunner; diff --git a/test/resolver.test.ts b/test/resolver.test.ts index cfc75c1..61f3ef6 100644 --- a/test/resolver.test.ts +++ b/test/resolver.test.ts @@ -24,7 +24,7 @@ describe('Resolver', function() { const resolver = new ResolverBuilder(en) .add('one') .add('one two three') - .build(); + .toMatcher(); it('#1', function() { return resolver.match('one') @@ -48,13 +48,13 @@ describe('Resolver', function() { .add('{a}') .add('one {a}') .add('{a} one') - .build(); + .toMatcher(); it('#1', function() { return resolver.match('one') .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('a')).toEqual('one'); + expect(r.best.values.a).toEqual('one'); expect(r.matches.length).toEqual(1); }); }); @@ -63,7 +63,7 @@ describe('Resolver', function() { return resolver.match('one test') .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('a')).toEqual('test'); + expect(r.best.values.a).toEqual('test'); expect(r.matches.length).toEqual(1); }); }); @@ -72,7 +72,7 @@ describe('Resolver', function() { return resolver.match('test one') .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('a')).toEqual('test'); + expect(r.best.values.a).toEqual('test'); expect(r.matches.length).toEqual(1); }); }); @@ -81,7 +81,7 @@ describe('Resolver', function() { return resolver.match('one one') .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('a')).toEqual('one'); + expect(r.best.values.a).toEqual('one'); expect(r.matches.length).toEqual(1); }); }); @@ -93,7 +93,7 @@ describe('Resolver', function() { .add('stuff {date}') .add('{date} stuff') .add('stuff {date} cookie') - .build(); + .toMatcher(); it('start', function() { return resolver.match('today stuff') @@ -161,7 +161,7 @@ describe('Resolver', function() { describe('Partial matching', function() { const resolver = new ResolverBuilder(en) .add('hello world') - .build(); + .toMatcher(); it('Full token', function() { @@ -190,7 +190,7 @@ describe('Resolver', function() { } })) .add('hello {test}') - .build(); + .toMatcher(); it('No value', function() { @@ -223,13 +223,13 @@ describe('Resolver', function() { .value('number', numberValue()) .add('stuff {number}') .add('a {number} c') - .build(); + .toMatcher(); it('With a number', function() { return resolver.match('stuff 2') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('number')).toEqual({ + expect(results.best.values.number).toEqual({ value: 2 }); }); @@ -253,7 +253,7 @@ describe('Resolver', function() { return resolver.match('stuff 2 thousand') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('number')).toEqual({ + expect(results.best.values.number).toEqual({ value: 2000 }); }); @@ -263,7 +263,7 @@ describe('Resolver', function() { return resolver.match('a two hundred c') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('number')).toEqual({ + expect(results.best.values.number).toEqual({ value: 200 }); }); @@ -275,13 +275,13 @@ describe('Resolver', function() { .value('boolean', booleanValue()) .add('stuff {boolean}') .add('a {boolean} c') - .build(); + .toMatcher(); it('With a boolean', function() { return resolver.match('stuff off') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('boolean')).toEqual(false); + expect(results.best.values.boolean).toEqual(false); }); }); @@ -303,7 +303,7 @@ describe('Resolver', function() { return resolver.match('stuff yes') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('boolean')).toEqual(true); + expect(results.best.values.boolean).toEqual(true); }); }); @@ -311,7 +311,7 @@ describe('Resolver', function() { return resolver.match('a false c') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('boolean')).toEqual(false); + expect(results.best.values.boolean).toEqual(false); }); }); }); @@ -341,13 +341,13 @@ describe('Resolver', function() { } })) .add('do {name}') - .build(); + .toMatcher(); it('Match', function() { return resolver.match('do one') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('name')).toEqual('one'); + expect(results.best.values.name).toEqual('one'); // Check that the expression matches const expression = results.best.expression; @@ -374,7 +374,7 @@ describe('Resolver', function() { return resolver.match('do four five') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('name')).toEqual('four five'); + expect(results.best.values.name).toEqual('four five'); // Check that the expression matches const expression = results.best.expression; @@ -472,13 +472,13 @@ describe('Resolver', function() { } })) .add('{name} value') - .build(); + .toMatcher(); it('Match', function() { return resolver.match('one value') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('name')).toEqual('one'); + expect(results.best.values.name).toEqual('one'); // Check that the expression matches const expression = results.best.expression; @@ -505,7 +505,7 @@ describe('Resolver', function() { return resolver.match('four five value') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('name')).toEqual('four five'); + expect(results.best.values.name).toEqual('four five'); // Check that the expression matches const expression = results.best.expression; @@ -574,13 +574,13 @@ describe('Resolver', function() { })) .add('{name} end') .add('{name} value end') - .build(); + .toMatcher(); it('Match', function() { return resolver.match('one value end') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('name')).toEqual('one'); + expect(results.best.values.name).toEqual('one'); }); }); @@ -609,7 +609,7 @@ describe('Resolver', function() { .add('stuff {boolean}') .add('a {boolean} c') .add('longer {free} message') - .build(); + .toMatcher(); it('Expression has source offsets', function() { return resolver.match('a yes c') diff --git a/test/time/dates.test.ts b/test/time/dates.test.ts index ff57626..826cded 100644 --- a/test/time/dates.test.ts +++ b/test/time/dates.test.ts @@ -1,10 +1,10 @@ -import { map } from '../../src/time/dates'; -import { TimeRelationship } from '../../src/time/relationship'; +import { mapDate } from '../../src/time/dates'; +import { TimeRelationship } from '../../src/time/TimeRelationship'; describe('Time', () => { describe('dates', () => { - describe('map', () => { + describe('mapDate', () => { const e = { options: { now: new Date(2017, 2, 24) @@ -12,7 +12,7 @@ describe('Time', () => { }; it('relativeYears keeps month and day', () => { - const r = map({ relativeYears: 0 }, e); + const r = mapDate({ relativeYears: 0 }, e); expect(r).toEqual({ period: 'year', @@ -23,7 +23,7 @@ describe('Time', () => { }); it('relativeYears works with positive years', () => { - const r = map({ relativeYears: 2 }, e); + const r = mapDate({ relativeYears: 2 }, e); expect(r).toEqual({ period: 'year', @@ -34,7 +34,7 @@ describe('Time', () => { }); it('relativeYears works with negative years', () => { - const r = map({ relativeYears: -2 }, e); + const r = mapDate({ relativeYears: -2 }, e); expect(r).toEqual({ period: 'year', @@ -45,7 +45,7 @@ describe('Time', () => { }); it('year resets month and day', () => { - const r = map({ year: 2018 }, e); + const r = mapDate({ year: 2018 }, e); expect(r).toEqual({ period: 'year', @@ -56,7 +56,7 @@ describe('Time', () => { }); it('relativeQuarters keeps day', () => { - const r = map({ relativeQuarters: 0 }, e); + const r = mapDate({ relativeQuarters: 0 }, e); expect(r).toEqual({ period: 'quarter', @@ -67,7 +67,7 @@ describe('Time', () => { }); it('relativeQuarters changes quarter', () => { - const r = map({ relativeQuarters: 1 }, e); + const r = mapDate({ relativeQuarters: 1 }, e); expect(r).toEqual({ period: 'quarter', @@ -78,7 +78,7 @@ describe('Time', () => { }); it('quarter after current keeps year', () => { - const r = map({ quarter: 3 }, e); + const r = mapDate({ quarter: 3 }, e); expect(r).toEqual({ period: 'quarter', @@ -89,7 +89,7 @@ describe('Time', () => { }); it('quarter before current changes year', () => { - const r = map({ quarter: 1 }, { options: { + const r = mapDate({ quarter: 1 }, { options: { now: new Date(2017, 4, 24) }}); @@ -102,7 +102,7 @@ describe('Time', () => { }); it('relativeWeeks keeps day', () => { - const r = map({ relativeWeeks: 0 }, e); + const r = mapDate({ relativeWeeks: 0 }, e); expect(r).toEqual({ period: 'week', @@ -113,7 +113,7 @@ describe('Time', () => { }); it('relativeWeeks changes week', () => { - const r = map({ relativeWeeks: 2 }, e); + const r = mapDate({ relativeWeeks: 2 }, e); expect(r).toEqual({ period: 'week', @@ -124,7 +124,7 @@ describe('Time', () => { }); it('week after current week keeps year', () => { - const r = map({ week: 13 }, e); + const r = mapDate({ week: 13 }, e); expect(r).toEqual({ period: 'week', @@ -135,7 +135,7 @@ describe('Time', () => { }); it('week before current week is next year', () => { - const r = map({ week: 11}, e); + const r = mapDate({ week: 11}, e); expect(r).toEqual({ period: 'week', @@ -146,7 +146,7 @@ describe('Time', () => { }); it('week before current week with past=true keeps year', () => { - const r = map({ week: 11, relationToCurrent: TimeRelationship.Past }, e); + const r = mapDate({ week: 11, relationToCurrent: TimeRelationship.Past }, e); expect(r).toEqual({ period: 'week', @@ -157,7 +157,7 @@ describe('Time', () => { }); it('relativeMonths keeps day', () => { - const r = map({ relativeMonths: 2 }, e); + const r = mapDate({ relativeMonths: 2 }, e); expect(r).toEqual({ period: 'month', @@ -168,7 +168,7 @@ describe('Time', () => { }); it('month after current month is same year', () => { - const r = map({ month: 4 }, e); + const r = mapDate({ month: 4 }, e); expect(r).toEqual({ period: 'month', @@ -179,7 +179,7 @@ describe('Time', () => { }); it('month before current month is next year', () => { - const r = map({ month: 1 }, e); + const r = mapDate({ month: 1 }, e); expect(r).toEqual({ period: 'month', @@ -190,7 +190,7 @@ describe('Time', () => { }); it('month before current month with past=true is same year', () => { - const r = map({ month: 1, relationToCurrent: TimeRelationship.Past }, e); + const r = mapDate({ month: 1, relationToCurrent: TimeRelationship.Past }, e); expect(r).toEqual({ period: 'month', @@ -201,7 +201,7 @@ describe('Time', () => { }); it('month before current month with year', () => { - const r = map({ month: 1, year: 2017 }, e); + const r = mapDate({ month: 1, year: 2017 }, e); expect(r).toEqual({ period: 'month', @@ -212,7 +212,7 @@ describe('Time', () => { }); it('month after current month with year', () => { - const r = map({ month: 6, year: 2017 }, e); + const r = mapDate({ month: 6, year: 2017 }, e); expect(r).toEqual({ period: 'month', @@ -223,7 +223,7 @@ describe('Time', () => { }); it('day after the current day is same month', () => { - const r = map({ day: 28 }, e); + const r = mapDate({ day: 28 }, e); expect(r).toEqual({ period: 'day', @@ -234,7 +234,7 @@ describe('Time', () => { }); it('day before the current day is next month', () => { - const r = map({ day: 2 }, e); + const r = mapDate({ day: 2 }, e); expect(r).toEqual({ period: 'day', @@ -245,7 +245,7 @@ describe('Time', () => { }); it('relativeYears and relativeMonths keeps day', () => { - const r = map({ relativeYears: 1, relativeMonths: 2 }, e); + const r = mapDate({ relativeYears: 1, relativeMonths: 2 }, e); expect(r).toEqual({ period: 'month', @@ -256,7 +256,7 @@ describe('Time', () => { }); it('relativeYears, relativeMonths and relativeDays', () => { - const r = map({ relativeYears: 1, relativeMonths: 2 }, e); + const r = mapDate({ relativeYears: 1, relativeMonths: 2 }, e); expect(r).toEqual({ period: 'month', @@ -267,7 +267,7 @@ describe('Time', () => { }); it('year, month and day', () => { - const r = map({ year: 2019, month: 1, day: 2 }, e); + const r = mapDate({ year: 2019, month: 1, day: 2 }, e); expect(r).toEqual({ period: 'day', diff --git a/test/time/months.test.ts b/test/time/months.test.ts index 04b99bc..2f310db 100644 --- a/test/time/months.test.ts +++ b/test/time/months.test.ts @@ -1,8 +1,8 @@ -import { map } from '../../src/time/months'; +import { mapMonth } from '../../src/time/months'; describe('Time', () => { describe('months', () => { - describe('map', () => { + describe('mapMonth', () => { const e = { options: { now: new Date(2017, 2, 24) @@ -10,7 +10,7 @@ describe('Time', () => { }; it('relativeMonths resolves to current year and month', () => { - const r = map({ relativeMonths: 0 }, e); + const r = mapMonth({ relativeMonths: 0 }, e); expect(r).toEqual({ period: 'month', @@ -21,7 +21,7 @@ describe('Time', () => { }); it('relativeMonths resolves to current year and +2 months', () => { - const r = map({ relativeMonths: 2 }, e); + const r = mapMonth({ relativeMonths: 2 }, e); expect(r).toEqual({ period: 'month', @@ -32,7 +32,7 @@ describe('Time', () => { }); it('relativeMonths resolves to current year and -2 months', () => { - const r = map({ relativeMonths: -2 }, e); + const r = mapMonth({ relativeMonths: -2 }, e); expect(r).toEqual({ period: 'month', @@ -43,7 +43,7 @@ describe('Time', () => { }); it('relativeMonths resolves to previous year', () => { - const r = map({ relativeMonths: -4 }, e); + const r = mapMonth({ relativeMonths: -4 }, e); expect(r).toEqual({ period: 'month', @@ -54,7 +54,7 @@ describe('Time', () => { }); it('Absolute month', () => { - const r = map({ month: 5 }, e); + const r = mapMonth({ month: 5 }, e); expect(r).toEqual({ period: 'month', diff --git a/test/time/times.test.ts b/test/time/times.test.ts index 98a5ecd..93791c9 100644 --- a/test/time/times.test.ts +++ b/test/time/times.test.ts @@ -1,5 +1,5 @@ -import { time12h, time24h, map } from '../../src/time/times'; -import { Meridiem } from '../../src/time/meridiem'; +import { time12h, time24h, mapTime } from '../../src/time/times'; +import { Meridiem } from '../../src/time/Meridiem'; describe('Time', () => { describe('times', () => { @@ -105,7 +105,7 @@ describe('Time', () => { }; it('Hour 14 with fixed meridiem', () => { - const r = map({ hour: 15, meridiem: Meridiem.Fixed }, defaultE); + const r = mapTime({ hour: 15, meridiem: Meridiem.Fixed }, defaultE); expect(r).toEqual({ period: 'hour', @@ -118,7 +118,7 @@ describe('Time', () => { }); it('Hour 8 with fixed meridiem', () => { - const r = map({ hour: 8, meridiem: Meridiem.Fixed }, defaultE); + const r = mapTime({ hour: 8, meridiem: Meridiem.Fixed }, defaultE); expect(r).toEqual({ period: 'hour', @@ -131,7 +131,7 @@ describe('Time', () => { }); it('Hour 8 with am meridiem', () => { - const r = map({ hour: 8, meridiem: Meridiem.Am }, defaultE); + const r = mapTime({ hour: 8, meridiem: Meridiem.Am }, defaultE); expect(r).toEqual({ period: 'hour', @@ -144,7 +144,7 @@ describe('Time', () => { }); it('Hour 12 with am meridiem', () => { - const r = map({ hour: 12, meridiem: Meridiem.Am }, defaultE); + const r = mapTime({ hour: 12, meridiem: Meridiem.Am }, defaultE); expect(r).toEqual({ period: 'hour', @@ -157,7 +157,7 @@ describe('Time', () => { }); it('Hour 8 with pm meridiem', () => { - const r = map({ hour: 8, meridiem: Meridiem.Pm }, defaultE); + const r = mapTime({ hour: 8, meridiem: Meridiem.Pm }, defaultE); expect(r).toEqual({ period: 'hour', @@ -170,7 +170,7 @@ describe('Time', () => { }); it('Hour 12 with pm meridiem', () => { - const r = map({ hour: 12, meridiem: Meridiem.Pm }, defaultE); + const r = mapTime({ hour: 12, meridiem: Meridiem.Pm }, defaultE); expect(r).toEqual({ period: 'hour', @@ -188,7 +188,7 @@ describe('Time', () => { now: new Date(2010, 1, 6, 10, 0) } }; - const r = map({ hour: 8, meridiem: Meridiem.Auto }, e); + const r = mapTime({ hour: 8, meridiem: Meridiem.Auto }, e); expect(r).toEqual({ period: 'hour', @@ -206,7 +206,7 @@ describe('Time', () => { now: new Date(2010, 1, 6, 4, 0) } }; - const r = map({ hour: 8, meridiem: Meridiem.Auto }, e); + const r = mapTime({ hour: 8, meridiem: Meridiem.Auto }, e); expect(r).toEqual({ period: 'hour', @@ -224,7 +224,7 @@ describe('Time', () => { now: new Date(2010, 1, 6, 16, 0) } }; - const r = map({ hour: 8, meridiem: Meridiem.Auto }, e); + const r = mapTime({ hour: 8, meridiem: Meridiem.Auto }, e); expect(r).toEqual({ period: 'hour', @@ -242,7 +242,7 @@ describe('Time', () => { now: new Date(2010, 1, 6, 4, 0) } }; - const r = map({ hour: 12, meridiem: Meridiem.Auto }, e); + const r = mapTime({ hour: 12, meridiem: Meridiem.Auto }, e); expect(r).toEqual({ period: 'hour', @@ -260,7 +260,7 @@ describe('Time', () => { now: new Date(2010, 1, 6, 16, 0) } }; - const r = map({ hour: 12, meridiem: Meridiem.Auto }, e); + const r = mapTime({ hour: 12, meridiem: Meridiem.Auto }, e); expect(r).toEqual({ period: 'hour', diff --git a/test/time/years.test.ts b/test/time/years.test.ts index 321ffd8..059278f 100644 --- a/test/time/years.test.ts +++ b/test/time/years.test.ts @@ -1,4 +1,4 @@ -import { map } from '../../src/time/years'; +import { mapYear } from '../../src/time/years'; describe('Time', () => { describe('years', () => { @@ -10,7 +10,7 @@ describe('Time', () => { }; it('relativeYears resolves to current year', () => { - const r = map({ relativeYears: 0 }, e); + const r = mapYear({ relativeYears: 0 }, e); expect(r).toEqual({ period: 'year', @@ -21,7 +21,7 @@ describe('Time', () => { }); it('relativeYears resolves positive years', () => { - const r = map({ relativeYears: 2 }, e); + const r = mapYear({ relativeYears: 2 }, e); expect(r).toEqual({ period: 'year', @@ -32,7 +32,7 @@ describe('Time', () => { }); it('relativeYears resolves negative years', () => { - const r = map({ relativeYears: -2 }, e); + const r = mapYear({ relativeYears: -2 }, e); expect(r).toEqual({ period: 'year', @@ -43,7 +43,7 @@ describe('Time', () => { }); it('Absolute year', () => { - const r = map({ year: 2015 }, e); + const r = mapYear({ year: 2015 }, e); expect(r).toEqual({ period: 'year', diff --git a/test/value-custom.test.ts b/test/value-custom.test.ts index 42d4c27..8ce3b86 100644 --- a/test/value-custom.test.ts +++ b/test/value-custom.test.ts @@ -21,7 +21,7 @@ describe('Value: Custom', function() { .value('company', customValue(match)) .add('{company}') .add('{company} company') - .build(); + .toMatcher(); it('Invalid company', function() { return resolver.match('ABC') @@ -34,7 +34,7 @@ describe('Value: Custom', function() { return resolver.match('Balloons') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Balloons'); + expect(results.best.values.company).toEqual('Balloons'); }); }); @@ -42,7 +42,7 @@ describe('Value: Custom', function() { return resolver.match('Cookie Co') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Cookie Co'); + expect(results.best.values.company).toEqual('Cookie Co'); }); }); @@ -50,7 +50,7 @@ describe('Value: Custom', function() { return resolver.match('Cookie Co company') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Cookie Co'); + expect(results.best.values.company).toEqual('Cookie Co'); }); }); }); @@ -60,7 +60,7 @@ describe('Value: Custom', function() { .value('company', customValue(match)) .add('{company}') .add('{company} company') - .build(); + .toMatcher(); it('Invalid company', function() { return resolver.match('A', { partial: true }) @@ -73,7 +73,7 @@ describe('Value: Custom', function() { return resolver.match('Ba', { partial: true }) .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Balloons'); + expect(results.best.values.company).toEqual('Balloons'); }); }); @@ -81,7 +81,7 @@ describe('Value: Custom', function() { return resolver.match('C', { partial: true }) .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Cookie Co'); + expect(results.best.values.company).toEqual('Cookie Co'); }); }); @@ -89,7 +89,7 @@ describe('Value: Custom', function() { return resolver.match('Cookie company', { partial: true }) .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Cookie Co'); + expect(results.best.values.company).toEqual('Cookie Co'); }); }); diff --git a/test/value-options.test.ts b/test/value-options.test.ts index 7e2fd0c..806c9fb 100644 --- a/test/value-options.test.ts +++ b/test/value-options.test.ts @@ -5,7 +5,6 @@ import { optionsValue, dateIntervalValue, enumerationValue, customValue } from ' describe('Value: Options', function() { - /* describe('Standalone option: No values', () => { const matcher = optionsValue() .option('deadline') @@ -14,16 +13,17 @@ describe('Value: Options', function() { .build() .matcher(en); - it('with deadline [partial=false]', () => matcher('with deadline') + it('with deadline [partial=false]', () => matcher.match('with deadline') .then(v => { - expect(v).toEqual([ + expect(v.toArray()).toEqual([ { option: 'deadline', values: {}, } ]); - expect(v[0].expression).toEqual([ + const option = v.get('deadline'); + expect(option.expression).toEqual([ { type: 'text', value: 'with deadline', @@ -33,16 +33,17 @@ describe('Value: Options', function() { }) ); - it('with deadline [partial=true]', () => matcher('with deadline', { partial: true }) + it('with deadline [partial=true]', () => matcher.match('with deadline', { partial: true }) .then(v => { - expect(v).toEqual([ + expect(v.toArray()).toEqual([ { option: 'deadline', values: {}, } ]); - expect(v[0].expression).toEqual([ + const option = v.get('deadline'); + expect(option.expression).toEqual([ { type: 'text', value: 'with deadline', @@ -52,16 +53,17 @@ describe('Value: Options', function() { }) ); - it('with [partial=true]', () => matcher('with', { partial: true }) + it('with [partial=true]', () => matcher.match('with', { partial: true }) .then(v => { - expect(v).toEqual([ + expect(v.toArray()).toEqual([ { option: 'deadline', values: {}, } ]); - expect(v[0].expression).toEqual([ + const option = v.get('deadline'); + expect(option.expression).toEqual([ { type: 'text', value: 'with deadline', @@ -71,16 +73,17 @@ describe('Value: Options', function() { }) ); - it('with d [partial=true]', () => matcher('with d', { partial: true }) + it('with d [partial=true]', () => matcher.match('with d', { partial: true }) .then(v => { - expect(v).toEqual([ + expect(v.toArray()).toEqual([ { option: 'deadline', values: {}, } ]); - expect(v[0].expression).toEqual([ + const option = v.get('deadline'); + expect(option.expression).toEqual([ { type: 'text', value: 'with deadline', @@ -90,9 +93,9 @@ describe('Value: Options', function() { }) ); - it('with deadline and wi [partial=true]', () => matcher('with deadline and wi', { partial: true }) + it('with deadline and wi [partial=true]', () => matcher.match('with deadline and wi', { partial: true }) .then(v => { - expect(v).toEqual([ + expect(v.toArray()).toEqual([ { option: 'deadline', values: {}, @@ -103,7 +106,9 @@ describe('Value: Options', function() { } ]); - expect(v[0].expression).toEqual([ + const options = v.getAll('deadline'); + + expect(options[0].expression).toEqual([ { type: 'text', value: 'with deadline', @@ -111,7 +116,7 @@ describe('Value: Options', function() { } ]); - expect(v[1].expression).toEqual([ + expect(options[1].expression).toEqual([ { type: 'text', value: 'with deadline', @@ -131,29 +136,30 @@ describe('Value: Options', function() { .build() .matcher(en); - it('with deadline [partial=false]', () => matcher('with deadline jan 12th', { now: new Date(2010, 0, 1) }) + it('with deadline [partial=false]', () => matcher.match('with deadline jan 12th', { now: new Date(2010, 0, 1) }) .then(v => { expect(v).not.toBeNull(); - expect(v.length).toEqual(1); - const a = v[0]; - expect(a.option).toEqual('deadline'); - expect(a.values.deadline).toEqual({ + const option = v.get('deadline'); + expect(option).not.toBeNull(); + expect(option.option).toEqual('deadline'); + expect(option.values.deadline).toEqual({ start: { period: 'day', year: 2010, month: 0, day: 12 }, end: { period: 'day', year: 2010, month: 0, day: 12 } }); }) ); - it('with deadline [partial=true]', () => matcher('with deadline', { partial: true }) + it('with deadline [partial=true]', () => matcher.match('with deadline', { partial: true }) .then(v => { expect(v).not.toBeNull(); - expect(v.length).toEqual(1); + const option = v.get('deadline'); + expect(option).not.toBeNull(); + expect(option.option).toEqual('deadline'); }) ); }); - */ describe('Single option - no value', () => { const queryOptions = optionsValue() @@ -165,17 +171,16 @@ describe('Value: Options', function() { const resolver = new ResolverBuilder(en) .value('queryOptions', queryOptions) .add('Things {queryOptions}') - .build(); + .toMatcher(); it('Full match', () => { return resolver.match('things with deadline', { partial: false }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('deadline'); expect(v.option).toEqual('deadline'); - expect(v.values).toEqual(new Map()); + expect(v.values).toEqual({}); }); }); @@ -183,11 +188,10 @@ describe('Value: Options', function() { return resolver.match('things with d', { partial: true }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('deadline'); expect(v.option).toEqual('deadline'); - expect(v.values).toEqual(new Map()); + expect(v.values).toEqual({}); }); }); }); @@ -208,17 +212,16 @@ describe('Value: Options', function() { const resolver = new ResolverBuilder(en) .value('queryOptions', queryOptions) .add('Things {queryOptions}') - .build(); + .toMatcher(); it('Full match', () => { return resolver.match('things with deadline jan 12th', { now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('deadline'); expect(v.option).toEqual('deadline'); - expect(v.values.get('deadline')).toEqual({ + expect(v.values.deadline).toEqual({ start: { period: 'day', year: 2018, month: 0, day: 12 }, end: { period: 'day', year: 2018, month: 0, day: 12 } }); @@ -229,18 +232,17 @@ describe('Value: Options', function() { return resolver.match('things with deadline jan 12th and completed today', { now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v0 = r.best.values.get('queryOptions')[0]; + const v0 = r.best.values.queryOptions.get('deadline'); expect(v0.option).toEqual('deadline'); - expect(v0.values.get('deadline')).toEqual({ + expect(v0.values.deadline).toEqual({ start: { period: 'day', year: 2018, month: 0, day: 12 }, end: { period: 'day', year: 2018, month: 0, day: 12 } }); - const v1 = r.best.values.get('queryOptions')[1]; + const v1 = r.best.values.queryOptions.get('completed'); expect(v1.option).toEqual('completed'); - expect(v1.values.get('completed')).toEqual({ + expect(v1.values.completed).toEqual({ start: { period: 'day', year: 2018, month: 0, day: 2 }, end: { period: 'day', year: 2018, month: 0, day: 2 } }); @@ -258,7 +260,7 @@ describe('Value: Options', function() { const queryOptions = optionsValue() .option('value') - .value('name', customValue(async function(encounter) { + .value('name', customValue(async function(encounter) { let text = encounter.text; if(encounter.partial) { for(const v of values) { @@ -284,23 +286,22 @@ describe('Value: Options', function() { const resolver = new ResolverBuilder(en) .value('queryOptions', queryOptions) .add('Things {queryOptions}') - .build(); + .toMatcher(); const resolver2 = new ResolverBuilder(en) .value('enum', enumerationValue([ 'test', 'abc' ])) .value('queryOptions', queryOptions) .add('{enum} {queryOptions}') - .build(); + .toMatcher(); it('Full match', () => { return resolver.match('things named one', { now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('value'); expect(v.option).toEqual('value'); - expect(v.values.get('name')).toEqual('one'); + expect(v.values.name).toEqual('one'); }); }); @@ -308,12 +309,11 @@ describe('Value: Options', function() { return resolver.match('thing', { partial: true, now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('value'); expect(v.option).toEqual('value'); - const v2 = r.matches[1].values.get('queryOptions')[0]; + const v2 = r.matches[1].values.queryOptions.get('completed'); expect(v2.option).toEqual('completed'); }); }); @@ -322,9 +322,8 @@ describe('Value: Options', function() { return resolver.match('thing com', { partial: true, now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('completed'); expect(v.option).toEqual('completed'); }); }); @@ -333,9 +332,8 @@ describe('Value: Options', function() { return resolver.match('thing completed ja', { partial: true, now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('completed'); expect(v.option).toEqual('completed'); }); }); @@ -344,11 +342,10 @@ describe('Value: Options', function() { return resolver2.match('test named one', { now: new Date(2018, 0, 2) }) .then(r => { expect(r.best).not.toBeNull(); - expect(r.best.values.get('queryOptions')).toBeInstanceOf(Array); - const v = r.best.values.get('queryOptions')[0]; + const v = r.best.values.queryOptions.get('value'); expect(v.option).toEqual('value'); - expect(v.values.get('name')).toEqual('one'); + expect(v.values.name).toEqual('one'); }); }); @@ -366,10 +363,10 @@ describe('Value: Options', function() { expect(r.best).not.toBeNull(); expect(r.matches.length).toEqual(1); - expect(r.best.values.get('queryOptions')).toEqual([ + expect(r.best.values.queryOptions.toArray()).toEqual([ { option: 'value', - values: new Map() + values: {} } ]); }); diff --git a/test/value.enum.test.ts b/test/value.enum.test.ts index 9fa31e8..c3bc64b 100644 --- a/test/value.enum.test.ts +++ b/test/value.enum.test.ts @@ -9,7 +9,7 @@ describe('Value: Enumeration', function() { .value('company', enumerationValue([ 'Balloons', 'Cookie Co', 'Banana Inc' ])) .add('{company} orders') .add('orders for {company}') - .build(); + .toMatcher(); it('Invalid company', function() { return resolver.match('orders for ABC') @@ -22,7 +22,7 @@ describe('Value: Enumeration', function() { return resolver.match('orders for Ballons') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Balloons'); + expect(results.best.values.company).toEqual('Balloons'); }); }); @@ -30,7 +30,7 @@ describe('Value: Enumeration', function() { return resolver.match('orders for Cookie Co') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Cookie Co'); + expect(results.best.values.company).toEqual('Cookie Co'); }); }); }); @@ -44,7 +44,7 @@ describe('Value: Enumeration', function() { ], v => v.name)) .add('{company} orders') .add('orders for {company}') - .build(); + .toMatcher(); it('Invalid company', function() { return resolver.match('orders for ABC') @@ -57,7 +57,7 @@ describe('Value: Enumeration', function() { return resolver.match('orders for Ballons') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual({ + expect(results.best.values.company).toEqual({ name: 'Balloons' }); }); @@ -67,7 +67,7 @@ describe('Value: Enumeration', function() { return resolver.match('orders for Cookie Co') .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual({ + expect(results.best.values.company).toEqual({ name: 'Cookie Co' }); }); @@ -79,14 +79,13 @@ describe('Value: Enumeration', function() { .value('company', enumerationValue([ 'Balloons', 'Cookie Co', 'Banana Inc' ])) .add('{company} orders') .add('orders for {company}') - .build(); - + .toMatcher(); it('Single token', function() { return resolver.match('orders ', { partial: true }) .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toBeUndefined(); + expect(results.best.values.company).toBeUndefined(); }); }); @@ -120,7 +119,7 @@ describe('Value: Enumeration', function() { }) .then(results => { expect(results.matches.length).toEqual(1); - expect(results.best.values.get('company')).toEqual('Cookie Co'); + expect(results.best.values.company).toEqual('Cookie Co'); const expr = results.best.expression; expect(expr[expr.length - 1]).toEqual({