Skip to content

Commit 7690e82

Browse files
committed
Add defmatchAsync. Improve defmatch functions by checking arities ahead of time
1 parent c37d84f commit 7690e82

9 files changed

Lines changed: 1239 additions & 199 deletions

File tree

.babelrc

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
{
2-
"plugins": ["transform-es2015-modules-commonjs"]
2+
"presets": [
3+
[
4+
"env",
5+
{
6+
"targets": {
7+
"node": "current"
8+
},
9+
"modules": false,
10+
"loose": true
11+
}
12+
]
13+
]
314
}

lib/tailored.js

Lines changed: 83 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tailored",
3-
"version": "2.3.2",
3+
"version": "2.4.0",
44
"description": "Pattern matching library",
55
"main": "lib/tailored.js",
66
"jsnext:main": "src/index.js",
@@ -19,13 +19,12 @@
1919
"author": "Bryan Joseph <bryanjos@gmail.com>",
2020
"license": "MIT",
2121
"devDependencies": {
22-
"babel-core": "^6.9.1",
23-
"babel-preset-es2015": "^6.9.0",
24-
"babel-preset-es2015-rollup": "^1.1.1",
22+
"babel-core": "^6.24.0",
23+
"babel-preset-env": "^1.3.2",
2524
"chai": "^3.5.0",
26-
"mocha": "^2.5.3",
27-
"rollup": "^0.32.0",
28-
"rollup-plugin-babel": "^2.5.1"
25+
"mocha": "^3.2.0",
26+
"rollup": "^0.41.0",
27+
"rollup-plugin-babel": "^2.7.1"
2928
},
3029
"dependencies": {
3130
"erlang-types": "^1.0.0"

rollup.config.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ export default {
66
dest: 'lib/tailored.js',
77
sourceMap: 'inline',
88
format: 'cjs',
9-
plugins: [
10-
babel({
11-
babelrc: false
12-
})
13-
]
9+
plugins: [babel()],
10+
external: ['erlang-types'],
1411
};

src/index.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
clause,
77
match_or_default,
88
defmatchgen,
9-
trampoline
10-
} from "./tailored/defmatch";
9+
defmatchGen,
10+
defmatchAsync,
11+
} from './tailored/defmatch';
1112
import {
1213
variable,
1314
wildcard,
@@ -16,15 +17,15 @@ import {
1617
headTail,
1718
type,
1819
bound,
19-
bitStringMatch
20-
} from "./tailored/types";
20+
bitStringMatch,
21+
} from './tailored/types';
2122

2223
import {
2324
list_generator,
2425
list_comprehension,
2526
bitstring_generator,
26-
bitstring_comprehension
27-
} from "./tailored/comprehensions";
27+
bitstring_comprehension,
28+
} from './tailored/comprehensions';
2829

