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

Commit 0f0b1c4

Browse files
committed
Merge branch 'release/2.3.0'
2 parents eefb589 + 838cdc3 commit 0f0b1c4

File tree

8 files changed

+228
-8
lines changed

8 files changed

+228
-8
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ after_success:
2020
notifications:
2121
email: false
2222
slack:
23-
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=
23+
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=

README.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ It supports:
4343
* Relative and absolute cell coordinates like `A1`, `$A1`, `A$1`, `$A$1`;
4444
* Build-in variables like `TRUE`, `FALSE`, `NULL`
4545
* Custom variables;
46-
* [TODO] Custom functions/formulas;
46+
* Custom functions/formulas;
4747
* Node and Browser environment.
4848

4949
## API (methods)
@@ -90,6 +90,37 @@ parser.setVariable('fooBar', 10);
9090
parser.getVariable('fooBar'); // returns `10`
9191
```
9292

93+
### .setFunction(name, fn)
94+
95+
Set custom function which can be visible while parsing formula expression.
96+
97+
```js
98+
parser.setFunction('ADD_5', function(params) {
99+
return params[0] + 5;
100+
});
101+
parser.setFunction('GET_LETTER', function(params) {
102+
var string = params[0];
103+
var index = params[1] - 1;
104+
105+
return string.charAt(index);
106+
});
107+
108+
parser.parse('SUM(4, ADD_5(1))'); // returns `10`
109+
parser.parse('GET_LETTER("Some string", 3)'); // returns `m`
110+
```
111+
112+
### .getFunction(name)
113+
114+
Get custom function.
115+
116+
```js
117+
parser.setFunction('ADD_5', function(params) {
118+
return params[0] + 5;
119+
});
120+
121+
parser.getFunction('ADD_5')([1]); // returns `6`
122+
```
123+
93124
### .SUPPORTED_FORMULAS
94125

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

148+
### 'callFunction' (name, params, done)
149+
150+
Fired while calling function. If function was defined earlier using `setFunction` you can overwrite it's result by this hook.
151+
You can also use this to override result of build-in formulas.
152+
153+
```js
154+
parser.on('callFunction', function(name, params, done) {
155+
if (name === 'ADD_5') {
156+
done(params[0] + 5);
157+
}
158+
});
159+
160+
parser.parse('ADD_5(3)'); // returns `8`
161+
```
162+
117163
### 'callCellValue' (cellCoord, done)
118164

119165
Fired while retrieving cell value by its label (eq: `B3`, `B$3`, `B$3`, `$B$3`).

dist/formula-parser.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12550,7 +12550,9 @@ var Parser = function (_Emitter) {
1255012550
return _this._callVariable(variable);
1255112551
},
1255212552
evaluateByOperator: _evaluateByOperator2['default'],
12553-
callFunction: _evaluateByOperator2['default'],
12553+
callFunction: function callFunction(name, params) {
12554+
return _this._callFunction(name, params);
12555+
},
1255412556
cellValue: function cellValue(value) {
1255512557
return _this._callCellValue(value);
1255612558
},
@@ -12559,6 +12561,7 @@ var Parser = function (_Emitter) {
1255912561
}
1256012562
};
1256112563
_this.variables = Object.create(null);
12564+
_this.functions = Object.create(null);
1256212565

1256312566
_this.setVariable('TRUE', true).setVariable('FALSE', false).setVariable('NULL', null);
1256412567
return _this;
@@ -12655,6 +12658,62 @@ var Parser = function (_Emitter) {
1265512658
return value;
1265612659
};
1265712660

12661+
/**
12662+
* Set custom function which can be visible while parsing formula expression.
12663+
*
12664+
* @param {String} name Custom function name.
12665+
* @param {Function} fn Custom function.
12666+
* @returns {Parser}
12667+
*/
12668+
12669+
12670+
Parser.prototype.setFunction = function setFunction(name, fn) {
12671+
this.functions[name] = fn;
12672+
12673+
return this;
12674+
};
12675+
12676+
/**
12677+
* Get custom function.
12678+
*
12679+
* @param {String} name Custom function name.
12680+
* @returns {*}
12681+
*/
12682+
12683+
12684+
Parser.prototype.getFunction = function getFunction(name) {
12685+
return this.functions[name];
12686+
};
12687+
12688+
/**
12689+
* Call function with provided params.
12690+
*
12691+
* @param name Function name.
12692+
* @param params Function params.
12693+
* @returns {*}
12694+
* @private
12695+
*/
12696+
12697+
12698+
Parser.prototype._callFunction = function _callFunction(name) {
12699+
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
12700+
12701+
var fn = this.getFunction(name);
12702+
var value = void 0;
12703+
12704+
if (fn) {
12705+
value = fn(params);
12706+
}
12707+
12708+
this.emit('callFunction', name, params, function (newValue) {
12709+
if (newValue !== void 0) {
12710+
value = newValue;
12711+
}
12712+
});
12713+
12714+
return value === void 0 ? (0, _evaluateByOperator2['default'])(name, params) : value;
12715+
};
12716+
1265812717
/**
1265912718
* Retrieve value by its label (`B3`, `B$3`, `B$3`, `$B$3`).
1266012719
*

dist/formula-parser.min.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hot-formula-parser",
3-
"version": "2.2.0",
3+
"version": "2.3.0",
44
"description": "Formula parser",
55
"browser": "dist/formula-parser.js",
66
"main": "lib/index.js",

src/parser.js

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ class Parser extends Emitter {
2020
throwError: (errorName) => this._throwError(errorName),
2121
callVariable: (variable) => this._callVariable(variable),
2222
evaluateByOperator,
23-
callFunction: evaluateByOperator,
23+
callFunction: (name, params) => this._callFunction(name, params),
2424
cellValue: (value) => this._callCellValue(value),
2525
rangeValue: (start, end) => this._callRangeValue(start, end),
2626
};
2727
this.variables = Object.create(null);
28+
this.functions = Object.create(null);
2829

2930
this
3031
.setVariable('TRUE', true)
@@ -115,6 +116,54 @@ class Parser extends Emitter {
115116
return value;
116117
}
117118

119+
/**
120+
* Set custom function which can be visible while parsing formula expression.
121+
*
122+
* @param {String} name Custom function name.
123+
* @param {Function} fn Custom function.
124+
* @returns {Parser}
125+
*/
126+
setFunction(name, fn) {
127+
this.functions[name] = fn;
128+
129+
return this;
130+
}
131+
132+
/**
133+
* Get custom function.
134+
*
135+
* @param {String} name Custom function name.
136+
* @returns {*}
137+
*/
138+
getFunction(name) {
139+
return this.functions[name];
140+
}
141+
142+
/**
143+
* Call function with provided params.
144+
*
145+
* @param name Function name.
146+
* @param params Function params.
147+
* @returns {*}
148+
* @private
149+
*/
150+
_callFunction(name, params = []) {
151+
const fn = this.getFunction(name);
152+
let value;
153+
154+
if (fn) {
155+
value = fn(params);
156+
}
157+
158+
this.emit('callFunction', name, params, (newValue) => {
159+
if (newValue !== void 0) {
160+
value = newValue;
161+
}
162+
});
163+
164+
return value === void 0 ? evaluateByOperator(name, params) : value;
165+
}
166+
118167
/**
119168
* Retrieve value by its label (`B3`, `B$3`, `B$3`, `$B$3`).
120169
*

test/integration/parsing/function.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Parser from '../../../src/parser';
2+
3+
describe('.parse() custom function', () => {
4+
let parser;
5+
6+
beforeEach(() => {
7+
parser = new Parser();
8+
});
9+
afterEach(() => {
10+
parser = null;
11+
});
12+
13+
it('should evaluate custom functions', () => {
14+
expect(parser.parse('foo()')).toMatchObject({error: '#NAME?', result: null});
15+
16+
parser.setFunction('ADD_5', (params) => params[0] + 5);
17+
parser.setFunction('GET_LETTER', (params) => {
18+
const string = params[0];
19+
const index = params[1] - 1;
20+
21+
return string.charAt(index);
22+
});
23+
24+
expect(parser.parse('SUM(4, ADD_5(1))')).toMatchObject({error: null, result: 10});
25+
expect(parser.parse('GET_LETTER("Some string", 3)')).toMatchObject({error: null, result: 'm'});
26+
});
27+
});

test/unit/parser.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ describe('Parser', () => {
127127
});
128128
});
129129

130+
describe('.setFunction()/.getFunction()', () => {
131+
it('should return custom functions', () => {
132+
parser.setFunction('foo', () => 1234);
133+
parser.setFunction('bar', (params) => params[0] + params[1]);
134+
135+
expect(parser.getFunction('foo')()).toBe(1234);
136+
expect(parser.getFunction('bar')([1, 2])).toBe(3);
137+
});
138+
});
139+
130140
describe('._callVariable()', () => {
131141
it('should return error (NAME) when variable not set', () => {
132142
parser.getVariable = jest.fn(() => void 0);
@@ -153,6 +163,35 @@ describe('Parser', () => {
153163
});
154164
});
155165

166+
describe('._callFunction()', () => {
167+
it('should return error (NAME) when function not set', () => {
168+
expect(() => parser._callFunction('NOT_DEFINED()')).toThrow(/NAME/);
169+
});
170+
171+
it('should call predefined function', () => {
172+
parser.getFunction = jest.fn(() => void 0);
173+
174+
expect(parser._callFunction('SUM', [1, 2])).toBe(3);
175+
});
176+
177+
it('should call custom funciton when it was set', () => {
178+
parser.getFunction = jest.fn(() => (params) => params[0] + 1);
179+
180+
expect(parser._callFunction('ADD_1', [2])).toBe(3);
181+
});
182+
183+
it('should return variable set by event emitter', () => {
184+
parser.getFunction = jest.fn(() => (params) => params[0] + 1);
185+
186+
parser.on('callFunction', (name, params, done) => {
187+
done(name === 'OVERRIDDEN' ? params[0] + 2 : void 0);
188+
});
189+
190+
expect(parser._callFunction('ADD_1', [2])).toBe(3);
191+
expect(parser._callFunction('OVERRIDDEN', [2])).toBe(4);
192+
});
193+
});
194+
156195
describe('._callCellValue()', () => {
157196
it('should return undefined if under specified coordinates data value not exist', () => {
158197
expect(parser._callCellValue('A1')).not.toBeDefined();

0 commit comments

Comments
 (0)