diff --git a/src/__tests__/__snapshots__/interpreter.test.js.snap b/src/__tests__/__snapshots__/interpreter.test.js.snap index 0dd5c69d..5da3c9cb 100644 --- a/src/__tests__/__snapshots__/interpreter.test.js.snap +++ b/src/__tests__/__snapshots__/interpreter.test.js.snap @@ -14,6 +14,8 @@ exports[`interpreter correct target code $ 2`] = `"(function(_) { return (functi exports[`interpreter correct target code ($x + $foobar | [$, $foobar]) where $x = 3 $foobar = ($x + 1) 1`] = `"(function(_) { return (function(input) { return ((function() {_ = _.assign('x', (3), _); _ = _.assign('foobar', ((_.get('x')+1)), _); return (((function (input) {return [input, _.get('foobar')]})(_.get('x')+_.get('foobar'))))})())})})"`; +exports[`interpreter correct target code ([1] | map $foo | $[0]) where $foo = ($ => 4) 1`] = `"(function(_) { return (function(input) { return ((function() {_ = _.assign('foo', (((function(input) {return 4}))), _); return (((function (input) {return (function (input) {return _.projection(input, [0])})(_.map(_.get('foo'))(input))})([1])))})())})})"`; + exports[`interpreter correct target code (3 + $foobar | [$, $foobar]) where $foobar = $ 1`] = `"(function(_) { return (function(input) { return ((function() {_ = _.assign('foobar', (input), _); return (((function (input) {return [input, _.get('foobar')]})(3+_.get('foobar'))))})())})})"`; exports[`interpreter correct target code (8 + 3.14) * 2 1`] = `"(function(_) { return (function(input) { return (8+3.14)*2})})"`; @@ -97,21 +99,21 @@ exports[`interpreter correct target code 8314 % 34 -3 1`] = `"(function(_) { ret exports[`interpreter correct target code false | true 1`] = `"(function(_) { return (function(input) { return (function (input) {return true})(false)})})"`; -exports[`interpreter correct target code filter $ => .age >= 18 | map $ => .name 1`] = `"(function(_) { return (function(input) { return (function (input) {return (_.map(function(input) {return input.name})(input))})((_.filter(function(input) {return input.age>=18})(input)))})})"`; +exports[`interpreter correct target code filter $ => .age >= 18 | map $ => .name 1`] = `"(function(_) { return (function(input) { return (function (input) {return _.map((function(input) {return input.name}))(input)})(_.filter((function(input) {return input.age>=18}))(input))})})"`; exports[`interpreter correct target code join " " 1`] = `"(function(_) { return (function(input) { return _.join(\\" \\")(input)})})"`; -exports[`interpreter correct target code map $ => {"original": $.foo} | "foo" 1`] = `"(function(_) { return (function(input) { return (function (input) {return \\"foo\\"})((_.map(function(input) {return (_.objectify([[\\"original\\",input.foo]]))})(input)))})})"`; +exports[`interpreter correct target code map $ => {"original": $.foo} | "foo" 1`] = `"(function(_) { return (function(input) { return (function (input) {return \\"foo\\"})(_.map((function(input) {return (_.objectify([[\\"original\\",input.foo]]))}))(input))})})"`; -exports[`interpreter correct target code map $ => {"original": $.foo} | map $=> {"new": $} 1`] = `"(function(_) { return (function(input) { return (function (input) {return (_.map(function(input) {return (_.objectify([[\\"new\\",input]]))})(input))})((_.map(function(input) {return (_.objectify([[\\"original\\",input.foo]]))})(input)))})})"`; +exports[`interpreter correct target code map $ => {"original": $.foo} | map $=> {"new": $} 1`] = `"(function(_) { return (function(input) { return (function (input) {return _.map((function(input) {return (_.objectify([[\\"new\\",input]]))}))(input)})(_.map((function(input) {return (_.objectify([[\\"original\\",input.foo]]))}))(input))})})"`; -exports[`interpreter correct target code map \\" " 1`] = `"(function(_) { return (function(input) { return (_.map(function(input) {return \\" \\"})(input))})})"`; +exports[`interpreter correct target code map \\" " 1`] = `"(function(_) { return (function(input) { return _.map((function(input) {return \\" \\"}))(input)})})"`; exports[`interpreter correct target code null 1`] = `"(function(_) { return (function(input) { return null})})"`; exports[`interpreter correct target code null: true 1`] = `"(function(_) { return (function(input) { return [null,true]})})"`; -exports[`interpreter correct target code sortBy \\.age | map \\.name 1`] = `"(function(_) { return (function(input) { return (function (input) {return (_.map(function(input) {return input.name})(input))})((_.sortBy(function(input) {return input.age})(input)))})})"`; +exports[`interpreter correct target code sortBy \\.age | map \\.name 1`] = `"(function(_) { return (function(input) { return (function (input) {return _.map((function(input) {return input.name}))(input)})(_.sortBy((function(input) {return input.age}))(input))})})"`; exports[`interpreter correct target code true 1`] = `"(function(_) { return (function(input) { return true})})"`; @@ -776,6 +778,138 @@ Object { } `; +exports[`interpreter correct target tree ([1] | map $foo | $[0]) where $foo = ($ => 4) 1`] = ` +Object { + "status": true, + "value": Object { + "end": Object { + "column": 46, + "line": 1, + "offset": 45, + }, + "name": "assignment", + "start": Object { + "column": 1, + "line": 1, + "offset": 0, + }, + "value": Object { + "assignments": Array [ + Array [ + Object { + "name": "identifier", + "value": "foo", + }, + Object { + "name": "parentheses", + "value": Object { + "end": Object { + "column": 45, + "line": 1, + "offset": 44, + }, + "name": "lambda", + "start": Object { + "column": 39, + "line": 1, + "offset": 38, + }, + "value": Object { + "name": "primitive", + "value": "4", + }, + }, + }, + ], + ], + "program": Object { + "name": "parentheses", + "value": Object { + "name": "pipe", + "value": Object { + "left": Object { + "end": Object { + "column": 5, + "line": 1, + "offset": 4, + }, + "name": "list", + "start": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "value": Array [ + Object { + "name": "primitive", + "value": "1", + }, + ], + }, + "right": Object { + "name": "pipe", + "value": Object { + "left": Object { + "name": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "map", + }, + "right": Object { + "end": Object { + "column": 17, + "line": 1, + "offset": 16, + }, + "name": "variable", + "start": Object { + "column": 12, + "line": 1, + "offset": 11, + }, + "value": "$foo", + }, + }, + }, + "right": Object { + "name": "projection", + "value": Object { + "left": Object { + "name": "input", + "value": "$", + }, + "right": Object { + "end": Object { + "column": 23, + "line": 1, + "offset": 22, + }, + "name": "list", + "start": Object { + "column": 20, + "line": 1, + "offset": 19, + }, + "value": Array [ + Object { + "name": "primitive", + "value": "0", + }, + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} +`; + exports[`interpreter correct target tree (3 + $foobar | [$, $foobar]) where $foobar = $ 1`] = ` Object { "status": true, @@ -3881,51 +4015,77 @@ Object { "name": "pipe", "value": Object { "left": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", "value": "filter", }, "right": Object { - "name": "binaryOperation", - "value": Array [ - Object { - "name": "inputProp", - "value": ".age", - }, - Object { - "end": Object { - "column": 20, - "line": 1, - "offset": 19, + "end": Object { + "column": 24, + "line": 1, + "offset": 23, + }, + "name": "lambda", + "start": Object { + "column": 8, + "line": 1, + "offset": 7, + }, + "value": Object { + "name": "binaryOperation", + "value": Array [ + Object { + "name": "inputProp", + "value": ".age", }, - "name": "primitive", - "start": Object { - "column": 18, - "line": 1, - "offset": 17, + Object { + "end": Object { + "column": 20, + "line": 1, + "offset": 19, + }, + "name": "primitive", + "start": Object { + "column": 18, + "line": 1, + "offset": 17, + }, + "value": ">=", }, - "value": ">=", - }, - Object { - "name": "primitive", - "value": "18", - }, - ], + Object { + "name": "primitive", + "value": "18", + }, + ], + }, }, }, }, "right": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", "value": "map", }, "right": Object { - "name": "inputProp", - "value": ".name", + "end": Object { + "column": 40, + "line": 1, + "offset": 39, + }, + "name": "lambda", + "start": Object { + "column": 30, + "line": 1, + "offset": 29, + }, + "value": Object { + "name": "inputProp", + "value": ".name", + }, }, }, }, @@ -3960,7 +4120,7 @@ Object { "name": "pipe", "value": Object { "left": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", @@ -3968,37 +4128,50 @@ Object { }, "right": Object { "end": Object { - "column": 29, + "column": 30, "line": 1, - "offset": 28, + "offset": 29, }, - "name": "object", + "name": "lambda", "start": Object { - "column": 10, + "column": 5, "line": 1, - "offset": 9, + "offset": 4, }, - "value": Array [ - Object { - "name": "tuple", - "value": Array [ - Object { - "name": "primitive", - "value": "\\"original\\"", - }, - Object { - "name": "valueProp", - "value": Object { - "left": Object { - "name": "input", - "value": "$", + "value": Object { + "end": Object { + "column": 29, + "line": 1, + "offset": 28, + }, + "name": "object", + "start": Object { + "column": 10, + "line": 1, + "offset": 9, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"original\\"", + }, + Object { + "name": "valueProp", + "value": Object { + "left": Object { + "name": "input", + "value": "$", + }, + "right": ".foo", }, - "right": ".foo", }, - }, - ], - }, - ], + ], + }, + ], + }, }, }, }, @@ -4018,7 +4191,7 @@ Object { "name": "pipe", "value": Object { "left": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", @@ -4026,42 +4199,55 @@ Object { }, "right": Object { "end": Object { - "column": 29, + "column": 30, "line": 1, - "offset": 28, + "offset": 29, }, - "name": "object", + "name": "lambda", "start": Object { - "column": 10, + "column": 5, "line": 1, - "offset": 9, + "offset": 4, }, - "value": Array [ - Object { - "name": "tuple", - "value": Array [ - Object { - "name": "primitive", - "value": "\\"original\\"", - }, - Object { - "name": "valueProp", - "value": Object { - "left": Object { - "name": "input", - "value": "$", + "value": Object { + "end": Object { + "column": 29, + "line": 1, + "offset": 28, + }, + "name": "object", + "start": Object { + "column": 10, + "line": 1, + "offset": 9, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"original\\"", + }, + Object { + "name": "valueProp", + "value": Object { + "left": Object { + "name": "input", + "value": "$", + }, + "right": ".foo", }, - "right": ".foo", }, - }, - ], - }, - ], + ], + }, + ], + }, }, }, }, "right": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", @@ -4073,27 +4259,40 @@ Object { "line": 1, "offset": 49, }, - "name": "object", + "name": "lambda", "start": Object { - "column": 40, + "column": 36, "line": 1, - "offset": 39, + "offset": 35, }, - "value": Array [ - Object { - "name": "tuple", - "value": Array [ - Object { - "name": "primitive", - "value": "\\"new\\"", - }, - Object { - "name": "input", - "value": "$", - }, - ], + "value": Object { + "end": Object { + "column": 50, + "line": 1, + "offset": 49, }, - ], + "name": "object", + "start": Object { + "column": 40, + "line": 1, + "offset": 39, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"new\\"", + }, + Object { + "name": "input", + "value": "$", + }, + ], + }, + ], + }, }, }, }, @@ -4106,15 +4305,28 @@ exports[`interpreter correct target tree map \\" " 1`] = ` Object { "status": true, "value": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", "value": "map", }, "right": Object { - "name": "primitive", - "value": "\\" \\"", + "end": Object { + "column": 9, + "line": 1, + "offset": 8, + }, + "name": "lambda", + "start": Object { + "column": 5, + "line": 1, + "offset": 4, + }, + "value": Object { + "name": "primitive", + "value": "\\" \\"", + }, }, }, }, @@ -4157,28 +4369,54 @@ Object { "name": "pipe", "value": Object { "left": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", "value": "sortBy", }, "right": Object { - "name": "inputProp", - "value": ".age", + "end": Object { + "column": 14, + "line": 1, + "offset": 13, + }, + "name": "lambda", + "start": Object { + "column": 8, + "line": 1, + "offset": 7, + }, + "value": Object { + "name": "inputProp", + "value": ".age", + }, }, }, }, "right": Object { - "name": "functionCallLambda", + "name": "functionCall", "value": Object { "left": Object { "name": "identifier", "value": "map", }, "right": Object { - "name": "inputProp", - "value": ".name", + "end": Object { + "column": 26, + "line": 1, + "offset": 25, + }, + "name": "lambda", + "start": Object { + "column": 20, + "line": 1, + "offset": 19, + }, + "value": Object { + "name": "inputProp", + "value": ".name", + }, }, }, }, diff --git a/src/__tests__/interpreter.test.js b/src/__tests__/interpreter.test.js index c4bacf5f..5f110cda 100644 --- a/src/__tests__/interpreter.test.js +++ b/src/__tests__/interpreter.test.js @@ -279,6 +279,10 @@ const tests = [ sourceCode: `($x + $foobar | [$, $foobar]) where $x = 3 $foobar = ($x + 1)`, input: 4, output: [7, 4] + }, + { + sourceCode: `([1] | map $foo | $[0]) where $foo = ($ => 4)`, + output: 4 } ] diff --git a/src/generators/__tests__/functionCallLambda.test.js b/src/generators/__tests__/functionCallLambda.test.js deleted file mode 100644 index 5c51ac1a..00000000 --- a/src/generators/__tests__/functionCallLambda.test.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow - -import functionCallLambda from '../functionCallLambda' - -describe('functionCallLambda generator', () => { - it('generates correct code', () => { - const fakeGenerator = ( - { name } // eslint-disable-line flowtype/require-parameter-type - ): string => 'input[0]' - expect( - functionCallLambda(fakeGenerator)({ - name: 'functionCallLambda', - value: { - left: { - name: 'identifier', - value: 'foo' - }, - right: { - name: 'list', - value: [] - } - } - }) - ).toEqual('(_.foo(function(input) {return input[0]})(input))') - }) -}) diff --git a/src/generators/__tests__/lambda.test.js b/src/generators/__tests__/lambda.test.js new file mode 100644 index 00000000..811620f3 --- /dev/null +++ b/src/generators/__tests__/lambda.test.js @@ -0,0 +1,27 @@ +import lambda from '../lambda' + +describe('lambda generator', () => { + it('returns correct code', () => { + expect( + lambda({ + name: 'lambda', + value: { + name: 'primitive', + value: '4' + } + }) + ).toEqual(`(function(input) {return 4})`) + }) + + it('returns correct code', () => { + expect( + lambda({ + name: 'lambda', + value: { + name: 'primitive', + value: '8' + } + }) + ).toEqual(`(function(input) {return 8})`) + }) +}) diff --git a/src/generators/functionCallLambda.js b/src/generators/functionCallLambda.js deleted file mode 100644 index 2e70021f..00000000 --- a/src/generators/functionCallLambda.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow - -import type { - FunctionCallLambdaNodeType, - GeneratedCodeType, - NodeType -} from '../types' - -export default ( - Generator: NodeType => GeneratedCodeType -): (FunctionCallLambdaNodeType => GeneratedCodeType) => ({ - value -}: FunctionCallLambdaNodeType): GeneratedCodeType => - `(_.${value.left.value}(function(input) {return ${Generator( - value.right - )}})(input))` diff --git a/src/generators/generator.js b/src/generators/generator.js index 61fe4477..ab688c7d 100644 --- a/src/generators/generator.js +++ b/src/generators/generator.js @@ -11,10 +11,10 @@ import object from './object' import projection from './projection' import pipe from './pipe' import functionCall from './functionCall' -import functionCallLambda from './functionCallLambda' import binaryOperator from './binaryOperator' import unaryOperator from './unaryOperator' import assignment from './assignment' +import lambda from './lambda' import variable from './variable' import type { NodeType, GeneratedCodeType } from '../types' @@ -44,8 +44,8 @@ const Generator = (node: NodeType): GeneratedCodeType => { return projection(Generator)(node) case 'functionCall': return functionCall(Generator)(node) - case 'functionCallLambda': - return functionCallLambda(Generator)(node) + case 'lambda': + return lambda(node) case 'binaryOperation': return binaryOperator(node) case 'unaryOperation': diff --git a/src/generators/lambda.js b/src/generators/lambda.js new file mode 100644 index 00000000..e2483119 --- /dev/null +++ b/src/generators/lambda.js @@ -0,0 +1,9 @@ +// @flow + +import type { LambdaNodeType, GeneratedCodeType } from '../types' + +export default ({ value }: LambdaNodeType): GeneratedCodeType => + `(function(input) {return ${((): GeneratedCodeType => { + const Generator = require('./generator').default + return Generator(value) + })()}})` diff --git a/src/parsers/__tests__/functionCallLambda.test.js b/src/parsers/__tests__/functionCallLambda.test.js deleted file mode 100644 index 8a9440c6..00000000 --- a/src/parsers/__tests__/functionCallLambda.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import parser from '../functionCallLambda' - -describe('functionCallLambda parser', () => { - it('parses map \\"foo"', () => { - expect(parser.parse('map \\"foo"').status).toBe(true) - }) - it('parses map \\ "foo"', () => { - expect(parser.parse('map \\ "foo"').status).toBe(true) - }) - - it('parses map $ => "foo"', () => { - expect(parser.parse('map $ => "foo"').status).toBe(true) - }) - - it('parses map $ =>"foo"', () => { - expect(parser.parse('map $ =>"foo"').status).toBe(true) - }) - - it('parses map $=>"foo"', () => { - expect(parser.parse('map $=>"foo"').status).toBe(true) - }) - - it('returns correct value', () => { - expect(parser.parse('map \\["a", "b"]').value).toMatchObject({ - name: 'functionCallLambda', - value: { - left: { - name: 'identifier', - value: 'map' - }, - right: { - name: 'list', - value: [ - { - name: 'primitive', - value: '"a"' - }, - { - name: 'primitive', - value: '"b"' - } - ] - } - } - }) - }) -}) diff --git a/src/parsers/__tests__/lambda.test.js b/src/parsers/__tests__/lambda.test.js new file mode 100644 index 00000000..28d2129e --- /dev/null +++ b/src/parsers/__tests__/lambda.test.js @@ -0,0 +1,23 @@ +import parser from '../lambda' + +describe('lambda parser', () => { + it('parses correct string - syntax 1', () => { + expect(parser.parse(`$ => 3 + 4`).status).toBe(true) + expect(parser.parse(`$ => 1`).status).toBe(true) + }) + + it('parses correct string - syntax 2', () => { + expect(parser.parse(`\\3 + 4`).status).toBe(true) + expect(parser.parse(`\\1`).status).toBe(true) + }) + + it('returns correct string', () => { + expect(parser.parse('$ => 4').value).toMatchObject({ + name: 'lambda', + value: { + name: 'primitive', + value: '4' + } + }) + }) +}) diff --git a/src/parsers/assignment.js b/src/parsers/assignment.js index ebdf9e70..1c4166c9 100644 --- a/src/parsers/assignment.js +++ b/src/parsers/assignment.js @@ -1,7 +1,7 @@ // @flow import P from 'parsimmon' -import OperandParser from './operand' +import SectionParser from './section' import IdentifierParser from './identifier' import TupleParser from './tuple/tuple' import crap from './crap' @@ -16,7 +16,7 @@ const Assignment = P.seq( P.string('$').then(IdentifierParser), P.string('=') .trim(crap) - .then(OperandParser) + .then(SectionParser) ) const Assignments = P.sepBy(Assignment, crap) diff --git a/src/parsers/functionCallLambda.js b/src/parsers/functionCallLambda.js deleted file mode 100644 index 1ffb0cc8..00000000 --- a/src/parsers/functionCallLambda.js +++ /dev/null @@ -1,44 +0,0 @@ -// @flow - -import P from 'parsimmon' -import crap from './crap' - -import type { - FunctionCallLambdaNodeType, - NodeType, - IdentifierNodeType -} from '../types' - -const FunctionCallLambdaParser = P.lazy((): mixed => { - const TupleParser = require('./tuple/tuple').default - const IdentifierParser = require('./identifier').default - return P.seq( - IdentifierParser, - crap, - P.alt( - P.string('$') - .then(crap) - .then(P.string('=>')) - .then(crap), - P.string('\\') - ), - crap, - TupleParser - ).map( - ([left, _, __, ___, right]: [ - IdentifierNodeType, - mixed, - mixed, - mixed, - NodeType - ]): FunctionCallLambdaNodeType => ({ - name: 'functionCallLambda', - value: { - left, - right - } - }) - ) -}) - -export default FunctionCallLambdaParser diff --git a/src/parsers/lambda.js b/src/parsers/lambda.js new file mode 100644 index 00000000..2a14ef89 --- /dev/null +++ b/src/parsers/lambda.js @@ -0,0 +1,18 @@ +// @flow + +import P from 'parsimmon' +import crap from './crap' +import TupleParser from './tuple/tuple' + +export default P.alt( + P.string('$') + .then(crap) + .then(P.string('=>')) + .then(crap) + .then(TupleParser), + P.string('\\') + .then(crap) + .then(TupleParser) +) + .trim(crap) + .node('lambda') diff --git a/src/parsers/section.js b/src/parsers/section.js index 1978def5..e474dd43 100644 --- a/src/parsers/section.js +++ b/src/parsers/section.js @@ -1,10 +1,8 @@ import P from 'parsimmon' import FunctionCallParser from './functionCall' -import FunctionCallLambdaParser from './functionCallLambda' import Boolean3Parser from './boolean3' export default P.alt( - FunctionCallLambdaParser, FunctionCallParser, Boolean3Parser ) diff --git a/src/parsers/value.js b/src/parsers/value.js index eaf4b342..9c63099a 100644 --- a/src/parsers/value.js +++ b/src/parsers/value.js @@ -6,11 +6,13 @@ import InputPropParser from './inputProp' import ListParser from './list' import ParenthesesParser from './parentheses' import VariableParser from './variable' +import LambdaParser from './lambda' export default P.alt( PrimitiveParser, InputPropParser, VariableParser, + LambdaParser, InputParser, ListParser, ParenthesesParser diff --git a/src/types.js b/src/types.js index 5b9730a1..c4b7202a 100644 --- a/src/types.js +++ b/src/types.js @@ -96,12 +96,9 @@ export type FunctionCallNodeType = {| } |}; -export type FunctionCallLambdaNodeType = {| - name: 'functionCallLambda', - value: { - left: IdentifierNodeType, - right: NodeType // eslint-disable-line no-use-before-define - } +export type LambdaNodeType = {| + name: 'lambda', + value: NodeType // eslint-disable-line no-use-before-define |}; export type ProjectionNodeType = {| @@ -151,7 +148,7 @@ export type NodeType = | ObjectNodeType | ValuePropNodeType | FunctionCallNodeType - | FunctionCallLambdaNodeType + | LambdaNodeType | OperationNodeType | UnaryOperationNodeType | ProjectionNodeType;