Skip to content
This repository has been archived by the owner on Jul 15, 2021. It is now read-only.

Commit

Permalink
Merge branch 'release/2.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
budnix committed Sep 20, 2017
2 parents eefb589 + 838cdc3 commit 0f0b1c4
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ after_success:
notifications:
email: false
slack:
secure: m548zGgvRHMY3sm2DutezQxxXbNOE8x0HlXHucJixwrastBsj82VfvkXldIuFZW93XGFM2o6oQ9+XJL9rPMDF9G30eTuIqS7u6Gw1UwPPTgH1VUuJej45iGujEnnitiEQS/D0wTxLCh+XD/6B/r+QwIn3ksxkVp+0lzVQof32+fonD1cqJelrKLsbuWikquIfzo/e9yxQD8tRLKNuibtIwj+y7aM1wdppKb97irNSrnwQ5P1yxDeJJtsjipDgwdOgUq5LfDepeWGNXTQCg47W0M2YHG7RGEs3UFbMBcZ1y2dhLmmhOO7vDueIWg7Hr/dQg18bLK3snEJEUZ0dEIRjwuSl6YVw2nuaGVihsXLiaNzgWAADxjCg1IhtDhwY4r8PS0Gmihg+5WoXmRHo9/CXZ4s+n9eXRO6KXOYBb6m9/rhna/t2vOf+WaKHL3wNep9RwhaVRMI91Ijy0LHADGtqRiUUObfbIeQBFWBP7ay7Q/ToIkECM4qkvFmh0ym5K7jay+nysi7OKtQEC3s52ynTToVlWvRfmg2bccLyfU5rOhLMxNT9kuzWLIrXrAm5eArQIXGD1KaWibSVH8g8YSb2vTlLsddWIew/++2/leUqbx25bfABRgTZPdFI/uJ+3EJAD+65e4WkUDH7mfun8/O2+q4Ar/Xjbg1M5D8BYyE1tI=
secure: NfC4fkY0ArSdIK0cR1i6x+GBPWviKltO6Qa1dtxCjJc3bxsiRdyfW8LpixaUC2s189VSREl+AobxruwFSkfcy/JZK2E44cMBysMmbh7QM5vyc8OBPx0ZkG519oBpRJ+br8rPoHrWzcbAGkLmWoA8BCc7zRB4NrDZJG0ltZvrFaU6f5pOVVR2M+AH3u4taafTBYFokRIt9Z6Y4ieEtPR5t2sKCba2MjPIyWOSu4Ve3qeh2GXQvczsIA8Ll/5rP5NrAZ44oBu37RcjRpADtGj9rkknnTP5T6wGgSgk+cRzM/YgMOmP7M4DDu4njcm9A4hfCbFyPQmTVGAay2r9hJLJFMaIMgOiYkkoS/Vnt78kCaQjgkxdG2KfibRaV4FOUMum5f5VvRas5ae2S9dwRJGFPgov9gLH+bV5ChuUjNOHC3t8Dw7CaDhpsEppvE8RuKNbfwVdYE2VWJgrgS9r/PzT4X/gbqsPBrA/z+4kVZ+m4Hb99Fdex3JHRCLVNCI911AWOe9kCn52wAuNd5CxW+L21Rg72zzSC+eTzb0ZyNr+368/ShNB0ly9g060SiuokzwErInR3gE3YTwtv1yt8i/JHK8/Vtrb306uH2jkBAYvv/kRRTMXYb0MpuReQD1UQn9xAT59gHahq0wdMF1UdP/k11qFYCzSEVCXcn1S5SfrTnk=
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ It supports:
* Relative and absolute cell coordinates like `A1`, `$A1`, `A$1`, `$A$1`;
* Build-in variables like `TRUE`, `FALSE`, `NULL`
* Custom variables;
* [TODO] Custom functions/formulas;
* Custom functions/formulas;
* Node and Browser environment.