2930
export default {
3031
defmatch,
@@ -46,5 +47,6 @@ export default {
4647
list_generator,
4748
bitstring_generator,
4849
bitstring_comprehension,
49-
trampoline
50+
defmatchGen,
51+
defmatchAsync,
5052
};

src/tailored/defmatch.js

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { buildMatch } from "./match";
2-
import * as Types from "./types";
1+
import { buildMatch } from './match';
2+
import * as Types from './types';
33

44
const FUNC = Symbol();
55

66
export class MatchError extends Error {
77
constructor(arg) {
88
super();
99

10-
if (typeof arg === "symbol") {
11-
this.message = "No match for: " + arg.toString();
10+
if (typeof arg === 'symbol') {
11+
this.message = 'No match for: ' + arg.toString();
1212
} else if (Array.isArray(arg)) {
1313
let mappedValues = arg.map(x => x.toString());
14-
this.message = "No match for: " + mappedValues;
14+
this.message = 'No match for: ' + mappedValues;
1515
} else {
16-
this.message = "No match for: " + arg;
16+
this.message = 'No match for: ' + arg;
1717
}
1818

1919
this.stack = new Error().stack;
@@ -46,15 +46,48 @@ export function trampoline(fn) {
4646
}
4747

4848
export function defmatch(...clauses) {
49+
const arities = getArityMap(clauses);
50+
4951
return function(...args) {
52+
let [funcToCall, params] = findMatchingFunction(args, arities);
53+
return funcToCall.apply(this, params);
54+
};
55+
}
56+
57+
export function defmatchgen(...clauses) {
58+
const arities = getArityMap(clauses);
59+
60+
return function*(...args) {
61+
let [funcToCall, params] = findMatchingFunction(args, arities);
62+
return yield* funcToCall.apply(this, params);
63+
};
64+
}
65+
66+
export function defmatchGen(...args) {
67+
return defmatchgen(...args);
68+
}
69+
70+
export function defmatchAsync(...clauses) {
71+
const arities = getArityMap(clauses);
72+
73+
return async function(...args) {
74+
let [funcToCall, params] = findMatchingFunction(args, arities);
75+
return funcToCall.apply(this, params);
76+
};
77+
}
78+
79+
function findMatchingFunction(args, arities) {
80+
if (arities.has(args.length)) {
81+
const arityClauses = arities.get(args.length);
82+
5083
let funcToCall = null;
5184
let params = null;
52-
for (let processedClause of clauses) {
85+
for (let processedClause of arityClauses) {
5386
let result = [];
5487
args = fillInOptionalValues(
5588
args,
5689
processedClause.arity,
57-
processedClause.optionals
90+
processedClause.optionals,
5891
);
5992

6093
if (
@@ -68,35 +101,49 @@ export function defmatch(...clauses) {
68101
}
69102

70103
if (!funcToCall) {
71-
console.error("No match for:", args);
104+
console.error('No match for:', args);
72105
throw new MatchError(args);
73106
}
74107

75-
return funcToCall.apply(this, params);
76-
};
108+
return [funcToCall, params];
109+
} else {
110+
console.error('Arity of', args.length, 'not found. No match for:', args);
111+
throw new MatchError(args);
112+
}
77113
}
78114

79-
export function defmatchgen(...clauses) {
80-
return function*(...args) {
81-
for (let processedClause of clauses) {
82-
let result = [];
83-
args = fillInOptionalValues(
84-
args,
85-
processedClause.arity,
86-
processedClause.optionals
87-
);
115+
function getArityMap(clauses) {
116+
let map = new Map();
88117

89-
if (
90-
processedClause.pattern(args, result) &&
91-
processedClause.guard.apply(this, result)
92-
) {
93-
return yield* processedClause.fn.apply(this, result);
118+
for (const clause of clauses) {
119+
const range = getArityRange(clause);
120+
121+
for (const arity of range) {
122+
let arityClauses = [];
123+
124+
if (map.has(arity)) {
125+
arityClauses = map.get(arity);
94126
}
127+
128+
arityClauses.push(clause);
129+
map.set(arity, arityClauses);
95130
}
131+
}
96132

97-
console.error("No match for:", args);
98-
throw new MatchError(args);
99-
};
133+
return map;
134+
}
135+
136+
function getArityRange(clause) {
137+
const min = clause.arity - clause.optionals.length;
138+
const max = clause.arity;
139+
140+
let range = [min];
141+
142+
while (range[range.length - 1] != max) {
143+
range.push(range[range.length - 1] + 1);
144+
}
145+
146+
return range;
100147
}
101148

102149
function getOptionalValues(pattern) {
@@ -105,7 +152,7 @@ function getOptionalValues(pattern) {
105152
for (let i = 0; i < pattern.length; i++) {
106153
if (
107154
pattern[i] instanceof Types.Variable &&
108-
pattern[i].default_value != Symbol.for("tailored.no_value")
155+
pattern[i].default_value != Symbol.for('tailored.no_value')
109156
) {
110157
optionals.push([i, pattern[i].default_value]);
111158
}
@@ -144,7 +191,7 @@ export function match(pattern, expr, guard = () => true) {
144191
if (processedPattern(expr, result) && guard.apply(this, result)) {
145192
return result;
146193
} else {
147-
console.error("No match for:", expr);
194+
console.error('No match for:', expr);
148195
throw new MatchError(expr);
149196
}
150197
}
@@ -153,7 +200,7 @@ export function match_or_default(
153200
pattern,
154201
expr,
155202
guard = () => true,
156-
default_value = null
203+
default_value = null,
157204
) {
158205
let result = [];
159206
let processedPattern = buildMatch(pattern);

0 commit comments

Comments
 (0)