Skip to content

Commit 4bf42f9

Browse files
committed
Initial commit 🚀
0 parents  commit 4bf42f9

12 files changed

+5656
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
node_modules
3+
npm-debug.log
4+
lib
5+
notes.md

.prettierrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": true
3+
}

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Match

jest.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
};

package-lock.json

+4,784
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "@gvergnaud/match",
3+
"version": "0.0.1",
4+
"description": "Typescript pattern matching library",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"clean": "rimraf lib",
8+
"build": "tsc -d",
9+
"prepare": "npm run clean && npm run build",
10+
"test": "jest"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+ssh://[email protected]/gvergnaud/match.git"
15+
},
16+
"keywords": [
17+
"pattern",
18+
"matching",
19+
"pattern-matching",
20+
"typescript",
21+
"match-with",
22+
"match",
23+
"switch",
24+
"adt"
25+
],
26+
"author": "Gabriel Vergnaud",
27+
"license": "MIT",
28+
"bugs": {
29+
"url": "https://github.com/gvergnaud/match/issues"
30+
},
31+
"homepage": "https://github.com/gvergnaud/match#readme",
32+
"devDependencies": {
33+
"@types/jest": "^25.2.3",
34+
"jest": "^26.0.1",
35+
"prettier": "^2.0.5",
36+
"rimraf": "^3.0.2",
37+
"ts-jest": "^26.0.0",
38+
"typescript": "^3.9.3"
39+
}
40+
}

src/index.d.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* # Pattern matching
3+
**/
4+
/**
5+
* ## Catch All Type
6+
* `__` refers to a wildcard pattern matching any value
7+
*/
8+
declare type __ = '__CATCH_ALL__';
9+
export declare const __: __;
10+
/**
11+
* ## Pattern
12+
* Patterns can be any (nested) javascript value.
13+
* They can also be "wildcards", using type constructors
14+
*/
15+
export declare type Pattern<a> = a extends number ? a | NumberConstructor | __ : a extends string ? a | StringConstructor | __ : a extends boolean ? a | BooleanConstructor | __ : a extends [infer b, infer c, infer d, infer e, infer f] ? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>, Pattern<f>] | __ : a extends [infer b, infer c, infer d, infer e] ? [Pattern<b>, Pattern<c>, Pattern<d>, Pattern<e>] | __ : a extends [infer b, infer c, infer d] ? [Pattern<b>, Pattern<c>, Pattern<d>] | __ : a extends [infer b, infer c] ? [Pattern<b>, Pattern<c>] | __ : a extends (infer b)[] ? Pattern<b>[] | __ : a extends Map<infer k, infer v> ? Map<k, Pattern<v>> | __ : a extends Set<infer v> ? Set<Pattern<v>> | __ : {
16+
[k in keyof a]?: Pattern<a[k]>;
17+
} | __;
18+
/**
19+
* ## Invert Pattern
20+
* Since patterns have special wildcard values, we need a way
21+
* to transform a pattern into the type of value it represents
22+
*/
23+
export declare type InvertPattern<p> = p extends NumberConstructor ? number : p extends StringConstructor ? string : p extends BooleanConstructor ? boolean : p extends [infer pb, infer pc, infer pd, infer pe, infer pf] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>, InvertPattern<pe>, InvertPattern<pf>] : p extends [infer pb, infer pc, infer pd, infer pe] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>, InvertPattern<pe>] : p extends [infer pb, infer pc, infer pd] ? [InvertPattern<pb>, InvertPattern<pc>, InvertPattern<pd>] : p extends [infer pb, infer pc] ? [InvertPattern<pb>, InvertPattern<pc>] : p extends (infer pp)[] ? InvertPattern<pp>[] : p extends Map<infer pk, infer pv> ? Map<pk, InvertPattern<pv>> : p extends Set<infer pv> ? Set<InvertPattern<pv>> : p extends __ ? __ : {
24+
[k in keyof p]: InvertPattern<p[k]>;
25+
};
26+
declare type Fun<a, b> = (value: a) => b;
27+
/**
28+
* ## LeastUpperBound
29+
* An interesting one. A type taking two imbricated sets and returning the
30+
* smallest one.
31+
* We need that because sometimes the pattern's infered type holds more
32+
* information than the value on which we are matching (if the value is any
33+
* or unknown for instance).
34+
*/
35+
declare type LeastUpperBound<a, b> = [a, b] extends [[infer aa, infer ab, infer ac, infer ad, infer ae], [infer ba, infer bb, infer bc, infer bd, infer be]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>, LeastUpperBound<ad, bd>, LeastUpperBound<ae, be>] : [a, b] extends [[infer aa, infer ab, infer ac, infer ad], [infer ba, infer bb, infer bc, infer bd]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>, LeastUpperBound<ad, bd>] : [a, b] extends [[infer aa, infer ab, infer ac], [infer ba, infer bb, infer bc]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>, LeastUpperBound<ac, bc>] : [a, b] extends [[infer aa, infer ab], [infer ba, infer bb]] ? [LeastUpperBound<aa, ba>, LeastUpperBound<ab, bb>] : [a, b] extends [(infer aa)[], (infer ba)[]] ? LeastUpperBound<aa, ba>[] : [a, b] extends [Map<infer ak, infer av>, Map<infer bk, infer bv>] ? Map<LeastUpperBound<ak, bk>, LeastUpperBound<av, bv>> : [a, b] extends [Set<infer av>, Set<infer bv>] ? Set<LeastUpperBound<av, bv>> : b extends __ ? a : a extends __ ? b : b extends a ? b : a extends b ? a : never;
36+
/**
37+
* ## match
38+
* Entry point to create pattern matching code branches. It returns an
39+
* empty builder
40+
*/
41+
export declare const match: <a, b>(value: a) => {
42+
with: <p extends Pattern<a>>(pattern: p, f: Fun<LeastUpperBound<a, InvertPattern<p>>, b>) => any;
43+
when: (predicate: Fun<a, unknown>, f: Fun<a, b>) => any;
44+
withWhen: <p_1 extends Pattern<a>>(pattern: p_1, predicate: Fun<LeastUpperBound<a, InvertPattern<p_1>>, unknown>, f: Fun<LeastUpperBound<a, InvertPattern<p_1>>, b>) => any;
45+
otherwise: (f: () => b) => any;
46+
run: () => b;
47+
};
48+
export {};

