Skip to content

Commit

Permalink
refactor: Always switch punctuation, fuzzy and partial modes for sub …
Browse files Browse the repository at this point in the history
…graphs
  • Loading branch information
aholstenson committed Oct 20, 2018
1 parent 3f14e43 commit b37f557
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 93 deletions.
82 changes: 45 additions & 37 deletions src/graph/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ export default class GraphBuilder extends Node {

this.language = language;

this.supportsPartial = false;
this._skipPunctuation = false;
this._fuzzy = false;
this.options = {
supportsPartial: false,
skipPunctuation: false,
fuzzy: false
};
}

/**
Expand All @@ -32,7 +34,7 @@ export default class GraphBuilder extends Node {
* self
*/
name(name) {
this._name = this.language.id + ':' + name;
this.options.name = this.language.id + ':' + name;
return this;
}

Expand All @@ -42,8 +44,8 @@ export default class GraphBuilder extends Node {
* @return
* self
*/
allowPartial() {
this.supportsPartial = true;
allowPartial(active=true) {
this.options.supportsPartial = active;
return this;
}

Expand All @@ -53,8 +55,8 @@ export default class GraphBuilder extends Node {
* @return
* self
*/
skipPunctuation() {
this._skipPunctuation = true;
skipPunctuation(active=true) {
this.options.skipPunctuation = active;
return this;
}

Expand All @@ -64,8 +66,8 @@ export default class GraphBuilder extends Node {
* @return
* self
*/
fuzzy() {
this._fuzzy = true;
fuzzy(active=true) {
this.options.active = active;
return this;
}

Expand Down Expand Up @@ -132,7 +134,7 @@ export default class GraphBuilder extends Node {
} else if(n instanceof RegExp) {
return push(new RegExpNode(n));
} else if(n instanceof Matcher) {
return push(new SubNode(n));
return push(new SubNode(n, n.options));
} else if(n instanceof Node) {
return push(n);
} else if(typeof n === 'string') {
Expand Down Expand Up @@ -176,62 +178,68 @@ export default class GraphBuilder extends Node {
}

mapResults(mapper) {
this._mapper = mapper;
this.options.mapper = mapper;

return this;
}

finalizer(func) {
if(this._finalizer) {
const previous = this._finalizer;
this._finalizer = function(results, encounter) {
if(this.options.finalizer) {
// Chain finalizer if several are requested
const previous = this.options.finalizer;
this.options.finalizer = function(results, encounter) {
return func(previous(results, encounter));
};
} else {
this._finalizer = func;
this.options.finalizer = func;
}
return this;
}

onlyBest() {
return this.finalizer((results, encounter) => {
let data = results[0] ? results[0].data : null;
if(data && this._mapper) {
data = this._mapper(data, encounter);
if(data && this.options.mapper) {
data = this.options.mapper(data, encounter);
}
return data;
});
}

/**
* Build this graph and turn it into a matcher.
*/
toMatcher() {
return this.createMatcher(this.language, this.outgoing, {
name: this._name,
fuzzy: this._fuzzy,
supportsPartial: this.supportsPartial,
skipPunctuation: this._skipPunctuation,
mapper: this._mapper,
finalizer: this._finalizer
});
return this.createMatcher(this.language, this.outgoing, this.options);
}

createMatcher(lang, nodes, options) {
return new Matcher(lang, nodes, options);
}

static result(node, validator) {
static result(matcher, validator) {
if(typeof validator === 'undefined') {
validator = node;
node = null;
if(typeof matcher === 'function') {
validator = matcher;
matcher = null;
} else if(matcher) {
throw new Error('Expected graph or a validation function, got ' + matcher);
}
}

if(matcher && ! (matcher instanceof Matcher)) {
throw new Error('matcher is not actually an instance of Matcher');
}

return function(builder) {
const sub = new SubNode(node ? node : builder.outgoing, validator);
if(validator) {
let name = node && node.name;
if(typeof name === 'function') {
name = node._name;
}
const sub = matcher
? new SubNode(matcher, matcher.options, validator)
: new SubNode(builder.outgoing, builder.options, validator);

sub.recursive = ! matcher;

if(validator) {
let name = matcher ? matcher.options.name : builder.options.name;
if(validator.name) {
if(name) {
name += ' - ' + validator.name;
Expand All @@ -240,8 +248,8 @@ export default class GraphBuilder extends Node {
}
}
sub.name = name;
} else {
sub.name = builder._name + ':self';
} else if(! matcher) {
sub.name = builder.options.name + ':self';
}
return sub;
};
Expand Down
4 changes: 3 additions & 1 deletion src/graph/matching/encounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export default class Encounter {
});
this.maxDepth = 0;

this.partial = options.partial || false;
this.initialPartial = this.partial = options.partial || false;
this.onMatch = options.onMatch;
this.verbose = options.verbose;
this.onlyComplete = options.onlyComplete || false;
this.skipPunctuation = options.skipPunctuation || false;
this.fuzzy = options.fuzzy || false;

this.options = options;

Expand Down
6 changes: 6 additions & 0 deletions src/graph/matching/match.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ export default class Match {
this.data = data;
}

isPartialData() {
return this.data === Match.PARTIAL;
}

copy() {
return new Match(this.index, this.score, clone(this.data));
}
}

Match.PARTIAL = '##PARTIAL##';
21 changes: 4 additions & 17 deletions src/graph/matching/matcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ export default class Matcher {
constructor(language, nodes, options={}) {
this.language = language;
this.nodes = nodes;
this.name = options.name;
this.fuzzy = options.fuzzy || false;
this.skipPunctuation = options.skipPunctuation || false;
this.finalizer = options.finalizer || null;
this.supportsPartial = options.supportsPartial || false;
this.mapper = options.mapper || null;

this.options = options;
this._cache = {};
}

Expand All @@ -31,24 +26,16 @@ export default class Matcher {

const encounter = new Encounter(this.language, expression, Object.assign({
onlyComplete: true
}, options));
}, this.options, options));
encounter.outgoing = this.nodes;

if(this.skipPunctuation) {
encounter.skipPunctuation = true;
}

if(this.fuzzy) {
encounter.fuzzy = true;
}

let promise = encounter.next(0, 0)
.then(() => {
return encounter.matches.toArray();
});

if(this.finalizer) {
promise = promise.then(results => this.finalizer(results, encounter));
if(this.options.finalizer) {
promise = promise.then(results => this.options.finalizer(results, encounter));
}

return promise;
Expand Down
60 changes: 27 additions & 33 deletions src/graph/sub.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Node from './node';
import Matcher from './matching/matcher';
import Match from './matching/match';

const ALWAYS_TRUE = () => true;

Expand All @@ -22,26 +23,24 @@ const PARSER_PENALTY = 0.001;
* once starting from `T3` and once from `T4`.
*/
export default class SubNode extends Node {
constructor(roots, filter) {
constructor(roots, options, filter) {
super();

this.filter = filter || ALWAYS_TRUE;

if(roots instanceof Matcher) {
// Roots is actually a matcher, copy the graph from the matcher
this.roots = roots.nodes;
this.supportsPartial = typeof roots.supportsPartial !== 'undefined' ? roots.supportsPartial : null;
this.name = roots.name || null;
this.skipPunctuation = typeof roots.skipPunctuation !== 'undefined' ? roots.skipPunctuation : null;
this.fuzzy = typeof roots.fuzzy !== 'undefined' ? roots.fuzzy : null;
this.state = roots._cache;
} else {
this.roots = roots;
this.state = this;
this.supportsPartial = null;
this.fuzzy = null;
this.skipPunctuation = null;
this.state = options.state || this;
}

this.supportsPartial = options.supportsPartial || false;
this.name = options.name || null;
this.skipPunctuation = options.skipPunctuation || false;
this.fuzzy = options.fuzzy || false;
}

match(encounter) {
Expand All @@ -53,21 +52,22 @@ export default class SubNode extends Node {
return;
}

if(! encounter.token()) {
if(encounter.partial) {
if(! this.supportsPartial) {
/**
* Partial match for nothing without support for it. Assume
* we will match in the future.
*/
return encounter.next(1.0, 0);
}
} else if(this.supportsPartial) {
if(! encounter.token() && encounter.initialPartial) {
if(this.recursive) {
/**
* No tokens means we can't match.
* If this evaluating a recursive match on a partial encounter
* skip it.
*/
return;
}

if(! this.supportsPartial) {
/**
* Partial match for nothing without support for it. Assume
* we will match in the future.
*/
return encounter.match(Match.PARTIAL);
}
}

// Set the index we were called at
Expand All @@ -79,7 +79,9 @@ export default class SubNode extends Node {
let promise = Promise.resolve();
for(let i=0; i<variants0.length; i++) {
const v = variants0[i];
if(! this.filter(v.data)) continue;
if(v.data !== Match.PARTIAL && ! this.filter(v.data)) {
continue;
}

promise = promise.then(() => {
return encounter.next(
Expand All @@ -104,7 +106,7 @@ export default class SubNode extends Node {

const onMatch = match => {
let result = match.data;
if(this.mapper && result !== null && typeof result !== 'undefined') {
if(this.mapper && ! match.isPartialData() && result !== null && typeof result !== 'undefined') {
result = this.mapper(result, encounter);
}

Expand All @@ -131,17 +133,9 @@ export default class SubNode extends Node {
const fuzzy = encounter.fuzzy;

return encounter.branchWithOnMatch(onMatch, () => {
if(partial && this.supportsPartial !== null) {
encounter.partial = this.supportsPartial;
}

if(this.skipPunctuation !== null) {
encounter.skipPunctuation = this.skipPunctuation;
}

if(this.fuzzy !== null) {
encounter.fuzzy = this.fuzzy;
}
encounter.partial = encounter.initialPartial && this.supportsPartial;
encounter.skipPunctuation = this.skipPunctuation;
encounter.fuzzy = this.fuzzy;

return encounter.next(this.roots);
}).then(() => {
Expand Down
16 changes: 13 additions & 3 deletions src/resolver/value-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ export default class ValueParser extends Node {
super();

this.id = id;
this.node = new SubNode(matcher);
this.node = new SubNode(matcher, matcher.options);
this.options = options;

const mapper = matcher.mapper;
/*
* Make sure that the result of evaluating this sub-graph is mapped
* using the same mapper as would be used if graph is directly matched
* on.
*/
const mapper = matcher.options.mapper;
this.node.mapper = (r, encounter) => {
r = mapper ? mapper(r, encounter) : r;
if(mapper) {
// Perform the mapping using the graphs mapper
r = mapper(r, encounter);
}

// Map it into a value format
return {
id: id,
value: options.mapper ? options.mapper(r) : r
Expand Down
2 changes: 1 addition & 1 deletion test/builder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe('Graph Builder', function() {
return parser.match('one two', { partial: true })
.then(map)
.then(results => {
expect(results).to.deep.equal([ 4, 2 ]);
expect(results).to.deep.equal([ 2 ]);
});
});

Expand Down
Loading

0 comments on commit b37f557

Please sign in to comment.