-
-
Save zkat/c89deb618d0d8aeed04fb7b3c49a714a to your computer and use it in GitHub Desktop.
Revisions
-
zkat revised this gist
Mar 21, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ // match ABNF Match := 'match' '(' RHSExpr ')' '{' MatchClause* '}' MatchClause := MatchClauseLHS '=>' FatArroyRHS MaybeASI MatchClauseLHS := [MatcherExpr] (LiteralMatcher | ArrayMatcher | ObjectMatcher | JSVar) MatcherExpr := LHSExpr LiteralMatcher := LitRegExp | LitString | LitNumber -
zkat revised this gist
Mar 21, 2018 . 1 changed file with 8 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,8 @@ // match ABNF Match := 'match' '(' RHSExpr ')' '{' MatchClause* '}' MatchClause := MatchClauseLHS '=>' RHSExpr MaybeASI MatchClauseLHS := [MatcherExpr] (LiteralMatcher | ArrayMatcher | ObjectMatcher | JSVar) MatcherExpr := LHSExpr LiteralMatcher := LitRegExp | LitString | LitNumber ArrayMatcher := '[' MatchClauseLHS [',', MatchClauseLHS]* ']' // and... whatever it takes to shove ...splat in there ObjectMatcher := '{' (JSVar [':' MatchClauseLHS]) [',' (JSVar [':', MatchClauseLHS])]* '}' // see above note about ...splat -
zkat revised this gist
Mar 21, 2018 . 2 changed files with 33 additions and 10 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -12,9 +12,22 @@ function mm (matcher, val) { } } function mv (matcher, val) { const v = matcher[Symbol.matchValue] if (v) { return v(val) } else { return val } } function match (val, ...expressions) { let matched const expr = expressions.find(expr => { matched = mv(expr.Matcher, val) return mm(expr.Matcher, matched) }) return expr && expr.body(matched) } function expr (Matcher, body) { @@ -34,18 +47,20 @@ class Foo extends Object { function tryMatch (val) { console.log('\nmatch', `(val = ${util.inspect(val)}) {`, '\n ', match(val, // sugar: /foo(bar)/ [match, submatch] expr( // Compound matcher generated { [Symbol.match] (val) { return ( mm(Array, val) ) }, [Symbol.matchValue] (val) { return String(val).match(/foo(bar)/) } }, ([match, submatch]) => `/foo(bar)/ [match, submatch] => match === ${util.inspect(match)} && submatch === ${util.inspect(submatch)}` ), // sugar: [a, b] @@ -162,3 +177,6 @@ tryMatch({y: new Foo(1, 2)}) console.log('\n== guards ==') tryMatch([3,2,1]) console.log('\n== using Symbol.matchValue api ==') tryMatch(/foobar/) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,3 @@ == basic match types == match (val = { x: 1, y: 2 }) { @@ -24,13 +23,13 @@ match (val = [ 1, 2 ]) { } match (val = [ 1, 2, 3, 4, 5 ]) { [a, 2, ...rest] => a === 1 && rest === [ 3, 4, 5 ] } == compound matching == match (val = { x: { y: 2 } }) { {x, x: {y}} => x === { y: 2 } && y === 2 // (follows destr. syntax) } match (val = { y: { x: 1 } }) { @@ -48,5 +47,11 @@ match (val = { y: Foo { x: 1, y: 2 } }) { == guards == match (val = [ 3, 2, 1 ]) { [a, 2, ...rest] => a === 3 && rest === [ 1 ] } == using Symbol.matchValue api == match (val = /foobar/) { /foo(bar)/ [match, submatch] => match === 'foobar' && submatch === 'bar' } -
zkat revised this gist
Mar 21, 2018 . 1 changed file with 23 additions and 18 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,8 +2,18 @@ const util = require('util') function mm (matcher, val) { // We only invoke matchers if there's a `Symbol.match` mthod const m = matcher[Symbol.match] if (m) { return m(val) } else { return val instanceof matcher } } function match (val, ...expressions) { const expr = expressions.find(expr => mm(expr.Matcher, val)) return expr.body(val) } @@ -14,11 +24,6 @@ function expr (Matcher, body) { } } class Foo extends Object { constructor (x, y) { super() @@ -35,7 +40,7 @@ function tryMatch (val) { { [Symbol.match] (val) { return ( mm(Array, val) && (([...rest]) => rest[0] === 3)(val) ) } @@ -49,7 +54,7 @@ function tryMatch (val) { { [Symbol.match] (val) { return ( mm(Array, val) && val.length === 2 ) } @@ -63,21 +68,21 @@ function tryMatch (val) { { [Symbol.match] (val) { return ( mm(Array, val) && val[1] === 2 ) } }, ([a, _, ...rest]) => `[a, 2, ...rest] => a === ${util.inspect(a)} && rest === ${util.inspect(rest)}` ), // sugar: {y: {x: 'hello'}} expr( // Compound matcher generated { [Symbol.match] (val) { return ( mm(Object, val) && mm(Object, val.y) && val.y.x === 'hello' ) } @@ -91,8 +96,8 @@ function tryMatch (val) { { [Symbol.match] (val) { return ( mm(Object, val) && mm(Foo, val.y) ) } }, @@ -105,8 +110,8 @@ function tryMatch (val) { { [Symbol.match] (val) { return ( mm(Object, val) && mm(Object, val.x) ) } }, @@ -119,8 +124,8 @@ function tryMatch (val) { { [Symbol.match] (val) { return ( mm(Object, val) && mm(Object, val.y) ) } }, -
zkat revised this gist
Mar 21, 2018 . 2 changed files with 17 additions and 19 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -30,7 +30,7 @@ match (val = [ 1, 2, 3, 4, 5 ]) { == compound matching == match (val = { x: { y: 2 } }) { {x: {y} as x} => x === { y: 2 } && y === 2 // (using hypothetical `as` syntax -- would have to be `{x, x: {y}}` otherwise) } match (val = { y: { x: 1 } }) { This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,48 +1,46 @@ // I believe all of the below are fully nestable const foo = match (x) { // Basic concepts. This is all you need to actually know. `Symbol.match` // operates on this Object {}-style protocol in all cases. // object-style destructuring. x, y are bound. Just {val} => val None {} => ... // (see below for alternative None) // bind value to x, match with matcher. Toplevel reduntant; used for nesting Matcher {} as x => x // Syntax sugar extensions, all statically compile down to above. // Desugars to `String/Number/RegExp {}` but compilers can special-case. 'literal' => ... 42 => ... /regexp/ => ... // Regexp has Array-like matcher. // This desugars to RegExp {length: 2, 0: a, b: 2} /regexp/ [a, b] => ... // Desugars to Object {x, y} {x, y} as obj => ... // Desugars to Array {length: 2, 0: a, 1: b}. Array[Symbol.match] method enforces the "fail if wrong length" [a, b] => ... // Desugars to TypedArray {length: 2, 0: a, 1: b} TypedArray [a, b] => ... // `as` syntax allows omitting {} for matchers maybe? Matcher as x => ... // Plain variable matches without running a matcher. // imo, this doesn't need a first-class fallthrough. I think encouraging // people to have "fully qualified" matchers is important and // regular variables can be used for fallthrough just fine // Please no * nonsense plz _ => 'just a variable' other => console.log(other) // lol // equality matches done with guards other if other === 1 => 'other is 1' } -
zkat revised this gist
Mar 21, 2018 . 2 changed files with 211 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,159 @@ 'use strict' const util = require('util') function match (val, ...expressions) { const expr = expressions.find(expr => expr.Matcher[Symbol.match](val)) return expr.body(val) } function expr (Matcher, body) { return { Matcher, body } } // default protocol impl Object[Symbol.match] = function (x) { return x instanceof this } Array[Symbol.match] = function (x) { return x instanceof this } String[Symbol.match] = function (x) { return x instanceof this } class Foo extends Object { constructor (x, y) { super() this.x = x this.y = y } } function tryMatch (val) { console.log('\nmatch', `(val = ${util.inspect(val)}) {`, '\n ', match(val, // sugar: [...rest] if rest[0] === 3 expr( // Compound matcher generated { [Symbol.match] (val) { return ( Array[Symbol.match](val) && (([...rest]) => rest[0] === 3)(val) ) } }, ([...rest]) => `[...rest] if rest[0] === 3 => rest === ${util.inspect(rest)}` ), // sugar: [a, b] expr( // Compound matcher generated { [Symbol.match] (val) { return ( Array[Symbol.match](val) && val.length === 2 ) } }, ([a, b]) => `[a, b] => ${util.inspect([a, b])}` ), // sugar: [a, 2, ...rest] expr( // Compound matcher generated { [Symbol.match] (val) { return ( Array[Symbol.match](val) && val[1] === 2 ) } }, ([a, _, ...rest]) => `[a, 2, ..rest] => a === ${util.inspect(a)} && rest === ${util.inspect(rest)}` ), // sugar: {y: {x: 'hello'}} expr( // Compound matcher generated { [Symbol.match] (val) { return ( Object[Symbol.match](val) && Object[Symbol.match](val.y) && val.y.x === 'hello' ) } }, ({y: {x}}) => `{y: {x: 'hello'}} => x === ${util.inspect(x)}` ), // sugar: {y: Foo {x: 'hello'}} expr( // Compound matcher generated { [Symbol.match] (val) { return ( Object[Symbol.match](val) && Foo[Symbol.match](val.y) ) } }, ({y: {x}}) => `{y: Foo {x}} => x === ${util.inspect(x)}` ), // sugar: {x, x: {y}} expr( // Compound matcher generated { [Symbol.match] (val) { return ( Object[Symbol.match](val) && Object[Symbol.match](val.x) ) } }, ({x, x: {y}}) => `{x, x: {y}} => x === ${util.inspect(x)} && y === ${y} // (follows destr. syntax)` ), // sugar: {y: {x}}: x + y expr( // Compound matcher generated { [Symbol.match] (val) { return ( Object[Symbol.match](val) && Object[Symbol.match](val.y) ) } }, ({y: {x}}) => `{y: {x}} => x === ${x} // (y is unbound)` ), // sugar: Foo {x, y} expr(Foo, ({x, y}) => `Foo {x, y} => x === ${x} && y === ${y}`), // sugar: {x, y} expr(Object, ({x, y}) => `{x, y} => x === ${x} && y === ${y}`), // sugar: <literal number/string> expr({ [Symbol.match] (v) { return v === val } }, (x) => `${util.inspect(val)} => val === ${util.inspect(x)}`) ), '\n}') } console.log('== basic match types ==') tryMatch({x: 1, y: 2}) tryMatch(new Foo(1, 2)) tryMatch('hello') tryMatch(1) console.log('\n== array matching ==') tryMatch([1, 2]) tryMatch([1, 2, 3, 4, 5]) console.log('\n== compound matching ==') tryMatch({x: {y: 2}}) tryMatch({y: {x: 1}}) tryMatch({y: {x: 'hello'}}) tryMatch({y: new Foo(1, 2)}) console.log('\n== guards ==') tryMatch([3,2,1]) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,52 @@ ➜ node ./matcher.js == basic match types == match (val = { x: 1, y: 2 }) { {x, y} => x === 1 && y === 2 } match (val = Foo { x: 1, y: 2 }) { Foo {x, y} => x === 1 && y === 2 } match (val = 'hello') { 'hello' => val === 'hello' } match (val = 1) { 1 => val === 1 } == array matching == match (val = [ 1, 2 ]) { [a, b] => [ 1, 2 ] } match (val = [ 1, 2, 3, 4, 5 ]) { [a, 2, ..rest] => a === 1 && rest === [ 3, 4, 5 ] } == compound matching == match (val = { x: { y: 2 } }) { {x, x: {y}} => x === { y: 2 } && y === 2 // (follows destr. syntax) } match (val = { y: { x: 1 } }) { {y: {x}} => x === 1 // (y is unbound) } match (val = { y: { x: 'hello' } }) { {y: {x: 'hello'}} => x === 'hello' } match (val = { y: Foo { x: 1, y: 2 } }) { {y: Foo {x}} => x === 1 } == guards == match (val = [ 3, 2, 1 ]) { [...rest] if rest[0] === 3 => rest === [ 3, 2, 1 ] } -
zkat created this gist
Mar 21, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,48 @@ // I believe all of the below are fully nestable const foo = match x { // Basic concepts. This is all you need to actually know. `Symbol.match` // operates on this Object {}-style protocol in all cases. // object-style destructuring. x, y are bound. Just {val}: val None {}: ... // (see below for alternative None) // bind value to x, match with matcher. Toplevel reduntant; used for nesting x@Matcher {}: x // Syntax sugar extensions, all statically compile down to above. // Desugars to `String/Number/RegExp {}` but compilers can special-case. 'literal': ... 42: ... /regexp/: ... x@/regexp/: ... // Regexp has Array-like matcher. // This desugars to RegExp {length: 2, 0: a, b: 2} /regexp/ [a, b]: ... // Desugars to Object {x, y} {x, y}: ... // Desugars to Array {length: 2, 0: a, 1: b}. Array[Symbol.match] method enforces the "fail if wrong length" [a, b]: ... // Desugars to TypedArray {length: 2, 0: a, 1: b} TypedArray [a, b]: ... // When @ is present, {} becomes optional: x@Matcher: ... @None: ... // Plain variable matches without running a matcher. // imo, this doesn't need a first-class fallthrough. I think encouraging // people to have "fully qualified" matchers is important and // regular variables can be used for fallthrough just fine // Please no * nonsense plz _: 'you need monads' other: console.log(other) // lol // equality matches done with guards other if other === 1 }