src/index.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"use strict";
2+
/**
3+
* # Pattern matching
4+
**/
5+
var __spreadArrays = (this && this.__spreadArrays) || function () {
6+
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
7+
for (var r = Array(s), k = 0, i = 0; i < il; i++)
8+
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
9+
r[k] = a[j];
10+
return r;
11+
};
12+
exports.__esModule = true;
13+
exports.match = exports.__ = void 0;
14+
exports.__ = '__CATCH_ALL__';
15+
/**
16+
* ## match
17+
* Entry point to create pattern matching code branches. It returns an
18+
* empty builder
19+
*/
20+
exports.match = function (value) { return builder(value, []); };
21+
/**
22+
* ## builder
23+
* This is the implementation of our pattern matching, using the
24+
* builder pattern.
25+
* This builder pattern is neat because we can have complexe type checking
26+
* for each of the methods adding new behavior to our pattern matching.
27+
*/
28+
var builder = function (value, patterns) { return ({
29+
"with": function (pattern, f) {
30+
return builder(value, __spreadArrays(patterns, [[matchPattern(pattern), f]]));
31+
},
32+
when: function (predicate, f) {
33+
return builder(value, __spreadArrays(patterns, [[predicate, f]]));
34+
},
35+
withWhen: function (pattern, predicate, f) {
36+
var doesMatch = function (value) {
37+
return Boolean(matchPattern(pattern)(value) && predicate(value));
38+
};
39+
return builder(value, __spreadArrays(patterns, [[doesMatch, f]]));
40+
},
41+
otherwise: function (f) {
42+
return builder(value, __spreadArrays(patterns, [
43+
[matchPattern(exports.__), f],
44+
]));
45+
},
46+
run: function () {
47+
var tupple = patterns.find(function (_a) {
48+
var predicate = _a[0];
49+
return predicate(value);
50+
});
51+
if (!tupple) {
52+
throw new Error("Pattern matching error: no pattern matches value " + value);
53+
}
54+
var mapper = tupple[1];
55+
return mapper(value);
56+
}
57+
}); };
58+
var isObject = function (value) {
59+
return value && typeof value === 'object';
60+
};
61+
var wildcards = [String, Boolean, Number];
62+
// tells us if the value matches a given pattern.
63+
var matchPattern = function (pattern) { return function (value) {
64+
if (pattern === exports.__)
65+
return true;
66+
if (pattern === String)
67+
return typeof value === 'string';
68+
if (pattern === Boolean)
69+
return typeof value === 'boolean';
70+
if (pattern === Number) {
71+
return typeof value === 'number' && !Number.isNaN(value);
72+
}
73+
if (typeof pattern !== typeof value)
74+
return false;
75+
if (Array.isArray(pattern) && Array.isArray(value)) {
76+
return pattern.length === 1
77+
? value.every(function (v, i) { return matchPattern(pattern[0])(v); })
78+
: pattern.length === value.length
79+
? value.every(function (v, i) {
80+
return pattern[i] ? matchPattern(pattern[i])(v) : false;
81+
})
82+
: false;
83+
}
84+
if (value instanceof Map && pattern instanceof Map) {
85+
return __spreadArrays(pattern.keys()).every(function (key) {
86+
return matchPattern(pattern.get(key))(value.get(key));
87+
});
88+
}
89+
if (value instanceof Set && pattern instanceof Set) {
90+
var patternValues = __spreadArrays(pattern.values());
91+
var allValues_1 = __spreadArrays(value.values());
92+
return patternValues.length === 0
93+
? allValues_1.length === 0
94+
: patternValues.length === 1
95+
? patternValues.every(function (subPattern) {
96+
return wildcards.includes(subPattern)
97+
? matchPattern([subPattern])(allValues_1)
98+
: value.has(subPattern);
99+
})
100+
: patternValues.every(function (subPattern) { return value.has(subPattern); });
101+
}
102+
if (isObject(value) && isObject(pattern)) {
103+
return Object.keys(pattern).every(function (k) {
104+
// @ts-ignore
105+
return matchPattern(pattern[k])(value[k]);
106+
});
107+
}
108+
return value === pattern;
109+
}; };

0 commit comments

Comments
 (0)