## API (methods)
Expand Down Expand Up @@ -90,6 +90,37 @@ parser.setVariable('fooBar', 10);
parser.getVariable('fooBar'); // returns `10`
```

### .setFunction(name, fn)

Set custom function which can be visible while parsing formula expression.

```js
parser.setFunction('ADD_5', function(params) {
return params[0] + 5;
});
parser.setFunction('GET_LETTER', function(params) {
var string = params[0];
var index = params[1] - 1;

return string.charAt(index);
});

parser.parse('SUM(4, ADD_5(1))'); // returns `10`
parser.parse('GET_LETTER("Some string", 3)'); // returns `m`
```

### .getFunction(name)

Get custom function.

```js
parser.setFunction('ADD_5', function(params) {
return params[0] + 5;
});

parser.getFunction('ADD_5')([1]); // returns `6`
```

### .SUPPORTED_FORMULAS

List of all supported formulas function.
Expand All @@ -114,6 +145,21 @@ parser.on('callVariable', function(name, done) {
parser.parse('SUM(SIN(foo), COS(foo))'); // returns `1`
```

### 'callFunction' (name, params, done)

Fired while calling function. If function was defined earlier using `setFunction` you can overwrite it's result by this hook.
You can also use this to override result of build-in formulas.

```js
parser.on('callFunction', function(name, params, done) {
if (name === 'ADD_5') {
done(params[0] + 5);
}
});

parser.parse('ADD_5(3)'); // returns `8`
```

### 'callCellValue' (cellCoord, done)

Fired while retrieving cell value by its label (eq: `B3`, `B$3`, `B$3`, `$B$3`).
Expand Down
61 changes: 60 additions & 1 deletion dist/formula-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -12550,7 +12550,9 @@ var Parser = function (_Emitter) {
return _this._callVariable(variable);
},
evaluateByOperator: _evaluateByOperator2['default'],
callFunction: _evaluateByOperator2['default'],
callFunction: function callFunction(name, params) {
return _this._callFunction(name, params);
},
cellValue: function cellValue(value) {
return _this._callCellValue(value);
},
Expand All @@ -12559,6 +12561,7 @@ var Parser = function (_Emitter) {
}
};
_this.variables = Object.create(null);
_this.functions = Object.create(null);

_this.setVariable('TRUE', true).setVariable('FALSE', false).setVariable('NULL', null);
return _this;
Expand Down Expand Up @@ -12655,6 +12658,62 @@ var Parser = function (_Emitter) {
return value;
};

/**
* Set custom function which can be visible while parsing formula expression.
*
* @param {String} name Custom function name.
* @param {Function} fn Custom function.
* @returns {Parser}
*/


Parser.prototype.setFunction = function setFunction(name, fn) {
this.functions[name] = fn;

return this;
};

/**
* Get custom function.
*
* @param {String} name Custom function name.
* @returns {*}
*/


Parser.prototype.getFunction = function getFunction(name) {
return this.functions[name];
};

/**
* Call function with provided params.
*
* @param name Function name.
* @param params Function params.
* @returns {*}
* @private
*/


Parser.prototype._callFunction = function _callFunction(name) {
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];

var fn = this.getFunction(name);
var value = void 0;

if (fn) {
value = fn(params);
}

this.emit('callFunction', name, params, function (newValue) {
if (newValue !== void 0) {
value = newValue;
}
});

return value === void 0 ? (0, _evaluateByOperator2['default'])(name, params) : value;
};

/**
* Retrieve value by its label (`B3`, `B$3`, `B$3`, `$B$3`).
*
Expand Down
6 changes: 3 additions & 3 deletions dist/formula-parser.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hot-formula-parser",
"version": "2.2.0",
"version": "2.3.0",
"description": "Formula parser",
"browser": "dist/formula-parser.js",
"main": "lib/index.js",
Expand Down
51 changes: 50 additions & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ class Parser extends Emitter {
throwError: (errorName) => this._throwError(errorName),
callVariable: (variable) => this._callVariable(variable),
evaluateByOperator,
callFunction: evaluateByOperator,
callFunction: (name, params) => this._callFunction(name, params),
cellValue: (value) => this._callCellValue(value),
rangeValue: (start, end) => this._callRangeValue(start, end),
};
this.variables = Object.create(null);
this.functions = Object.create(null);

this
.setVariable('TRUE', true)
Expand Down Expand Up @@ -115,6 +116,54 @@ class Parser extends Emitter {
return value;
}

/**
* Set custom function which can be visible while parsing formula expression.
*
* @param {String} name Custom function name.
* @param {Function} fn Custom function.
* @returns {Parser}
*/
setFunction(name, fn) {
this.functions[name] = fn;

return this;
}

