├── .DS_Store ├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── package.json ├── src ├── match │ ├── case-types.js │ ├── case.js │ ├── errors.js │ └── match.js └── util │ └── string-util.js └── test ├── babel-test.js ├── non-babel-test.js └── test.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roli93/js-pattern-matching/6a865744bde360e3f7cd126a0f18086e25b69378/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-2" 5 | ], 6 | "ignore": ["test/non-babel-test.js"], 7 | "plugins": [ 8 | "babel-plugin-js-pattern-matching", 9 | ["babel-plugin-transform-builtin-extend", { 10 | "globals": ["Error"] 11 | }] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Built Distribution files 40 | dist 41 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roli93/js-pattern-matching/6a865744bde360e3f7cd126a0f18086e25b69378/.npmignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6.9.2" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/roli93/js-pattern-matching.svg?branch=master) ]( https://travis-ci.org/roli93/js-pattern-matching) 2 | [![Dependency Status](https://david-dm.org/roli93/js-pattern-matching.svg)](https://david-dm.org/roli93/js-pattern-matching) 3 | [![devDependencies Status](https://david-dm.org/roli93/js-pattern-matching/dev-status.svg)](https://david-dm.org/roli93/js-pattern-matching?type=dev) 4 | 5 | JS-Pattern-Matching 6 | ==================== 7 | A small library intended to provide simple Pattern Matching capabilities for JavaScript. 8 | 9 | ```javascript 10 | var match = require('js-pattern-matching'); 11 | 12 | const sum = (list) => match (list) ( 13 | ([x,...xs]) => x + sum(xs), 14 | ([]) => 0 15 | ) 16 | 17 | console.log(sum([])); 18 | // prints 0 19 | console.log(sum([1,2,3])); 20 | // prints 6 21 | ``` 22 | 23 | Installation 24 | ==================== 25 | 26 | ``` 27 | npm install --save js-pattern-matching 28 | ``` 29 | 30 | JS-Pattern-Matching leverages ES2015 syntax to make more readable and easy-to-use code. Therefore, it can only be run in ES2015-supporting environments (Node ^4 or any platform transpiled with [Babel](https://babeljs.io/)) 31 | 32 | Babel 33 | ------ 34 | [Babel](https://babeljs.io/) is now supported by JS-Pattern-Matching! 35 | 36 | To make use of it, you just need to install the [Babel Plugin for JS-Pattern-Matching](https://www.npmjs.com/package/babel-plugin-js-pattern-matching) 37 | 38 | ``` 39 | npm install --save-dev babel-plugin-js-pattern-matching 40 | ``` 41 | 42 | Finally, you have to tell [Babel](https://babeljs.io/) to use it by adding it to your `.babelrc` file: 43 | 44 | ```javascript 45 | { 46 | "presets": [ 47 | "es2015" 48 | ], 49 | "plugins": [ 50 | "babel-plugin-js-pattern-matching", 51 | ] 52 | } 53 | ``` 54 | 55 | Using JS-Pattern-Matching 56 | ==================== 57 | We import the powerful `match` function by doing 58 | 59 | ```javascript 60 | var match = require('js-pattern-matching'); 61 | ``` 62 | ### General Syntax 63 | 64 | The syntax is always of the form: 65 | ``` 66 | match (valueToMatch)( 67 | listOfCommaSeparatedCaseClosures 68 | ) 69 | ``` 70 | 71 | Each case-closure has to be a valid ES2015 closure and is itself of the form `(pattern) => closureBody` 72 | 73 | We explore different patterns in the following section 74 | 75 | ### Matching literal values 76 | 77 | The pattern for literal values is always `(someVariable= matchingValue)`. By convention, we use the variable `v` which simply stands for "value", but any other valid JS identifier will do 78 | 79 | * We can match literal values of primitive types: `number`, `string`, `boolean`, `null`, `undefined`: 80 | ```javascript 81 | const getValue = (value) => match (value) ( 82 | (v= 1) => "The number one", 83 | (v= "hello") => "A greeting", 84 | (v= undefined) => "An undefined value", 85 | (v= null) => "A null value", 86 | (v= true) => "The true boolean", 87 | (v= NaN) => "Not a number" 88 | ) 89 | 90 | getValue(1) //returns "The number one" 91 | getValue("hello") //returns "A greeting" 92 | getValue(parseInt("lala")) //returns "Not a number" 93 | getValue(2 == 2)//returns "The true boolean" 94 | ... 95 | ``` 96 | 97 | * If no value matches, `MatchError` is thrown: 98 | ```javascript 99 | const getNumberName = (value) => match (value) ( 100 | (v= 1) => "one", 101 | (v= 2) => "two" 102 | ) 103 | 104 | getNumberName(1) //returns "one" 105 | getNumberName(5) //throws MatchError 106 | ``` 107 | 108 | * If more than one case-closures match, the first takes precedence: 109 | ```javascript 110 | const getNumberName = (value) => match (value) ( 111 | (v= 1) => "the first one", 112 | (v= 1) => "another one" 113 | ) 114 | 115 | getNumberName(1) //returns "the first one" 116 | ``` 117 | 118 | ### Matching and binding variables 119 | 120 | The pattern for a variable is simply `(variableName)` 121 | 122 | * A variable pattern always matches anything and binds the variable to the matching value 123 | ```javascript 124 | const length = (array) => match (array) ( 125 | (array) => array.length 126 | ) 127 | 128 | length([1,2,3]) //returns 3 129 | length("Hello!") //returns 6 130 | ``` 131 | 132 | * An annonymous variable (_) pattern always matches anything but doesn't bind the variable. It is usually used as a fallback case-closure 133 | 134 | ```javascript 135 | const isVowel = (letter) => match (letter) ( 136 | (v= 'A') => true, 137 | (v= 'E') => true, 138 | (v= 'I') => true, 139 | (v= 'O') => true, 140 | (v= 'U') => true, 141 | (_) => false 142 | ) 143 | 144 | isVowel('I') //returns true 145 | isVowel('R') //returns false 146 | ``` 147 | ### Matching and Binding Class instances 148 | 149 | The pattern for Classes is simply `(ClassName)` or `( variable = ClassName)` to bind the class instance 150 | 151 | * We can match values according to their class: 152 | 153 | ```javascript 154 | const hasPermission = (user) => match (user) ( 155 | (Admin) => true, 156 | (FreeUser) => false, 157 | (PremiumUser) => false 158 | ) 159 | 160 | hasPermission(new FreeUser()) //returns false 161 | ``` 162 | * We can match values according to their superclass: 163 | 164 | ```javascript 165 | const readError = (user) => match (user) ( 166 | (ReferenceError) => "Something was undeclared!", 167 | (Error) => "Other Error" 168 | ) 169 | 170 | readError(new ReferenceError()) //returns "Something was undeclared!" 171 | readError(new SyntaxError()) //returns "Other Error" 172 | ``` 173 | * We can bind to variables values that match to a class o superclass: 174 | 175 | ```javascript 176 | try { 177 | x + 1 178 | } 179 | catch(error) { 180 | match (error) ( 181 | (e = ReferenceError) => "Reference error:" + e.message, 182 | (Error) => "Other Error" 183 | ) 184 | } 185 | 186 | //As x hasn´t been declared, it returns "Reference Error: x is not defined" 187 | ``` 188 | * We can even bing to variables by ES2015 destructuring: 189 | 190 | ```javascript 191 | try { 192 | x + 1 193 | } 194 | catch(error) { 195 | match (error) ( 196 |   ({ message } = ReferenceError) => "Reference error:" + message, 197 | (Error) => "Other Error" 198 | ) 199 | } 200 | 201 | //As x hasn´t been declared, it returns "Reference Error: x is not defined" 202 | ``` 203 | 204 | ### Matching and Binding Array instances: 205 | 206 | To simplify array handling, we provide specific Array matchers, based on ES2015 Array destructuring: 207 | 208 | * We can match an empty or nonempty array: 209 | 210 | ```javascript 211 | const sum = (array) => match (array) ( 212 | ([x,...xs]) => x + sum(xs), 213 | ([]) => 0 214 | ) 215 | 216 | sum([1,2,3]) // returns 6 217 | ``` 218 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-pattern-matching", 3 | "version": "0.2.0", 4 | "description": "A small library intended to provide simple Pattern Matching for JavaScript", 5 | "main": "./dist/match/match.js", 6 | "scripts": { 7 | "test": "mocha --require babel-register", 8 | "build": "babel src --out-dir dist", 9 | "prepublish": "npm run build" 10 | }, 11 | "author": { 12 | "name": "Rodolfo Caputo", 13 | "email": "rodcap.93@gmail.com" 14 | }, 15 | "license": "ISC", 16 | "devDependencies": { 17 | "babel-cli": "^6.22.2", 18 | "babel-plugin-js-pattern-matching": "^0.1.0", 19 | "babel-plugin-transform-builtin-extend": "^1.1.2", 20 | "babel-preset-es2015": "^6.22.0", 21 | "babel-preset-stage-2": "^6.22.0", 22 | "babel-register": "^6.23.0", 23 | "expect.js": "^0.3.1", 24 | "mocha": "^3.2.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/roli93/js-pattern-matching" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/match/case-types.js: -------------------------------------------------------------------------------- 1 | import { substringFrom, substringTo, head, last } from '../util/string-util.js'; 2 | 3 | const isUndeclared = (pattern) => { 4 | let result = false; 5 | try { 6 | eval(pattern); 7 | } catch(e) { 8 | if(e instanceof ReferenceError || e instanceof SyntaxError) result = true; 9 | } 10 | return result 11 | } 12 | 13 | const isA = (value,type) => (type === "null" && value === null) || typeof value == type 14 | 15 | class Binding { 16 | 17 | static binds(){ 18 | return true; 19 | } 20 | 21 | } 22 | 23 | class NonBinding { 24 | 25 | static binds(){ 26 | return false; 27 | } 28 | 29 | } 30 | 31 | class PrimitiveValue extends NonBinding{ 32 | 33 | static matches(value, pattern){ 34 | return Object.is(eval(pattern), value); 35 | } 36 | 37 | static applysFor(pattern){ 38 | if(isUndeclared(pattern)) return false; 39 | let value = eval(pattern); 40 | return ( 41 | isA(value, "number") || isA(value, "string") || isA(value, "boolean") || isA(value, "null") || isA(value, "undefined") 42 | ); 43 | } 44 | 45 | } 46 | 47 | class Variable extends Binding { 48 | 49 | static matches(value, pattern){ 50 | return true; 51 | } 52 | 53 | static applysFor(pattern){ 54 | return ( 55 | /^([a-z]|[A-Z]|_)(\w)*/.test(pattern) && 56 | !PrimitiveValue.applysFor(pattern) && 57 | !Class.applysFor(pattern) && 58 | !AnnonymousVariable.applysFor(pattern) 59 | ) 60 | } 61 | 62 | } 63 | 64 | class AnnonymousVariable extends NonBinding { 65 | 66 | static matches(value, pattern){ 67 | return true; 68 | } 69 | 70 | static applysFor(pattern){ 71 | return pattern === "_" 72 | } 73 | 74 | } 75 | 76 | class Class extends Binding { 77 | 78 | static matches(value, pattern){ 79 | return value instanceof eval(pattern) 80 | } 81 | 82 | static applysFor(pattern){ 83 | if(isUndeclared(pattern)) return false; 84 | return isA(eval(pattern), 'function') 85 | } 86 | } 87 | 88 | class GenericArray extends Binding { 89 | 90 | static matches(value, pattern){ 91 | return value instanceof Array && this.emptinessHolds(value) 92 | } 93 | 94 | } 95 | 96 | class EmptyArray extends GenericArray { 97 | 98 | static applysFor(pattern){ 99 | return pattern === "[]" 100 | } 101 | 102 | static emptinessHolds(value){ 103 | return value.length === 0; 104 | } 105 | 106 | } 107 | 108 | class NonemptyArray extends GenericArray { 109 | 110 | static applysFor(pattern){ 111 | return pattern !== "[]" && head(pattern) === "[" && last(pattern) == "]" 112 | } 113 | 114 | static emptinessHolds(value){ 115 | return value.length > 0; 116 | } 117 | } 118 | 119 | export default [ 120 | PrimitiveValue, 121 | Variable, 122 | Class, 123 | EmptyArray, 124 | NonemptyArray, 125 | AnnonymousVariable 126 | ]; 127 | -------------------------------------------------------------------------------- /src/match/case.js: -------------------------------------------------------------------------------- 1 | import caseTypes from './case-types.js'; 2 | import { withoutWhitespaces, substringFrom } from '../util/string-util.js'; 3 | 4 | class Case { 5 | 6 | constructor(func, pattern, type){ 7 | this.resultFunction = func; 8 | this.pattern = pattern; 9 | this.type = type; 10 | } 11 | 12 | getPattern(){ 13 | return this.pattern; 14 | } 15 | 16 | getResultFunction(){ 17 | return this.resultFunction 18 | } 19 | 20 | matches(value){ 21 | return this.type.matches(value, this.getPattern()); 22 | } 23 | 24 | binds(){ 25 | return this.type.binds(); 26 | } 27 | 28 | } 29 | 30 | class CaseBuilder { 31 | 32 | static types(){ 33 | return caseTypes; 34 | } 35 | 36 | static build(caseDefinition){ 37 | return new Case( 38 | this.getCaseFunction(caseDefinition), 39 | this.getCasePattern(caseDefinition), 40 | this.getCaseType(caseDefinition) 41 | ); 42 | } 43 | 44 | static isBabelized(caseDefinition){ 45 | let r = Boolean(caseDefinition.pattern) && Boolean(caseDefinition.function); 46 | return r 47 | } 48 | 49 | static getCaseFunction(caseDefinition){ 50 | return this.isBabelized(caseDefinition)? caseDefinition.function : caseDefinition 51 | } 52 | 53 | static getCasePattern(caseDefinition){ 54 | return this.isBabelized(caseDefinition)? caseDefinition.pattern : this.buildPattern(caseDefinition) 55 | } 56 | 57 | static getCaseType(caseDefinition){ 58 | return CaseBuilder.types().find( type => type.applysFor(this.getCasePattern(caseDefinition)) ) 59 | } 60 | 61 | static isValuePattern(parameters){ 62 | return parameters.includes("=") 63 | } 64 | 65 | static buildPattern(caseDefinition){ 66 | let cleanString = withoutWhitespaces(caseDefinition.toString()) 67 | let parameters = cleanString.slice(cleanString.indexOf("(") + 1, cleanString.indexOf(")=>")); 68 | return this.isValuePattern(parameters) ? substringFrom(parameters, "=") : parameters; 69 | } 70 | 71 | } 72 | 73 | export default CaseBuilder; 74 | -------------------------------------------------------------------------------- /src/match/errors.js: -------------------------------------------------------------------------------- 1 | class MatchError extends Error{} 2 | 3 | class ParseError extends Error{} 4 | 5 | export { 6 | MatchError, 7 | ParseError 8 | } 9 | -------------------------------------------------------------------------------- /src/match/match.js: -------------------------------------------------------------------------------- 1 | import { MatchError, ParseError } from './errors.js'; 2 | import CaseBuilder from './case.js'; 3 | 4 | const match = (value) => (...functionCases) => { 5 | let cases = functionCases.map( aCase => CaseBuilder.build(aCase) ) 6 | 7 | let matchingCase = cases.find( aCase => aCase.matches(value) ) 8 | if(!matchingCase) 9 | throw new MatchError() 10 | 11 | return matchingCase.getResultFunction()(matchingCase.binds()? value : undefined) 12 | } 13 | 14 | module.exports = match 15 | -------------------------------------------------------------------------------- /src/util/string-util.js: -------------------------------------------------------------------------------- 1 | const withoutWhitespaces = (string) => string.replace(/\s/g, ''); 2 | 3 | const substringFrom = (string, start) => { 4 | return string.substring(string.indexOf(start)+start.length) 5 | } 6 | 7 | const substringTo = (string, end) => { 8 | return string.substring(0,string.indexOf(end)) 9 | } 10 | 11 | const head = (string) => string[0] 12 | 13 | const last = (string) => string[string.length - 1] 14 | 15 | export { 16 | withoutWhitespaces, 17 | substringFrom, 18 | substringTo, 19 | head, 20 | last 21 | } 22 | -------------------------------------------------------------------------------- /test/babel-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import match from '../src/match/match.js'; 3 | import { MatchError } from '../src/match/errors.js'; 4 | 5 | export default () => { 6 | context('Value matching', () => { 7 | 8 | const getValueName = (value) => match (value) ( 9 | (v= 1) => "one", 10 | (v= 2) => "two", 11 | (v= 2) => "another two", 12 | (v= "three") => "3", 13 | (v= undefined) => "undefined", 14 | (v= null) => "null", 15 | (v= true) => "true", 16 | (v= NaN) => "Not a number" 17 | ) 18 | 19 | it('should match a literal number value', () => { 20 | expect(getValueName(1)).to.equal("one"); 21 | }); 22 | 23 | it('should match a literal boolean value', () => { 24 | expect(getValueName(true)).to.equal("true"); 25 | }); 26 | 27 | it('should match a literal string value', () => { 28 | expect(getValueName("three")).to.equal("3"); 29 | }); 30 | 31 | it('should match a literal null value', () => { 32 | expect(getValueName(null)).to.equal("null"); 33 | }); 34 | 35 | it('should match a literal undefined value', () => { 36 | expect(getValueName(undefined)).to.equal("undefined"); 37 | }); 38 | 39 | it('should match a literal NaN value', () => { 40 | expect(getValueName(NaN)).to.equal("Not a number"); 41 | }); 42 | 43 | it('should throw a MatchError when no literal value matches', () => { 44 | expect(() => getValueName(3)).to.throwError((e) => expect(e).to.be.a(MatchError)); 45 | }); 46 | 47 | it('should match the first matching value', () => { 48 | expect(getValueName(2)).to.equal("two"); 49 | }); 50 | 51 | }); 52 | 53 | context('Variables', () => { 54 | 55 | const getValueName = (number) => match (number) ( 56 | (v= 1) => "one", 57 | (v= 2) => "two", 58 | (_) => "other", 59 | (v= 100) => "a hundred" 60 | ) 61 | 62 | const getVar = (variable) => match (variable) ( 63 | (whatever) => "Whatever was " + whatever 64 | ) 65 | 66 | const getAnon = (variable) => match (variable) ( 67 | (_) => _ 68 | ) 69 | 70 | it('should always match an annonymous variable if no previous value matches', () => { 71 | expect(getValueName(5)).to.equal("other"); 72 | }); 73 | 74 | it('should absorb further cases with an annonymous variable', () => { 75 | expect(getValueName(100)).to.equal("other"); 76 | }); 77 | 78 | it('should always match and bind a variable', () => { 79 | expect(getVar("Blah")).to.equal("Whatever was Blah"); 80 | }); 81 | 82 | it('should never bind an annonymous variable', () => { 83 | expect(getAnon("Blah")).to.equal(undefined); 84 | }); 85 | 86 | }); 87 | 88 | context('Context wrapping', () => { 89 | 90 | const bang = "!" 91 | 92 | const getValueName = (number) => match (number) ( 93 | (v= 1) => "one"+bang, 94 | (v= 2) => "two" 95 | ) 96 | 97 | it('should wrap external scope variables', () => { 98 | expect(getValueName(1)).to.equal("one!"); 99 | }); 100 | 101 | }); 102 | 103 | context('Class matching', () => { 104 | 105 | const getValueName = (value) => match (value) ( 106 | (v= 1) => "one", 107 | (EvalError) => "EvalError", 108 | (e = ReferenceError) => e.message, 109 | ({ message } = SyntaxError) => message+"!", 110 | (Error) => "Other Error" 111 | ) 112 | 113 | it('should match a value belonging to a class', () => { 114 | expect(getValueName(new EvalError())).to.equal("EvalError"); 115 | }); 116 | 117 | it('should match a value belonging to a subclass', () => { 118 | expect(getValueName(new RangeError())).to.equal("Other Error"); 119 | }); 120 | 121 | it('should allow for the matching value to be used in the closure', () => { 122 | expect(getValueName(new ReferenceError("Undeclared variable") )).to.equal("Undeclared variable"); 123 | }); 124 | 125 | it('should allow for the matching value to be deconstructed as object and used in the closure', () => { 126 | expect(getValueName(new SyntaxError("Bleh"))).to.equal("Bleh!"); 127 | }); 128 | 129 | }); 130 | 131 | context('Array matching', () => { 132 | 133 | const sum = (array) => match (array) ( 134 | ([x,...xs]) => x + sum(xs), 135 | ([]) => 0 136 | ) 137 | 138 | const empty = (array) => match (array) ( 139 | ([]) => true, 140 | (_) => false 141 | ) 142 | 143 | it('should match an empty array', () => { 144 | expect(empty([])).to.equal(true); 145 | }); 146 | 147 | it('should match a nonempty array', () => { 148 | expect(sum([1,2,3])).to.equal(6); 149 | }); 150 | }); 151 | 152 | } 153 | -------------------------------------------------------------------------------- /test/non-babel-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var match = require('../src/match/match.js'); 3 | var { MatchError } = require('../src/match/errors.js'); 4 | 5 | module.exports = () => { 6 | 7 | context('Value matching', () => { 8 | 9 | const getValueName = (value) => match (value) ( 10 | (v= 1) => "one", 11 | (v= 2) => "two", 12 | (v= 2) => "another two", 13 | (v= "three") => "3", 14 | (v= undefined) => "undefined", 15 | (v= null) => "null", 16 | (v= true) => "true", 17 | (v= NaN) => "Not a number" 18 | ) 19 | 20 | it('should match a literal number value', () => { 21 | expect(getValueName(1)).to.equal("one"); 22 | }); 23 | 24 | it('should match a literal boolean value', () => { 25 | expect(getValueName(true)).to.equal("true"); 26 | }); 27 | 28 | it('should match a literal string value', () => { 29 | expect(getValueName("three")).to.equal("3"); 30 | }); 31 | 32 | it('should match a literal null value', () => { 33 | expect(getValueName(null)).to.equal("null"); 34 | }); 35 | 36 | it('should match a literal undefined value', () => { 37 | expect(getValueName(undefined)).to.equal("undefined"); 38 | }); 39 | 40 | it('should match a literal NaN value', () => { 41 | expect(getValueName(NaN)).to.equal("Not a number"); 42 | }); 43 | 44 | it('should throw a MatchError when no literal value matches', () => { 45 | expect(() => getValueName(3)).to.throwError((e) => expect(e).to.be.a(MatchError)); 46 | }); 47 | 48 | it('should match the first matching value', () => { 49 | expect(getValueName(2)).to.equal("two"); 50 | }); 51 | 52 | }); 53 | 54 | context('Variables', () => { 55 | 56 | const getValueName = (number) => match (number) ( 57 | (v= 1) => "one", 58 | (v= 2) => "two", 59 | (_) => "other", 60 | (v= 100) => "a hundred" 61 | ) 62 | 63 | const getVar = (variable) => match (variable) ( 64 | (whatever) => "Whatever was " + whatever 65 | ) 66 | 67 | const getAnon = (variable) => match (variable) ( 68 | (_) => _ 69 | ) 70 | 71 | it('should always match an annonymous variable if no previous value matches', () => { 72 | expect(getValueName(5)).to.equal("other"); 73 | }); 74 | 75 | it('should absorb further cases with an annonymous variable', () => { 76 | expect(getValueName(100)).to.equal("other"); 77 | }); 78 | 79 | it('should always match and bind a variable', () => { 80 | expect(getVar("Blah")).to.equal("Whatever was Blah"); 81 | }); 82 | 83 | it('should never bind an annonymous variable', () => { 84 | expect(getAnon("Blah")).to.equal(undefined); 85 | }); 86 | 87 | }); 88 | 89 | context('Context wrapping', () => { 90 | 91 | const bang = "!" 92 | 93 | const getValueName = (number) => match (number) ( 94 | (v= 1) => "one"+bang, 95 | (v= 2) => "two" 96 | ) 97 | 98 | it('should wrap external scope variables', () => { 99 | expect(getValueName(1)).to.equal("one!"); 100 | }); 101 | 102 | }); 103 | 104 | context('Class matching', () => { 105 | 106 | const getValueName = (value) => match (value) ( 107 | (v= 1) => "one", 108 | (EvalError) => "EvalError", 109 | (e = ReferenceError) => e.message, 110 | ({ message } = SyntaxError) => message+"!", 111 | (Error) => "Other Error" 112 | ) 113 | 114 | it('should match a value belonging to a class', () => { 115 | expect(getValueName(new EvalError())).to.equal("EvalError"); 116 | }); 117 | 118 | it('should match a value belonging to a subclass', () => { 119 | expect(getValueName(new RangeError())).to.equal("Other Error"); 120 | }); 121 | 122 | it('should allow for the matching value to be used in the closure', () => { 123 | expect(getValueName(new ReferenceError("Undeclared variable") )).to.equal("Undeclared variable"); 124 | }); 125 | 126 | it('should allow for the matching value to be deconstructed as object and used in the closure', () => { 127 | expect(getValueName(new SyntaxError("Bleh"))).to.equal("Bleh!"); 128 | }); 129 | 130 | }); 131 | 132 | context('Array matching', () => { 133 | 134 | const sum = (array) => match (array) ( 135 | ([x,...xs]) => x + sum(xs), 136 | ([]) => 0 137 | ) 138 | 139 | const empty = (array) => match (array) ( 140 | ([]) => true, 141 | (_) => false 142 | ) 143 | 144 | it('should match an empty array', () => { 145 | expect(empty([])).to.equal(true); 146 | }); 147 | 148 | it('should match a nonempty array', () => { 149 | expect(sum([1,2,3])).to.equal(6); 150 | }); 151 | }); 152 | } 153 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import babelTests from './babel-test.js'; 2 | import nonBabelTests from './non-babel-test.js'; 3 | 4 | describe('Basic Match', nonBabelTests); 5 | 6 | describe('Match with Babel', babelTests); 7 | --------------------------------------------------------------------------------