From 6a17c84e019eaf739ac4a37310f7b34fa1f35817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?= Date: Sun, 15 Sep 2019 17:37:36 +0200 Subject: [PATCH] feat: support short syntax for map functions fixes #344 --- docs/functions.md | 16 ++ .../__snapshots__/interpreter.test.js.snap | 163 ++++++++++++++++++ src/__tests__/descriptions.test.js | 1 + src/__tests__/interpreter.test.js | 10 ++ src/parsers/__tests__/mapFunctionCall.js | 78 +++++++++ src/parsers/mapFunctionCall.js | 31 ++++ src/parsers/section.js | 3 +- 7 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 src/parsers/__tests__/mapFunctionCall.js create mode 100644 src/parsers/mapFunctionCall.js diff --git a/docs/functions.md b/docs/functions.md index 67f36b66..2d432538 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -44,6 +44,22 @@ For mapping objects, use a curried function: ``` +### Shorthand for map + +For simplifying map calls there is a syntactic sugar: instead of writing `map $ =>` you can write `~`. + +For example instead of + +``` +["Hello", "World!"] | map $ => {"word": $} +``` + +you can write: + +``` +["Hello", "World!"] | ~ {"word": $} +``` + ## sortBy \ Takes an array as input and sorts it using the supplied function. diff --git a/src/__tests__/__snapshots__/interpreter.test.js.snap b/src/__tests__/__snapshots__/interpreter.test.js.snap index d51b4df1..07eef79e 100644 --- a/src/__tests__/__snapshots__/interpreter.test.js.snap +++ b/src/__tests__/__snapshots__/interpreter.test.js.snap @@ -178,6 +178,10 @@ exports[`interpreter correct target code {'foo': ('bar' | 'baz')} 1`] = `"(funct exports[`interpreter correct target code {each $: (length) in $} 1`] = `"(function(_) { return (function(input) { return (_.objectify(Array.from(((function (input) {return _.map((function(input) {return [input,(_.length(input))]}))(input)})(input)))))})})"`; +exports[`interpreter correct target code ~ " " 1`] = `"(function(_) { return (function(input) { return _.map((function(input) {return \\" \\"}))(input)})})"`; + +exports[`interpreter correct target code ~{"original": $.foo} |~ {"new": $} 1`] = `"(function(_) { return (function(input) { return (function (input) {return _.map((function(input) {return (_.objectify(Array.from([[\\"new\\",input]])))}))(input)})(_.map((function(input) {return (_.objectify(Array.from([[\\"original\\",input.foo]])))}))(input))})})"`; + exports[`interpreter correct target code -(3 * 2) -1 > -1.34 * 3 && true 1`] = `"(function(_) { return (function(input) { return (-((3*2)))-1>(-(1.34))*3&&true})})"`; exports[`interpreter correct target code 3 * 2 -1 != -1.34 * 3 1`] = `"(function(_) { return (function(input) { return 3*2-1!==(-(1.34))*3})})"`; @@ -5503,6 +5507,165 @@ Object { } `; +exports[`interpreter correct target tree ~ " " 1`] = ` +Object { + "status": true, + "value": Object { + "name": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "map", + }, + "right": Object { + "name": "lambda", + "value": Object { + "definition": Object { + "name": "primitive", + "value": "\\" \\"", + }, + "variable": "input", + }, + }, + }, + }, +} +`; + +exports[`interpreter correct target tree ~{"original": $.foo} |~ {"new": $} 1`] = ` +Object { + "status": true, + "value": Object { + "name": "pipe", + "value": Object { + "left": Object { + "name": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "map", + }, + "right": Object { + "name": "lambda", + "value": Object { + "definition": Object { + "end": Object { + "column": 21, + "line": 1, + "offset": 20, + }, + "name": "object", + "start": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "value": Array [ + Object { + "end": Object { + "column": 20, + "line": 1, + "offset": 19, + }, + "name": "simpleList", + "start": Object { + "column": 3, + "line": 1, + "offset": 2, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"original\\"", + }, + Object { + "name": "valueProp", + "value": Object { + "left": Object { + "name": "variable", + "value": "$", + }, + "optional": false, + "right": ".foo", + }, + }, + ], + }, + ], + }, + ], + }, + "variable": "input", + }, + }, + }, + }, + "right": Object { + "name": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "map", + }, + "right": Object { + "name": "lambda", + "value": Object { + "definition": Object { + "end": Object { + "column": 35, + "line": 1, + "offset": 34, + }, + "name": "object", + "start": Object { + "column": 25, + "line": 1, + "offset": 24, + }, + "value": Array [ + Object { + "end": Object { + "column": 34, + "line": 1, + "offset": 33, + }, + "name": "simpleList", + "start": Object { + "column": 26, + "line": 1, + "offset": 25, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"new\\"", + }, + Object { + "name": "variable", + "value": "$", + }, + ], + }, + ], + }, + ], + }, + "variable": "input", + }, + }, + }, + }, + }, + }, +} +`; + exports[`interpreter correct target tree -(3 * 2) -1 > -1.34 * 3 && true 1`] = ` Object { "status": true, diff --git a/src/__tests__/descriptions.test.js b/src/__tests__/descriptions.test.js index e504e132..2dc1ee1b 100644 --- a/src/__tests__/descriptions.test.js +++ b/src/__tests__/descriptions.test.js @@ -22,6 +22,7 @@ const exclude = [ 'collections', 'pipe.js', 'ternary.js', + 'mapFunctionCall.js', 'math' ] const files = fs diff --git a/src/__tests__/interpreter.test.js b/src/__tests__/interpreter.test.js index 7ff6d6d3..7e753233 100644 --- a/src/__tests__/interpreter.test.js +++ b/src/__tests__/interpreter.test.js @@ -143,6 +143,11 @@ x` input: ['Hello', 'World'], output: [' ', ' '] }, + { + sourceCode: `~ " "`, + input: ['Hello', 'World'], + output: [' ', ' '] + }, { sourceCode: `{"original": $.foo} | {"new": $}`, input: { foo: 42 }, @@ -153,6 +158,11 @@ x` input: [{ foo: 42 }, { foo: 'hello' }], output: [{ new: { original: 42 } }, { new: { original: 'hello' } }] }, + { + sourceCode: `~{"original": $.foo} |~ {"new": $}`, + input: [{ foo: 42 }, { foo: 'hello' }], + output: [{ new: { original: 42 } }, { new: { original: 'hello' } }] + }, { sourceCode: `map $ => {"original": $.foo} | "foo"`, input: [{ foo: 42 }, { foo: 'hello' }], diff --git a/src/parsers/__tests__/mapFunctionCall.js b/src/parsers/__tests__/mapFunctionCall.js new file mode 100644 index 00000000..076d6b86 --- /dev/null +++ b/src/parsers/__tests__/mapFunctionCall.js @@ -0,0 +1,78 @@ +import parser from '../mapFunctionCall' + +describe('functionCall parser', () => { + it('doesnt parse ~', () => { + expect(parser.parse('~').status).toBe(false) + }) + it('parses ~ 4', () => { + expect(parser.parse('~ 4').status).toBe(true) + }) + + it('parses ~ $ + 3.1', () => { + expect(parser.parse('~ $ + 3.1').status).toBe(true) + }) + + it('returns correct value', () => { + expect(parser.parse('~ 4: "; "').value).toEqual({ + name: 'functionCall', + value: { + left: { + name: 'identifier', + value: 'map' + }, + + right: { + name: 'lambda', + value: { + variable: 'input', + definition: { + name: 'tuple', + value: [ + { + name: 'primitive', + value: '4' + }, + { + name: 'primitive', + value: '"; "' + } + ] + } + } + } + } + }) + }) + + it('returns correct value', () => { + expect(parser.parse('~ ", ": "; "').value).toEqual({ + name: 'functionCall', + value: { + left: { + name: 'identifier', + value: 'map' + }, + + right: { + name: 'lambda', + value: { + variable: 'input', + definition: { + name: 'tuple', + value: [ + { + name: 'primitive', + value: '", "' + }, + { + name: 'primitive', + value: '"; "' + } + ] + } + } + } + } + }) + }) +}) diff --git a/src/parsers/mapFunctionCall.js b/src/parsers/mapFunctionCall.js new file mode 100644 index 00000000..32f726a5 --- /dev/null +++ b/src/parsers/mapFunctionCall.js @@ -0,0 +1,31 @@ +// @flow + +import P from 'parsimmon' +import crap from './crap' + +import type { FunctionCallNodeType, NodeType } from '../types' + +const FunctionCallParser = P.lazy((): mixed => { + const TupleParser = require('./collections/tuple').default + return P.string('~') + .then(crap) + .then(TupleParser) + .map((definition: NodeType): FunctionCallNodeType => ({ + name: 'functionCall', + value: { + left: { + name: 'identifier', + value: 'map' + }, + right: { + name: 'lambda', + value: { + variable: 'input', + definition + } + } + } + })) +}) + +export default FunctionCallParser diff --git a/src/parsers/section.js b/src/parsers/section.js index ec052496..e6815278 100644 --- a/src/parsers/section.js +++ b/src/parsers/section.js @@ -1,5 +1,6 @@ import P from 'parsimmon' import FunctionCallParser from './functionCall' +import MapFunctionCallParser from './mapFunctionCall' import MathParser from './math/math' -export default P.alt(MathParser, FunctionCallParser) +export default P.alt(MathParser, FunctionCallParser, MapFunctionCallParser)