/**
* Get custom function.
*
* @param {String} name Custom function name.
* @returns {*}
*/
getFunction(name) {
return this.functions[name];
}

/**
* Call function with provided params.
*
* @param name Function name.
* @param params Function params.
* @returns {*}
* @private
*/
_callFunction(name, params = []) {
const fn = this.getFunction(name);
let value;

if (fn) {
value = fn(params);
}

this.emit('callFunction', name, params, (newValue) => {
if (newValue !== void 0) {
value = newValue;
}
});

return value === void 0 ? evaluateByOperator(name, params) : value;
}

/**
* Retrieve value by its label (`B3`, `B$3`, `B$3`, `$B$3`).
*
Expand Down
27 changes: 27 additions & 0 deletions test/integration/parsing/function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Parser from '../../../src/parser';

describe('.parse() custom function', () => {
let parser;

beforeEach(() => {
parser = new Parser();
});
afterEach(() => {
parser = null;
});

it('should evaluate custom functions', () => {
expect(parser.parse('foo()')).toMatchObject({error: '#NAME?', result: null});

parser.setFunction('ADD_5', (params) => params[0] + 5);
parser.setFunction('GET_LETTER', (params) => {
const string = params[0];
const index = params[1] - 1;

return string.charAt(index);
});

expect(parser.parse('SUM(4, ADD_5(1))')).toMatchObject({error: null, result: 10});
expect(parser.parse('GET_LETTER("Some string", 3)')).toMatchObject({error: null, result: 'm'});
});
});
39 changes: 39 additions & 0 deletions test/unit/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ describe('Parser', () => {
});
});

describe('.setFunction()/.getFunction()', () => {
it('should return custom functions', () => {
parser.setFunction('foo', () => 1234);
parser.setFunction('bar', (params) => params[0] + params[1]);

expect(parser.getFunction('foo')()).toBe(1234);
expect(parser.getFunction('bar')([1, 2])).toBe(3);
});
});

describe('._callVariable()', () => {
it('should return error (NAME) when variable not set', () => {
parser.getVariable = jest.fn(() => void 0);
Expand All @@ -153,6 +163,35 @@ describe('Parser', () => {
});
});

describe('._callFunction()', () => {
it('should return error (NAME) when function not set', () => {
expect(() => parser._callFunction('NOT_DEFINED()')).toThrow(/NAME/);
});

it('should call predefined function', () => {
parser.getFunction = jest.fn(() => void 0);

expect(parser._callFunction('SUM', [1, 2])).toBe(3);
});

it('should call custom funciton when it was set', () => {
parser.getFunction = jest.fn(() => (params) => params[0] + 1);

expect(parser._callFunction('ADD_1', [2])).toBe(3);
});

it('should return variable set by event emitter', () => {
parser.getFunction = jest.fn(() => (params) => params[0] + 1);

parser.on('callFunction', (name, params, done) => {
done(name === 'OVERRIDDEN' ? params[0] + 2 : void 0);
});

expect(parser._callFunction('ADD_1', [2])).toBe(3);
expect(parser._callFunction('OVERRIDDEN', [2])).toBe(4);
});
});

describe('._callCellValue()', () => {
it('should return undefined if under specified coordinates data value not exist', () => {
expect(parser._callCellValue('A1')).not.toBeDefined();
Expand Down

0 comments on commit 0f0b1c4

Please sign in to comment.