From 26494630b3800e686aa7afa88325663c56e007d4 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 19 Oct 2018 21:24:07 +0200 Subject: [PATCH] feat: allow for functions with no extra arguments --- .../__snapshots__/interpreter.test.js.snap | 190 ++++++++++++++++++ src/__tests__/builtins.test.js | 15 +- src/__tests__/interpreter.test.js | 8 + src/builtins.js | 7 +- src/generators/__tests__/functionCall.test.js | 22 +- src/generators/functionCall.js | 4 +- src/parsers/__tests__/functionCall.test.js | 17 ++ src/parsers/__tests__/identifier.test.js | 12 ++ src/parsers/functionCall.js | 4 +- src/parsers/identifier.js | 12 +- src/types.js | 2 +- 11 files changed, 276 insertions(+), 17 deletions(-) diff --git a/src/__tests__/__snapshots__/interpreter.test.js.snap b/src/__tests__/__snapshots__/interpreter.test.js.snap index 5da3c9cb..b58b5be0 100644 --- a/src/__tests__/__snapshots__/interpreter.test.js.snap +++ b/src/__tests__/__snapshots__/interpreter.test.js.snap @@ -14,6 +14,10 @@ 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, 2] | foo) where $foo = $reverse 1`] = `"(function(_) { return (function(input) { return ((function() {_ = _.assign('foo', (_.get('reverse')), _); return (((function (input) {return _.foo(input)})([1, 2])))})())})})"`; + +exports[`interpreter correct target code ([1] | foo 4) where $foo = ($ => $ => 4) 1`] = `"(function(_) { return (function(input) { return ((function() {_ = _.assign('foo', (((function(input) {return (function(input) {return 4})}))), _); return (((function (input) {return _.foo(4)(input)})([1])))})())})})"`; + 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'))))})())})})"`; @@ -778,6 +782,192 @@ Object { } `; +exports[`interpreter correct target tree ([1, 2] | foo) where $foo = $reverse 1`] = ` +Object { + "status": true, + "value": Object { + "end": Object { + "column": 37, + "line": 1, + "offset": 36, + }, + "name": "assignment", + "start": Object { + "column": 1, + "line": 1, + "offset": 0, + }, + "value": Object { + "assignments": Array [ + Array [ + Object { + "name": "identifier", + "value": "foo", + }, + Object { + "end": Object { + "column": 37, + "line": 1, + "offset": 36, + }, + "name": "variable", + "start": Object { + "column": 29, + "line": 1, + "offset": 28, + }, + "value": "$reverse", + }, + ], + ], + "program": Object { + "name": "parentheses", + "value": Object { + "name": "pipe", + "value": Object { + "left": Object { + "end": Object { + "column": 8, + "line": 1, + "offset": 7, + }, + "name": "list", + "start": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "value": Array [ + Object { + "name": "primitive", + "value": "1", + }, + Object { + "name": "primitive", + "value": "2", + }, + ], + }, + "right": Object { + "name": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "foo", + }, + "right": null, + }, + }, + }, + }, + }, + }, + }, +} +`; + +exports[`interpreter correct target tree ([1] | foo 4) where $foo = ($ => $ => 4) 1`] = ` +Object { + "status": true, + "value": Object { + "end": Object { + "column": 41, + "line": 1, + "offset": 40, + }, + "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": 40, + "line": 1, + "offset": 39, + }, + "name": "lambda", + "start": Object { + "column": 29, + "line": 1, + "offset": 28, + }, + "value": Object { + "end": Object { + "column": 40, + "line": 1, + "offset": 39, + }, + "name": "lambda", + "start": Object { + "column": 34, + "line": 1, + "offset": 33, + }, + "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": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "foo", + }, + "right": Object { + "name": "primitive", + "value": "4", + }, + }, + }, + }, + }, + }, + }, + }, +} +`; + exports[`interpreter correct target tree ([1] | map $foo | $[0]) where $foo = ($ => 4) 1`] = ` Object { "status": true, diff --git a/src/__tests__/builtins.test.js b/src/__tests__/builtins.test.js index 6c7f33d8..780fe7ec 100644 --- a/src/__tests__/builtins.test.js +++ b/src/__tests__/builtins.test.js @@ -1,6 +1,6 @@ import builtIns from '../builtins' -const { projection, join, map, sortBy, filter, get, assign } = builtIns +const { projection, join, map, sortBy, filter, get, assign, reverse } = builtIns describe('built ins', () => { describe('projection', () => { @@ -67,6 +67,19 @@ describe('built ins', () => { }) }) + describe('reverse', () => { + it('returns correct value', () => { + expect(reverse(['c', 'a', 'b'])).toEqual(['b', 'a', 'c']) + }) + + it('does not mutate original array', () => { + const input = ['c', 'a', 'b'] + const originalValue = input.slice() + reverse(input) + expect(input).toEqual(originalValue) + }) + }) + describe('filter', () => { it('returns correct value', () => { const foo = a => a.foo >= 0; // eslint-disable-line diff --git a/src/__tests__/interpreter.test.js b/src/__tests__/interpreter.test.js index 5f110cda..8361bc08 100644 --- a/src/__tests__/interpreter.test.js +++ b/src/__tests__/interpreter.test.js @@ -283,6 +283,14 @@ const tests = [ { sourceCode: `([1] | map $foo | $[0]) where $foo = ($ => 4)`, output: 4 + }, + { + sourceCode: `([1] | foo 4) where $foo = ($ => $ => 4)`, + output: 4 + }, + { + sourceCode: `([1, 2] | foo) where $foo = $reverse`, + output: [2, 1] } ] diff --git a/src/builtins.js b/src/builtins.js index 382942ee..03451220 100644 --- a/src/builtins.js +++ b/src/builtins.js @@ -56,13 +56,14 @@ export default { input: Array ): Array => input.filter(f), get: function (variable: string): mixed { - return this[`__var__${variable}`] + return this[variable] }, assign: function ( variable: string, value: mixed, context: {[string]: mixed} ): {[string]: mixed} { - return Object.assign({}, context, { [`__var__${variable}`]: value }) - } + return Object.assign({}, context, { [variable]: value }) + }, + reverse: (input: Array): Array => input.slice().reverse() } diff --git a/src/generators/__tests__/functionCall.test.js b/src/generators/__tests__/functionCall.test.js index 2351d589..60ce6a4d 100644 --- a/src/generators/__tests__/functionCall.test.js +++ b/src/generators/__tests__/functionCall.test.js @@ -2,11 +2,12 @@ import functionCall from '../functionCall' +const fakeGenerator = ( + { name } // eslint-disable-line flowtype/require-parameter-type +): string => '[]' + describe('functionCall generator', () => { it('generates correct code', () => { - const fakeGenerator = ( - { name } // eslint-disable-line flowtype/require-parameter-type - ): string => '[]' expect( functionCall(fakeGenerator)({ name: 'functionCall', @@ -23,4 +24,19 @@ describe('functionCall generator', () => { }) ).toEqual('_.foo([])(input)') }) + + it('generates correct code - no args', () => { + expect( + functionCall(fakeGenerator)({ + name: 'functionCall', + value: { + left: { + name: 'identifier', + value: 'foo' + }, + right: null + } + }) + ).toEqual('_.foo(input)') + }) }) diff --git a/src/generators/functionCall.js b/src/generators/functionCall.js index 3580abf6..32d63484 100644 --- a/src/generators/functionCall.js +++ b/src/generators/functionCall.js @@ -7,4 +7,6 @@ export default ( ): (FunctionCallNodeType => GeneratedCodeType) => ({ value }: FunctionCallNodeType): GeneratedCodeType => - `_.${value.left.value}(${Generator(value.right)})(input)` + value.right + ? `_.${value.left.value}(${Generator(value.right)})(input)` + : `_.${value.left.value}(input)` diff --git a/src/parsers/__tests__/functionCall.test.js b/src/parsers/__tests__/functionCall.test.js index 33145bef..c4233637 100644 --- a/src/parsers/__tests__/functionCall.test.js +++ b/src/parsers/__tests__/functionCall.test.js @@ -5,6 +5,10 @@ describe('functionCall parser', () => { expect(parser.parse('join ", "').status).toBe(true) }) + it('parses replace', () => { + expect(parser.parse('replace').status).toBe(true) + }) + it('returns correct value', () => { expect(parser.parse('replace ", ": "; "').value).toEqual({ name: 'functionCall', @@ -29,4 +33,17 @@ describe('functionCall parser', () => { } }) }) + + it('returns correct value', () => { + expect(parser.parse('reverse').value).toEqual({ + name: 'functionCall', + value: { + left: { + name: 'identifier', + value: 'reverse' + }, + right: null + } + }) + }) }) diff --git a/src/parsers/__tests__/identifier.test.js b/src/parsers/__tests__/identifier.test.js index 7026ef74..b36164d0 100644 --- a/src/parsers/__tests__/identifier.test.js +++ b/src/parsers/__tests__/identifier.test.js @@ -4,6 +4,9 @@ describe('identifier parser', () => { it('parses foobar_123', () => { expect(parser.parse('foobar_123').status).toBe(true) }) + it('parses false_', () => { + expect(parser.parse('false_').status).toBe(true) + }) it('returns correct value', () => { expect(parser.parse('foobar_123').value).toEqual({ name: 'identifier', @@ -19,4 +22,13 @@ describe('identifier parser', () => { it('does not parse 0foo', () => { expect(parser.parse('0foo').status).toBe(false) }) + it('does not parse false', () => { + expect(parser.parse('false').status).toBe(false) + }) + it('does not parse true', () => { + expect(parser.parse('true').status).toBe(false) + }) + it('does not parse null', () => { + expect(parser.parse('null').status).toBe(false) + }) }) diff --git a/src/parsers/functionCall.js b/src/parsers/functionCall.js index 2aac24c1..724c1229 100644 --- a/src/parsers/functionCall.js +++ b/src/parsers/functionCall.js @@ -12,7 +12,7 @@ import type { const FunctionCallParser = P.lazy((): mixed => { const TupleParser = require('./tuple/tuple').default const IdentifierParser = require('./identifier').default - return P.seq(IdentifierParser, crap, TupleParser).map( + return P.seq(IdentifierParser, crap, TupleParser.atMost(1)).map( ([left, _, right]: [ IdentifierNodeType, mixed, @@ -21,7 +21,7 @@ const FunctionCallParser = P.lazy((): mixed => { name: 'functionCall', value: { left, - right + right: right[0] || null } }) ) diff --git a/src/parsers/identifier.js b/src/parsers/identifier.js index 6f9ce926..ac41e9b8 100644 --- a/src/parsers/identifier.js +++ b/src/parsers/identifier.js @@ -3,9 +3,9 @@ import P from 'parsimmon' import type { IdentifierNodeType } from '../types' -export default P.regexp(/[a-zA-Z][0-9a-zA-Z_$]*/).map( - (value: string): IdentifierNodeType => ({ - name: 'identifier', - value - }) -) +export default P.regexp( + /((?!null|false|true)[a-zA-Z][0-9a-zA-Z_$]*|(null|false|true)[0-9a-zA-Z_$])/ +).map((value: string): IdentifierNodeType => ({ + name: 'identifier', + value +})) diff --git a/src/types.js b/src/types.js index c4b7202a..44254d6f 100644 --- a/src/types.js +++ b/src/types.js @@ -92,7 +92,7 @@ export type FunctionCallNodeType = {| name: 'functionCall', value: { left: IdentifierNodeType, - right: NodeType // eslint-disable-line no-use-before-define + right: ?NodeType // eslint-disable-line no-use-before-define } |};