├── .gitignore
├── API.md
├── Intro101.md
├── README.md
├── build
├── webpack.config.dev.js
├── webpack.config.dist.dev.js
└── webpack.config.dist.min.js
├── docs
├── ROADMAP.md
└── design
│ ├── ErrorHandling.md
│ ├── IDE.md
│ ├── Misc.md
│ └── UnitTest.md
├── gen
├── ReportFormulaLexer.interp
├── ReportFormulaLexer.java
└── ReportFormulaLexer.tokens
├── gulpfile.js
├── karma.conf.js
├── package-lock.json
├── package.json
├── src
├── base
│ ├── ArrayUtils.js
│ ├── StringBuffer.js
│ ├── StringUtils.js
│ ├── color
│ │ └── colorTest.js
│ ├── common
│ │ ├── assert.js
│ │ └── types.js
│ └── debounce.js
├── index.js
├── jsconfig.json
├── platform
│ ├── contrib
│ │ └── errorHandler
│ │ │ ├── BaseErrorHandler.js
│ │ │ ├── EditorErrorHandler.js
│ │ │ └── test
│ │ │ └── MockEditorErrorHandler.js
│ ├── formula
│ │ ├── FormulaEngine.js
│ │ ├── FormulaLanguageService.js
│ │ ├── autofix
│ │ │ └── AutoFixFormulaTool.js
│ │ ├── cellAddressParts
│ │ │ ├── common
│ │ │ │ └── CellAddressParts.js
│ │ │ └── test
│ │ │ │ ├── CellAddressGrammar.test.js
│ │ │ │ └── CellAddressParts.test.js
│ │ ├── cellDependency
│ │ │ ├── CellDependencyVisitor.js
│ │ │ ├── DependencyBuilder.js
│ │ │ ├── DependencyFinder.js
│ │ │ ├── DependencyGraph.js
│ │ │ ├── DependencyTransformer.js
│ │ │ ├── FormulaAstVisitor.js
│ │ │ ├── common
│ │ │ │ ├── ElementaryCircuitsSearch.js
│ │ │ │ ├── Graph.js
│ │ │ │ └── StrongConnectedComponents.js
│ │ │ └── test
│ │ │ │ ├── ElementaryCircuitsSearch.test.js
│ │ │ │ └── Graph.test.js
│ │ ├── cellEvaluation
│ │ │ ├── CellValueProviderProxy.js
│ │ │ ├── EvaluationErrors.js
│ │ │ ├── Evaluator.js
│ │ │ └── FormulaEvaluationVisitor.js
│ │ ├── core
│ │ │ ├── ASTWalker.js
│ │ │ ├── EditorTokensVisitor.js
│ │ │ ├── SingleFormulaAST.js
│ │ │ ├── SingleFormulaContext.js
│ │ │ ├── SingleFormulaCore.js
│ │ │ └── syntax.js
│ │ ├── error
│ │ │ ├── BaseErrorListener.js
│ │ │ ├── FormulaExceptions.js
│ │ │ ├── LexerErrorListener.js
│ │ │ └── ParserErrorListener.js
│ │ ├── generation
│ │ │ └── AutoFillTransformer.js
│ │ ├── grammar
│ │ │ ├── .antlr
│ │ │ │ ├── CellAddress.interp
│ │ │ │ ├── CellAddressLexer.interp
│ │ │ │ ├── ReportFormulaLexer.interp
│ │ │ │ └── ReportFormulaParser.interp
│ │ │ ├── CellAddress.g4
│ │ │ ├── ReportFormulaLexer.g4
│ │ │ ├── ReportFormulaParser.g4
│ │ │ └── formulatree.md
│ │ ├── intelliSense
│ │ │ └── formulaSignatureHelp.js
│ │ ├── runtime
│ │ │ ├── CellAddress.tokens
│ │ │ ├── CellAddressLexer.js
│ │ │ ├── CellAddressLexer.tokens
│ │ │ ├── CellAddressListener.js
│ │ │ ├── CellAddressParser.js
│ │ │ ├── CellAddressVisitor.js
│ │ │ ├── ReportFormulaLexer.js
│ │ │ ├── ReportFormulaLexer.tokens
│ │ │ ├── ReportFormulaParser.js
│ │ │ ├── ReportFormulaParser.tokens
│ │ │ ├── ReportFormulaParserListener.js
│ │ │ └── ReportFormulaParserVisitor.js
│ │ └── test
│ │ │ ├── DependencyBuilder.test.js
│ │ │ ├── DependencyTransformer.test.js
│ │ │ ├── Evaluator.test.js
│ │ │ ├── FormulaParser.test.js
│ │ │ ├── SingleFormulaAST.test.js
│ │ │ ├── SingleFormulaCore.test.js
│ │ │ └── TestUtilsASTNodeBuilder.js
│ ├── registry
│ │ └── common
│ │ │ └── platform.js
│ └── theme
│ │ └── colorRegistry.js
└── workbench
│ ├── CellValueProvider.js
│ └── monaco-editor
│ ├── colorsProvider.js
│ ├── languageFeatures.js
│ └── monaco.contribution.js
└── test
├── .mocharc.js
├── FormulaEngine.test.js
├── event-loop-example.js
├── index.html
├── input
└── formula1.txt
└── require-alias
└── loader.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | .nyc_output/
4 | node_modules/
5 | out/
6 | mochawesome-report/
7 | dist/
8 | dist_dev/
9 | coverage/
10 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 | # excel-formula-sdk
2 |
3 | **注意:**
4 | 本项目长期维护,如果您有疑问或功能需求,欢迎在 [issue](https://github.com/yezhang/excel-formula-sdk/issues) 提问。
5 |
6 | ### 基本功能
7 | Excel 公式解析引擎,用于支持公式输入编辑器的智能提示、单元格之间的公式依赖计算、公式的求值、公式语法校验。
8 |
9 |
10 | ### 集成用法
11 | 本 SDK 还可以与各类编辑器(或输入框)、表格组件配合使用。
12 | - [x] 支持公式的语法解析、单元格地址解析、单元格范围解析、单元格公式之间的依赖关系管理等。
13 | - [x] 支持嵌套公式的解析、公式的求值。
14 | - [x] 支持自动补全、函数签名提示、鼠标浮动提示等 IntelliSense 功能所需要的核心信息,包括当前光标所在的函数上下文、当前光标的参数索引。
15 |
16 | 使用场景概述:
17 | - 独立使用。
18 | - 与个人/公司项目中的表格组件、输入框组件配合使用。
19 | - 与 [monaco-editor](https://www.npmjs.com/package/monaco-editor)/[code mirror](https://www.npmjs.com/package/codemirror) 编辑器配合使用,提供智能提示的功能。
20 | - 与 [handsontable](https://www.npmjs.com/package/handsontable) 配合使用,提供公式的联动计算。
21 |
22 |
23 | ### 函数的扩展性
24 | 本 SDK 支持 Excel 内置函数的解析,还支持其他「任何」函数调用语法,可用于扩展支持项目个性化函数。
25 | 例如,项目中需要一个“根据用户ID查询用户名”的函数 getUserNameById('id')。
26 | 并且,输入的公式为 “= getUserNameById('u001')”。该函数及参数是可以解析的。
27 |
28 | ### 变量的扩展性
29 | 本 SDK 支持特殊变量的解析。当变量以 '@' 符号开头时,将解析为特殊变量。
30 | 例如,公式为 '= @lastyear'。
31 |
32 | ### 用于文本格式化(beautify)
33 | 通过生成的解析树、语法树,可以生成美化后的公式文本。
34 |
35 | ## 安装
36 |
37 | CommonJS 规范:
38 | ```js
39 | const formulaSDK = require('excel-formula-sdk');
40 | const { FormulaEngine, WorkBookContext } = formulaSDK;
41 | ```
42 |
43 | `` 引入:
44 | ```html
45 |
46 | ```
47 |
48 | 通过 `` 标签引用,会形成 formulaSDK 全局变量,用于对公式 SDK 执行调用。
49 |
50 | 使用 `formulaSDK.FormulaEngine` 访问公式引擎。
51 | 使用 `formulaSDK.WorkBookContext` 访问工作表的上下文。
52 |
53 |
54 | ## 与表格组件集成API
55 |
56 | **场景:设置单元格公式**
57 | 当用户输入公式按下回车时,执行如下调用:
58 | ```js
59 | const engine = new FormulaEngine();
60 | // 工作簿上下文(用于提供“活动的工作表”,“活动的单元格”等信息),目前仅支持“活动工作表”。
61 | let context = new WorkBookContext('sheet1');
62 | // 当前单元格是 A1,公式为“=B1”,即 A1 = B1
63 | let A1CellRef = { column: 1, row: 1 };
64 | engine.setCellFormula(context, A1CellRef, '=B1');
65 | ```
66 |
67 | 如果输入的公式发生了错误,`engine.setCellFormula` 函数会抛出异常,提示禁止用户提交公式即可。
68 |
69 | **场景:对指定单元格内的公式求值**
70 | 当对单元格 A1 求值时,执行如下调用:
71 | ```js
72 | const engine = new FormulaEngine();
73 | const context = new WorkBookContext('sheet1');
74 | const A1CellRef = { column: 1, row: 1 };
75 |
76 | // 当对公式求值时,engine 会自动回调该对象的方法,用于获取某个单元格的值。
77 | const cellValueProvider = {
78 | getCellValue: function (cell) {
79 | // {column, row} = cell
80 | // column in [1..n]
81 | // row in [1..n]
82 | },
83 | getCellRangeValues: function (cellRange) {
84 | // 本函数用于获取单元格范围的具体数值,返回一维数组。
85 | return [];
86 | },
87 | getCellFloatRangeValues: function (cellRange) {
88 | // 本函数用于获取 “浮动单元格范围” 的具体数值,返回一维数组。
89 | return [];
90 | }
91 | };
92 | engine.prepareToEvaluateTable(cellValueProvider);
93 |
94 | // 求值结果存放至 ret 变量中。
95 | let ret = engine.evaluate(context, A1CellRef);
96 | ```
97 |
98 | **场景 - 自定义公式**
99 | 当内置函数无法满足业务需要时,可以使用自定义函数。
100 |
101 | 注意:自定义函数名区分大小写。
102 |
103 | ``` js
104 | const engine = new FormulaEngine();
105 | const context = new WorkBookContext('sheet1');
106 | const cellValueProvider = {
107 | getCellValue: function (cell) {
108 | if (cell.column === 2 && cell.row === 1) {
109 | return 10; // 假设 B1 单元格的值是数字 10
110 | }
111 | },
112 | customFns: {
113 | MyFn: function (val) {
114 | /**
115 | * 自定义函数,求单元格数字的字符长度。
116 | */
117 | const str = String(val);
118 | return str.length;
119 | }
120 | }
121 | };
122 |
123 | const A1 = {column: 1, row: 1};
124 | engine.setf(context, A1, '=MyFn(B1)');
125 |
126 | let ret = 0;
127 | try {
128 | engine.prepareToEvaluateTable(cellValueProvider);
129 | ret = engine.evaluate(workBookContext, A1);
130 | } catch (e) {
131 | ret = e.getResult();
132 | }
133 | ```
134 |
135 | ret 的值是 2.
136 |
137 |
138 | **场景:单元格全部重算**
139 | 假设表格中已经设置了如下公式: A1 = B1, B1 = C1, B2 = C2.
140 | 当单元格 C1 的数值在表格组件中发生变更时,需要重新计算 B1, A1 处的单元格的值。
141 | ```js
142 | const engine = new FormulaEngine();
143 | const context = new WorkBookContext('sheet1');
144 | const C1 = { column: 3, row: 1 };
145 | engine.reEvaluateAll(context, C1);
146 | ```
147 |
148 | engine.reEvaluateAll(...) 函数可能会抛出计算异常,处理方法同 engine.evaluate(...)。
149 |
150 | **场景:计算失败处理**
151 | 在公式计算失败时,会抛出异常;在异常中包含界面显示需要的文本。
152 | ```js
153 | const engine = new FormulaEngine();
154 | const context = new WorkBookContext('sheet1');
155 | let ret = undefined;
156 | try{
157 | ret = engine.evaluate(context, A1CellRef);
158 | }catch(e){
159 | ret = e.getResult();
160 | }
161 | ```
162 |
163 | **场景:自动填充公式**
164 | 自动填充单元格。当希望在表格组件中,拖动鼠标,从当前选中单元格开始向下填充单元格公式时,
165 | 使用 `engine.autofillDown(context, formulaText, rowNumber)` 函数自动生成新单元格的公式。
166 | 随着鼠标不断向下移动,循环调用该 `engine.autofillDown` 函数,并传递逐渐递增的参数 `rowNumber`。
167 |
168 | 例如,向下 1 行填充公式。
169 | ```js
170 | let f = '= C3 + D3 + SUM(C3:D3) + MIN($C3:D$3)';
171 | let context = new WorkBookContext('sheet1');
172 | let downRet = engine.autofillDown(context, f, 1);
173 | // downRet = '=C4+D4+SUM(C4:D4)+MIN($C4:D$3)';
174 | ```
175 |
176 | **场景:「浮动范围」**
177 | 本公式引擎支持「浮动范围」的概念。公式语法是使用箭头连接两个单元格地址,例如 A1->A1。
178 | 浮动范围与“单元格范围(例如,A1:A1)”的用作类似,都具有覆盖范围内的所有单元格的作用。
179 | 区别是,浮动范围的公式联动方式与单元格范围不同。
180 |
181 | 样例:对于公式 B1 = A1->A1, B2 = A1:A1,
182 | (1)操作方式1:当用户选择 A1 单元格所在的行,执行“增加浮动行”时,B1 处的公式发生联动变化,变更为 "=A1->A2"。
183 | B2 处的公式不发生变更,维持 "=A1:A1"。
184 |
185 | (2)操作方式2:当用户选择 A1 单元格所在的行,执行“插入行”时,B1 处的公式发生平移,变更为 "=A2->A2"。
186 | B2 处的公式发生变更,称为 "=A2:A2"。
187 |
188 | 适用的业务:增值税申报表浮动行。
189 |
190 | ```js
191 | const B1 = { column: 2, row: 1 };
192 | engine.setCellFormula(context, B1, '=SUM(A1->A1)+SUM(A1:A1)');
193 |
194 | const B2 = { column: 2, row: 2 };
195 | engine.setCellFormula(context, B2, '=SUM(A5->A5)');
196 |
197 | // 选中第 1 行,向下增加 2 个浮动行
198 | engine.expandFloatRows(context, 1, 2);
199 | ```
200 |
201 |
202 |
203 | ## 与编辑器组件 monoca-editor 集成API
204 |
205 | ```js
206 | require(['vs/editor/editor.main'], function () {
207 |
208 | formulaSDK.contrib.init(monaco);
209 |
210 | // 定义 monaco editor 的主题颜色
211 | monaco.editor.defineTheme('theme1', {
212 | base: 'vs',
213 | inherit: false,
214 | rules: [{
215 | token: 'basicnumberliteral.formula',
216 | foreground: '1155cc' //#1155cc
217 | },
218 | {
219 | token: 'unexpectedcharacter.formula',
220 | foreground: 'ff0000' //#ff0000
221 | },
222 | {
223 | token: 'fnidentifier.formula',
224 | foreground: '000000' //#000000
225 | },
226 | {
227 | token: 'error.formula',
228 | foreground: 'ff0000'
229 | }
230 | ]
231 | });
232 | var editor = monaco.editor.create(document.getElementById('container'), {
233 | value: [
234 | 'IF(C7 < E7, MIN( ABS(E7 -C7),D7), 0)',
235 | ].join('\n'),
236 | language: 'lang-formula',
237 | theme: 'theme1'
238 | });
239 |
240 | // 初始化编辑器,提供自动补全、函数参数提示等功能。
241 | formulaSDK.contrib.initEditor(monaco, editor);
242 | });
243 |
244 | ```
245 |
246 |
247 | ## 联系方式
248 | zhangyef@yonyou.com
249 |
250 | ## LICENSE
251 | MIT
252 |
253 |
--------------------------------------------------------------------------------
/Intro101.md:
--------------------------------------------------------------------------------
1 | # 公式解析
2 |
3 | 输入:
4 | = A1 + $B1 + 1 + Sheet2!C1 + SUM(B1:C2)
5 | = B1->C2
6 | = @Entity1.member * 50%
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # excel-formula-sdk
2 |
3 |
4 |
5 |
6 |
7 |
8 | **注意:** 本项目接口仍处于不稳定阶段,接口会随时发生调整。
9 |
10 | Excel 公式解析引擎,用于支持公式输入编辑器的智能提示、单元格之间的公式依赖计算、公式的求值、语法校验。
11 |
12 | 本 SDK 可以与各类编辑器(或输入框)、表格组件配合使用。
13 | - [x] 支持公式的语法解析、单元格地址解析、单元格范围解析、单元格公式之间的依赖关系管理等。
14 | - [x] 支持嵌套公式的解析、公式的求值。
15 | - [x] 支持自动补全、函数签名提示、鼠标浮动提示等 IntelliSense 功能所需要的核心信息,包括当前光标所在的函数上下文、当前光标的参数索引。
16 |
17 | 词法、语法解析,使用了 [Antlr4](https://www.antlr.org)。
18 | 数学函数的实际执行,使用了 [formulajs](https://formulajs.info)。
19 |
20 | ## 安装
21 |
22 | 在项目目录下,执行 `npm i excel-formula-sdk -S` 进行安装。
23 | 参考 [https://www.npmjs.com/package/excel-formula-sdk](https://www.npmjs.com/package/excel-formula-sdk)
24 |
25 | ## API
26 | API 文档请参考[这里](./API.md)
27 |
28 | ## 贡献代码
29 | * [提 BUG 或提需求](https://github.com/yezhang/excel-formula-sdk/issues)
--------------------------------------------------------------------------------
/build/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: './src/index.js',
6 | output: {
7 | path: path.resolve(__dirname, '../dist/dev'),
8 | filename: 'formula-sdk.js',
9 | library: 'formulaSDK',
10 | libraryTarget: 'umd',
11 | },
12 | devtool: 'inline-source-map',
13 | module: {
14 | rules: [
15 | {
16 | test: /\.js/,
17 | include: /src/,
18 | exclude: /node_modules|\.test\.js$/,
19 | use: "coverage-istanbul-loader"
20 | }
21 | ]
22 | },
23 | optimization: {
24 | runtimeChunk: true
25 | },
26 | node: { module: "empty", net: "empty", fs: "empty" }, //配置 antlr4 不在 nodejs 环境工作。
27 | resolve: {
28 | alias: {
29 | base: path.resolve(__dirname, '../src/base/'),
30 | platform: path.resolve(__dirname, '../src/platform/'),
31 | workbench: path.resolve(__dirname, '../src/workbench/')
32 | }
33 | },
34 | devServer: {
35 | contentBase: [path.join(__dirname, '../dist'),
36 | path.join(__dirname, '../test'),
37 | path.join(__dirname, '../')],
38 | watchContentBase: true,
39 | compress: true,
40 | port: 9000
41 | }
42 | };
--------------------------------------------------------------------------------
/build/webpack.config.dist.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: './src/index.js',
6 | output: {
7 | path: path.resolve(__dirname, '../dist/excel-formula-sdk/dev'),
8 | filename: 'formula-sdk.js',
9 | library: 'formulaSDK',
10 | libraryTarget: 'umd',
11 | },
12 | devtool: 'source-map',
13 | module: {
14 | rules: [
15 | {
16 | test: /\.js/,
17 | include: /src/,
18 | exclude: /node_modules|\.test\.js$/,
19 | use: "coverage-istanbul-loader"
20 | }
21 | ]
22 | },
23 | optimization: {
24 | runtimeChunk: false
25 | },
26 | node: { module: "empty", net: "empty", fs: "empty" }, //配置 antlr4 不在 nodejs 环境工作。
27 | resolve: {
28 | alias: {
29 | base: path.resolve(__dirname, '../src/base/'),
30 | platform: path.resolve(__dirname, '../src/platform/'),
31 | workbench: path.resolve(__dirname, '../src/workbench/')
32 | }
33 | },
34 | devServer: {
35 | contentBase: [path.join(__dirname, '../dist'),
36 | path.join(__dirname, '../test'),
37 | path.join(__dirname, '../')],
38 | watchContentBase: true,
39 | compress: true,
40 | port: 9000
41 | }
42 | };
--------------------------------------------------------------------------------
/build/webpack.config.dist.min.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | entry: './src/index.js',
6 | output: {
7 | path: path.resolve(__dirname, '../dist/excel-formula-sdk/min'),
8 | filename: 'formula-sdk.js',
9 | library: 'formulaSDK',
10 | libraryTarget: 'umd',
11 | },
12 | module: {
13 | rules: [
14 | {
15 | test: /\.js/,
16 | include: /src/,
17 | exclude: /node_modules|\.test\.js$/,
18 | use: {
19 | loader: 'babel-loader',
20 | options: {
21 | presets: ['@babel/preset-env']
22 | }
23 | }
24 | }
25 | ]
26 | },
27 | optimization: {
28 | runtimeChunk: false
29 | },
30 | node: { module: "empty", net: "empty", fs: "empty" }, //配置 antlr4 不在 nodejs 环境工作。
31 | resolve: {
32 | alias: {
33 | base: path.resolve(__dirname, '../src/base/'),
34 | platform: path.resolve(__dirname, '../src/platform/'),
35 | workbench: path.resolve(__dirname, '../src/workbench/')
36 | }
37 | },
38 | };
--------------------------------------------------------------------------------
/docs/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # 项目路线图
2 |
3 | [ ] 公式计算优化(最小次数调用取数、公式计算效率最高)
4 | [ ] babel
5 | [ ] 函数扩展性
6 | [ ] 变量扩展性
7 | [ ] 独立使用(tokenize、根据位置返回字符)
--------------------------------------------------------------------------------
/docs/design/ErrorHandling.md:
--------------------------------------------------------------------------------
1 | ## 错误处理
2 |
3 | 当有公式的解析错误时,仍然需要使用 visitor 访问解析树。以便支持变量高亮等内容。
4 |
5 | 当有解析错误时,对公式求值会出错。
6 |
7 | 本项目打包后,形成一个独立的组件。在使用时,需要与表格组件、编辑器组件配合。
8 |
9 | 公式解析出错时,使用监听器处理错误,不使用异常。
10 | 公式求值出错时,抛出异常;异常仅用于求值引擎内部使用,用于方便处理不同类型的错误;外部显示计算错误的结果时,使用文本显示。
--------------------------------------------------------------------------------
/docs/design/IDE.md:
--------------------------------------------------------------------------------
1 | ## 开发环境配置
2 |
3 | ### 配置编辑器 VSCode
4 | Go To Defination (Ctrl + Click),在配置相对目录后,无法识别。但是 vscode 的源码(monoca-editor-core),则可以识别。
5 |
6 | 在 src 文件夹下,新建 jsconfig.json 配置文件。
7 | 配置 jsconfig.json 中的 paths 参数。
8 | 配置内容如下:
9 | ```json
10 | {
11 | "compilerOptions": {
12 | "module": "amd",
13 | "target": "es2017",
14 | "moduleResolution": "node",
15 | "baseUrl": ".",
16 | "paths": {
17 | "base/*": [
18 | "./base/*",
19 | ],
20 | "platform/*": [
21 | "./platform/*",
22 | ],
23 | "workbench/*": [
24 | "./workbench/*",
25 | ]
26 | }
27 | },
28 | "include": [
29 | "./base",
30 | "./platform",
31 | "./workbench"
32 | ]
33 | }
34 | ```
35 | 具体配置方法,参考 [vscode jsconfg.json](https://code.visualstudio.com/docs/languages/jsconfig)。
36 |
37 | 配置完成后,重新打开文件夹,使得相对路径生效。
38 |
39 | FAQ:
40 | 如果使用 vscode 调试时,发生 nvm 指定的 node 无法找到的情况,
--------------------------------------------------------------------------------
/docs/design/Misc.md:
--------------------------------------------------------------------------------
1 | # 杂项
2 |
3 | ## 技术选型关注点
4 | 代码中使用了 Map 类型,注意对于浏览器的兼容性。
5 |
6 | Babel 工具中使用了 core-js。
7 |
8 | ## 依赖包
9 | - [core-js](https://github.com/zloirock/core-js),用于支持 Map 数据结构。
10 |
11 | ## 打包
12 | 形成开发包、生产包。
13 | 参考 build/ 目录。
14 |
15 | ## 打包部署
16 | 执行 `npx gulp` 命令,不要执行全局 gulp 命令。
17 |
18 |
--------------------------------------------------------------------------------
/docs/design/UnitTest.md:
--------------------------------------------------------------------------------
1 | ## 单元测试
2 |
3 | 测试文件与源文件放置在同一个目录下。
4 | 在 webpack 中配置 alias,支持路径别名,避免深层相对路径。
5 | 配置 webpack 的 alias 后,需要配置 karma,来支持单元测试。
6 | 在执行 karma 时,会调用 webpack 进行代码编译。
7 |
8 | 单元测试框架使用 mocha。
9 | 在浏览器环境,测试执行器使用 [karma](https://github.com/karma-runner/karma)。
10 |
11 | karma 配置方式:https://www.meziantou.net/test-javascript-code-using-karma-mocha-chai-and-headless-browsers.htm
12 |
13 | 1. 执行基本的测试
14 | package.json
15 | ```javascript
16 | npx karma start karma.conf.js
17 | ```
18 |
19 |
20 | karma.conf.js
21 | ```json
22 | plugins: [
23 | require('karma-mocha'),
24 | require('karma-chrome-launcher'),
25 | require('karma-webpack'),
26 | ],
27 | ```
28 |
29 | 2. 添加 sourcemap
30 |
31 | browsers: ['Chrome'], //ChromeHeadless
32 | singleRun: false, // true,执行后退出
33 |
34 | 配置完成后,可以在浏览器中查看错误信息对应的源码位置信息。
35 |
36 | 3. 添加测试用例执行结果报告(终端)
37 | 使用 mocha 风格的报告:https://www.npmjs.com/package/karma-mocha-reporter
38 |
39 | 4. 添加 coverage 报表(HTML)
40 | 代码覆盖度报告的生成包括两个步骤:测量代码注入、代码执行数据收集。
41 |
42 | 测量代码注入发生在 webpack 打包过程中,使用 webpack 插件:coverage-istanbul-loader
43 | 代码执行数据收集:karma-coverage-istanbul-reporter
44 |
45 | **注意**:当同时使用 mocha 的单元测试执行结果报告、istanbul 的代码覆盖度报告时,
46 | 需要在 karma.conf.js 中配置报告的参数:reporters: ['coverage-istanbul', 'mocha']。
47 | 为了防止 mocha 的终端(Terminal)报告结果格式被破坏,
48 | 在 reporters 数组中,'mocha' 需要放在 'coverage-istanbul' 后面。
49 |
50 | ## require 路径配置
51 | 在 src 路径中,为了防止深层嵌套 ../../../ ,使用了 webpack alias 来处理。
52 | 为了在 NodeJS 中运行 mocha,需要在 NodeJS 中配置相对路径别名。
53 | 实现自定义 require:test/require-alias/loader.js。
54 |
55 |
56 | ## 配置单元测试的调试环境
57 | 使用 VSCode 的 launch.json 配置。
58 |
59 |
--------------------------------------------------------------------------------
/gen/ReportFormulaLexer.tokens:
--------------------------------------------------------------------------------
1 | MultiLineComment=1
2 | SingleLineComment=2
3 | OpenParen=3
4 | CloseParen=4
5 | OpenBracket=5
6 | CloseBracket=6
7 | OpenBrace=7
8 | CloseBrace=8
9 | SemiColon=9
10 | Comma=10
11 | Assign=11
12 | QuestionMark=12
13 | Colon=13
14 | Dollar=14
15 | At=15
16 | Dot=16
17 | PlusPlus=17
18 | MinusMinus=18
19 | Plus=19
20 | Minus=20
21 | Not=21
22 | Multiply=22
23 | Divide=23
24 | Modulus=24
25 | Power=25
26 | Hashtag=26
27 | LessThan=27
28 | MoreThan=28
29 | LessThanEquals=29
30 | GreaterThanEquals=30
31 | Equals_=31
32 | NotEquals=32
33 | And=33
34 | Or=34
35 | ArrowRight=35
36 | If=36
37 | BooleanLiteral=37
38 | NullLiteral=38
39 | CellRangeLiteral=39
40 | CellFloatRangeLiteral=40
41 | CellAddressLiteral=41
42 | BasicNumberLiteral=42
43 | DecimalLiteral=43
44 | HexIntegerLiteral=44
45 | OctalIntegerLiteral=45
46 | OctalIntegerLiteral2=46
47 | BinaryIntegerLiteral=47
48 | BigHexIntegerLiteral=48
49 | BigOctalIntegerLiteral=49
50 | BigBinaryIntegerLiteral=50
51 | BigDecimalIntegerLiteral=51
52 | Identifier=52
53 | StringLiteral=53
54 | WhiteSpaces=54
55 | LineTerminator=55
56 | UnexpectedCharacter=56
57 | '('=3
58 | ')'=4
59 | '['=5
60 | ']'=6
61 | '{'=7
62 | '}'=8
63 | ';'=9
64 | ','=10
65 | '='=11
66 | '?'=12
67 | ':'=13
68 | '$'=14
69 | '@'=15
70 | '.'=16
71 | '++'=17
72 | '--'=18
73 | '+'=19
74 | '-'=20
75 | '!'=21
76 | '*'=22
77 | '/'=23
78 | '%'=24
79 | '**'=25
80 | '#'=26
81 | '<'=27
82 | '>'=28
83 | '<='=29
84 | '>='=30
85 | '=='=31
86 | '!='=32
87 | '&&'=33
88 | '||'=34
89 | '->'=35
90 | 'if'=36
91 | 'null'=38
92 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const _rimraf = require('rimraf');
4 | const cp = require('child_process');
5 | const { spawn } = cp;
6 | const gulp = require('gulp');
7 | const { series, parallel } = gulp;
8 | const bump = require('gulp-bump');
9 | const rename = require("gulp-rename");
10 |
11 | const webpack = require('webpack');
12 | const webpackDevConfig = require('./build/webpack.config.dist.dev');
13 | const webpackMinConfig = require('./build/webpack.config.dist.min');
14 |
15 | let root = __dirname;
16 |
17 | function clean(cb) {
18 | let dir = path.join(__dirname, './dist');
19 | _rimraf(dir, function (err) {
20 | if (!err) {
21 | cb();
22 | }
23 |
24 | cb('删除文件失败');
25 | });
26 |
27 | }
28 |
29 | /**
30 | * 打包开发版本
31 | * @param {*} cb
32 | */
33 | function buildDev(cb) {
34 | webpack(webpackDevConfig, function (err, stats) {
35 | if (err) {
36 | console.log(err);
37 | }
38 | if (stats.hasErrors()) {
39 | console.log(stats);
40 | }
41 | cb();
42 | })
43 | }
44 |
45 | /**
46 | * 打包压缩版本
47 | */
48 | function buildMin(cb) {
49 | webpack(webpackMinConfig, function (err, stats) {
50 | if (err) {
51 | console.log(err);
52 | }
53 | if (stats.hasErrors()) {
54 | console.log(stats);
55 | }
56 | cb();
57 | })
58 | }
59 |
60 | /**
61 | * 执行 webpack 打包。
62 | */
63 | const build = parallel(buildDev, buildMin);
64 |
65 | /**
66 | * 将 excel-formula-sdk 的打包文件提炼到发布文件目录。
67 | * 包括依赖的相关资源文件。
68 | */
69 | function extractSDK(cb) {
70 | const destFolder = './dist/excel-formula-sdk/';
71 |
72 | gulp.src(path.join(root, './package.json'))
73 | .pipe(gulp.dest(destFolder, { overwrite: true }));
74 |
75 | gulp.src(path.join(root, './API.md'))
76 | .pipe(rename(function (path) {
77 | path.basename = 'README';
78 | }))
79 | .pipe(gulp.dest(destFolder, { overwrite: true }));
80 |
81 | cb();
82 | }
83 |
84 | /**
85 | * 更新发布版本
86 | */
87 | function updateSemver(cb) {
88 | const options = {};
89 | const stream = gulp
90 | .src(['./package.json'])
91 | .pipe(bump(options))
92 | .pipe(gulp.dest('./'));
93 |
94 | stream.on('end', function () {
95 | // 文件复制完成
96 | cb();
97 | });
98 | stream.on('error', function (err) {
99 | // 文件复制出错
100 | cb(err);
101 | });
102 | }
103 |
104 | /**
105 | * 发布到 npm 仓库
106 | * @param {*} cb
107 | */
108 | function npmPublish(cb) {
109 | spawn('npm', ['publish', './dist/excel-formula-sdk'], { stdio: 'inherit' }).on('close', function (err) {
110 | cb();
111 | });
112 | }
113 |
114 | /**
115 | * 执行完整的打包、更新版本、上传流程。
116 | */
117 | const sdkDistro = series(clean, build, updateSemver, extractSDK, npmPublish);
118 |
119 | exports.clean = clean;
120 | exports.build = build;
121 | exports.updateSemver = updateSemver;
122 | exports.extractSDK = extractSDK;
123 | exports.publish = npmPublish;
124 | exports.default = sdkDistro;
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | process.env.CHROME_BIN = require('puppeteer').executablePath();
2 |
3 | const webpackDevConfig = require('./build/webpack.config.dev');
4 |
5 | webpackDevConfig.stats = 'errors-only'; // 关闭不必要的日志
6 |
7 | module.exports = function (config) {
8 | config.set({
9 | basePath: '.',
10 | frameworks: ['mocha'],
11 | client: {
12 | mocha: {
13 | // change Karma's debug.html to the mocha web reporter
14 | reporter: 'html'
15 | }
16 | },
17 | files: [
18 | 'test/**/*.test.js',
19 | 'src/**/*.test.js',
20 | ],
21 | // 排除的文件列表
22 | exclude: [
23 | 'node_modules'
24 | ],
25 | preprocessors: {
26 | // add webpack as preprocessor
27 | 'test/**/*.test.js': ['webpack', 'sourcemap'],
28 | 'src/**/*.test.js': [ 'webpack', 'sourcemap'],
29 | },
30 |
31 | webpack: webpackDevConfig,
32 | plugins: [
33 | require('karma-mocha'),
34 | require('karma-chrome-launcher'),
35 | require('karma-webpack'),
36 | require('karma-coverage'),
37 | require('karma-sourcemap-loader'),
38 | require('karma-mocha-reporter'),
39 | require('karma-coverage-istanbul-reporter')
40 | ],
41 | /**
42 | * 服务端口号
43 | */
44 | port: 9876,
45 |
46 | /**
47 | * 启用或禁用输出报告或者日志中的颜色
48 | */
49 | colors: true,
50 |
51 | /**
52 | * 日志等级
53 | * 可能的值:
54 | * config.LOG_DISABLE //不输出信息
55 | * config.LOG_ERROR //只输出错误信息
56 | * config.LOG_WARN //只输出警告信息
57 | * config.LOG_INFO //输出全部信息
58 | * config.LOG_DEBUG //输出调试信息
59 | */
60 | logLevel: config.LOG_DISABLE,
61 | browsers: ['ChromeHeadless'], // 或者 ChromeHeadless, Chrome
62 |
63 | // 开启或禁用持续集成模式
64 | singleRun: true, // true,执行后退出
65 | // coverage reporter generates the coverage
66 | reporters: ['coverage-istanbul', 'mocha'], //, 'coverage-istanbul'
67 | coverageIstanbulReporter: {
68 | dir: "coverage/%browser%",
69 | reports: ["html"]
70 | }
71 | });
72 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "excel-formula-sdk",
3 | "version": "0.0.41",
4 | "description": "Excel 公式解析引擎(公式编辑器、公式计算)",
5 | "main": "./min/formula-sdk.js",
6 | "repository": {
7 | "url": "https://github.com/yezhang/excel-formula-sdk.git",
8 | "type": "git"
9 | },
10 | "author": "zhangyef@yonyou.com",
11 | "license": "MIT",
12 | "keywords": [
13 | "excel",
14 | "formula",
15 | "sdk",
16 | "engine",
17 | "monaco"
18 | ],
19 | "scripts": {
20 | "test": "node ./test/require-alias/loader.js --colors --config ./test/.mocharc.js",
21 | "coverage": "npx karma start karma.conf.js",
22 | "start": "webpack-dev-server --config build/webpack.config.dev.js",
23 | "build:dev": "webpack --config build/webpack.config.dist.dev.js",
24 | "build:min": "webpack --config build/webpack.config.dist.min.js",
25 | "build-all": "npm run build:dev && npm run build:min"
26 | },
27 | "dependencies": {
28 | "@formulajs/formulajs": "^2.4.3",
29 | "antlr4": "^4.8.0",
30 | "chroma-js": "^2.1.0",
31 | "core-js-pure": "^3.6.5",
32 | "lodash.debounce": "^4.0.8"
33 | },
34 | "peerDependencies": {
35 | "monaco-editor-core": "^0.20.0"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "^7.12.3",
39 | "@babel/preset-env": "^7.12.1",
40 | "babel-loader": "^8.1.0",
41 | "bufferutil": "^4.0.1",
42 | "cd": "^0.3.3",
43 | "chai": "^4.2.0",
44 | "coverage-istanbul-loader": "^3.0.4",
45 | "cz-conventional-changelog": "^3.3.0",
46 | "gulp": "^4.0.2",
47 | "gulp-bump": "^3.2.0",
48 | "gulp-rename": "^2.0.0",
49 | "karma": "^5.1.1",
50 | "karma-chrome-launcher": "^3.1.0",
51 | "karma-coverage": "^2.0.3",
52 | "karma-coverage-istanbul-reporter": "^3.0.3",
53 | "karma-mocha": "^2.0.1",
54 | "karma-mocha-reporter": "^2.2.5",
55 | "karma-sourcemap-loader": "^0.3.8",
56 | "karma-webpack": "^4.0.2",
57 | "mocha": "^8.1.1",
58 | "mochawesome": "^6.1.1",
59 | "nyc": "^15.1.0",
60 | "puppeteer": "^5.2.1",
61 | "rimraf": "^3.0.2",
62 | "sinon": "^9.0.2",
63 | "utf-8-validate": "^5.0.2",
64 | "webpack": "^4.44.1",
65 | "webpack-cli": "^3.3.12",
66 | "webpack-dev-server": "^3.11.0"
67 | },
68 | "config": {
69 | "commitizen": {
70 | "path": "./node_modules/cz-conventional-changelog",
71 | "types": {
72 | "feat": {
73 | "description": "一项新的特性",
74 | "title": "Features"
75 | },
76 | "daily": {
77 | "description": "日常变更",
78 | "title": "Daily Development"
79 | },
80 | "fix": {
81 | "description": "BUG 修复",
82 | "title": "Bug Fixes"
83 | },
84 | "docs": {
85 | "description": "只修改了文档",
86 | "title": "Documentation"
87 | },
88 | "style": {
89 | "description": "代码样式调整 (空白符, 格式化, 补充分号等)",
90 | "title": "Styles"
91 | },
92 | "refactor": {
93 | "description": "代码重构调整,既没有修复 BUG,也没有补充新功能",
94 | "title": "Code Refactoring"
95 | },
96 | "perf": {
97 | "description": "对性能改进的代码",
98 | "title": "Performance Improvements"
99 | },
100 | "test": {
101 | "description": "补充测试用例、调整现有测试用例",
102 | "title": "Tests"
103 | },
104 | "build": {
105 | "description": "修改构建过程、修改外部依赖 (可能的作用域: gulp, broccoli, npm)",
106 | "title": "Builds"
107 | },
108 | "chore": {
109 | "description": "杂项,其他变更(不影响 src、test 目录)",
110 | "title": "Chores"
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/base/ArrayUtils.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 返回不包含重复元素的新数组
4 | */
5 | function uniqueArray(array, hashFn) {
6 | var j = {};
7 |
8 | array.forEach( function(v) {
9 | j[hashFn(v)] = v;
10 | });
11 |
12 | return Object.keys(j).map(function(v){
13 | return j[v];
14 | });
15 | }
16 |
17 | exports.uniqueArray = uniqueArray;
--------------------------------------------------------------------------------
/src/base/StringBuffer.js:
--------------------------------------------------------------------------------
1 | var StringBuffer = function() {
2 | this.buffer = [];
3 | this.index;
4 | }
5 |
6 | Stringbufer.prototype = {
7 | append: function(s) {
8 | this.buffer[this.index] = s; // 使用数组下标比 push 方法更快
9 | this.index++;
10 | return this;
11 | },
12 | toString: function() {
13 | return this.buffer.join('');
14 | }
15 | }
16 |
17 | module.exports = StringBuffer;
--------------------------------------------------------------------------------
/src/base/StringUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 字符串处理工具类。
3 | *
4 | * 可能用到的工具:punycode。
5 | */
6 |
7 | function StringUtils (){}
8 |
9 | const LiteralEscapedCharValue = {
10 | '0':'\0',
11 | 'n':'\n',
12 | 'r':'\r',
13 | 't':'\t',
14 | 'v':'\v',
15 | 'b':'\b',
16 | 'f':'\f',
17 | '\\':'\\',
18 | '\'':'\'',
19 | '"':'"'
20 | };
21 |
22 | /**
23 | * \\ - 匹配反斜线,表达转义序列的开始
24 | * (
25 | * u\{([0-9A-Fa-f]+)\} - 可选1; 匹配可变长度的 16 进制转义序列 (\u{ABCD0})
26 | * |
27 | * u([0-9A-Fa-f]{4}) - 可选2; 匹配 4 位 16 进制转义序列 (\uABCD)
28 | * |
29 | * x([0-9A-Fa-f]{2}) - 可选3; 匹配 3 位 16 进制转义序列 (\xA5)
30 | * |
31 | * ([1-7][0-7]{0,2}|[0-7]{2,3}) - 可选4; 匹配最多 3 位 8 进制转义序列 (\5 or \512)
32 | * |
33 | * (['"tbrnfv0\\]) - 可选5; 匹配特殊转义字符 (\t, \n 等)
34 | * |
35 | * \U([0-9A-Fa-f]+) - 可选6; 匹配 8 位 16 进制转义序列,python 使用 (\U0001F3B5)
36 | * )
37 | */
38 | const jsEscapeRegex = /\\(u\{([0-9A-Fa-f]+)\}|u([0-9A-Fa-f]{4})|x([0-9A-Fa-f]{2})|([1-7][0-7]{0,2}|[0-7]{2,3})|(['"tbrnfv0\\]))|\\U([0-9A-Fa-f]{8})/g;
39 | const fromHex = (str) => String.fromCodePoint(parseInt(str, 16));
40 | const fromOct = (str) => String.fromCodePoint(parseInt(str, 8));
41 |
42 | StringUtils.unescape = function (str) {
43 | return str.replace(jsEscapeRegex, function(_, __, varHex, longHex, shortHex, octal, specialCharacter, python) {
44 | if (varHex !== undefined) {
45 | return fromHex(varHex);
46 | } else if (longHex !== undefined) {
47 | return fromHex(longHex);
48 | } else if (shortHex !== undefined) {
49 | return fromHex(shortHex);
50 | } else if (octal !== undefined) {
51 | return fromOct(octal);
52 | } else if (python !== undefined) {
53 | return fromHex(python);
54 | } else {
55 | return LiteralEscapedCharValue[specialCharacter];
56 | }
57 | });
58 | }
59 |
60 | /**
61 | * 移除两侧的引号
62 | *
63 | * @param {String} str
64 | * @returns {String}
65 | */
66 | StringUtils.removeQuotes = function removeQuotes(str) {
67 | let newStr = str;
68 |
69 | if (
70 | (str.charAt(0) === '"' && str.charAt(str.length - 1) === '"') ||
71 | (str.charAt(0) === '\'' && str.charAt(str.length - 1) === '\'')
72 | ) {
73 | newStr = str.substr(1, str.length - 2);
74 | }
75 |
76 | return newStr;
77 | }
78 |
79 | /**
80 | * 从 String token 中解析出真正的文本内容。
81 | * 移除字符串两侧的双引号或单引号,移除字面量中的转义符。
82 | *
83 | * 支持的 JavaScript 转义字符串有:
84 | * '\t'
85 | * '\v'
86 | * '\0'
87 | * '\b'
88 | * '\f'
89 | * '\n'
90 | * '\r'
91 | * '\''
92 | * '\"'
93 | * '\\'
94 | */
95 | StringUtils.unwrapText = function(quotedString) {
96 | if(quotedString == null) {
97 | return quotedString;
98 | }
99 | var innerText = StringUtils.removeQuotes(quotedString);
100 | return StringUtils.unescape(innerText);
101 | }
102 |
103 | exports.StringUtils = StringUtils;
104 |
105 |
--------------------------------------------------------------------------------
/src/base/color/colorTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 前景色、背景色,颜色测试工具。
3 | * REF: https://www.myndex.com/SAPC/
4 | */
5 |
6 | ///// CONSTANTS USED IN THIS VERSION ///////////////////////////////////////////
7 |
8 | const sRGBtrc = 2.218; // Gamma for sRGB linearization. 2.223 could be used instead
9 | // 2.218 sets unity with the piecewise sRGB at #777
10 |
11 | const Rco = 0.2126; // sRGB Red Coefficient
12 | const Gco = 0.7156; // sRGB Green Coefficient
13 | const Bco = 0.0722; // sRGB Blue Coefficient
14 |
15 | const scaleBoW = 161.8; // Scaling for dark text on light (phi * 100)
16 | const scaleWoB = 161.8; // Scaling for light text on dark — same as BoW, but
17 | // this is separate for possible future use.
18 |
19 | const normBGExp = 0.38; // Constants for Power Curve Exponents.
20 | const normTXTExp = 0.43; // One pair for normal text,and one for REVERSE
21 | const revBGExp = 0.5; // FUTURE: These will eventually be dynamic
22 | const revTXTExp = 0.43; // as a function of light adaptation and context
23 |
24 | const blkThrs = 0.02; // Level that triggers the soft black clamp
25 | const blkClmp = 1.75; // Exponent for the soft black clamp curve
26 |
27 | ///// Ultra Simple Basic Bare Bones SAPC Function //////////////////////////////
28 |
29 |
30 | function linearizeRBG(r, g, b) {
31 | return {
32 | r: Math.pow(r / 255.0, sRGBtrc),
33 | g: Math.pow(g / 255.0, sRGBtrc),
34 | b: Math.pow(b / 255.0, sRGBtrc)
35 | }
36 | }
37 |
38 | /**
39 | * This REQUIRES linearized R,G,B values of 0.0-1.0
40 | */
41 | function SAPCbasic(Rbg,Gbg,Bbg,Rtxt,Gtxt,Btxt) {
42 |
43 | var SAPC = 0.0;
44 |
45 | // Find Y by applying coefficients and sum.
46 | // This REQUIRES linearized R,G,B 0.0-1.0
47 |
48 | var Ybg = Rbg*Rco + Gbg*Gco + Bbg*Bco;
49 | var Ytxt = Rtxt*Rco + Gtxt*Gco + Btxt*Bco;
50 |
51 | ///// INSERT COLOR MODULE HERE /////
52 |
53 | // Now, determine polarity, soft clamp black, and calculate contrast
54 | // Finally scale for easy to remember percentages
55 | // Note that reverse (white text on black) intentionally
56 | // returns a negative number
57 |
58 | if ( Ybg > Ytxt ) { ///// For normal polarity, black text on white
59 |
60 | // soft clamp darkest color if near black.
61 | Ytxt = (Ytxt > blkThrs) ? Ytxt : Ytxt + Math.abs(Ytxt - blkThrs) ** blkClmp;
62 | SAPC = ( Ybg ** normBGExp - Ytxt ** normTXTExp ) * scaleBoW;
63 |
64 | return (SAPC < 15 ) ? 0 : SAPC.toPrecision(3);
65 |
66 | } else { ///// For reverse polarity, white text on black
67 |
68 | Ybg = (Ybg > blkThrs) ? Ybg : Ybg + Math.abs(Ybg - blkThrs) ** blkClmp;
69 | SAPC = ( Ybg ** revBGExp - Ytxt ** revTXTExp ) * scaleWoB;
70 |
71 | return (SAPC > -15 ) ? 0: SAPC.toPrecision(3);
72 | }
73 |
74 | // If SAPC's more than 15%, return that value, otherwise clamp to zero
75 | // this is to remove noise and unusual behavior if the user inputs
76 | // colors too close to each other.
77 | // This will be more important with future modules. Nevertheless
78 | // In order to simplify code, SAPC will not report accurate contrasts
79 | // of less than approximately 15%, so those are clamped.
80 | // 25% is the "point of invisibility" for many people.
81 |
82 | }
83 |
84 | //////////////////////////////////////////////////////////////
85 | ///// END OF SAPC BLOCK //////////////////////////
86 | //////////////////////////////////////////////////////////////
87 |
88 | function colorTest(bgR, bgG, bgB, textR, txtG, txtB) {
89 | let linColorbg = linearizeRBG(bgR, bgG, bgB);
90 | let linColorTxt = linearizeRBG(textR, txtG, txtB);
91 | return SAPCbasic(linColorbg.r, linColorbg.g, linColorbg.b, linColorTxt.r, linColorTxt.g, linColorTxt.b);
92 | }
93 |
94 |
95 | exports.ColorTest = colorTest;
--------------------------------------------------------------------------------
/src/base/common/assert.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 如果提供的 value 无法计算为一个 Javascript true 值,则使用给定的消息抛出异常。
3 | */
4 | function ok(value, message) {
5 | if (!value) {
6 | throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed');
7 | }
8 | }
9 |
10 | exports.ok = ok;
--------------------------------------------------------------------------------
/src/base/common/types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @returns whether the provided parameter is a JavaScript Array or not.
3 | */
4 | function isArray(array) {
5 | return Array.isArray(array);
6 | }
7 |
8 | /**
9 | * @returns whether the provided parameter is a JavaScript String or not.
10 | */
11 | function isString(str) {
12 | return (typeof str === 'string');
13 | }
14 |
15 | /**
16 | *
17 | * @returns whether the provided parameter is of type `object` but **not**
18 | * `null`, an `array`, a `regexp`, nor a `date`.
19 | */
20 | function isObject(obj) {
21 | // The method can't do a type cast since there are type (like strings) which
22 | // are subclasses of any put not positvely matched by the function. Hence type
23 | // narrowing results in wrong results.
24 | return typeof obj === 'object'
25 | && obj !== null
26 | && !Array.isArray(obj)
27 | && !(obj instanceof RegExp)
28 | && !(obj instanceof Date);
29 | }
30 |
31 | function isInt(value) {
32 | var x;
33 | if (isNaN(value)) {
34 | return false;
35 | }
36 | x = parseFloat(value);
37 | return (x | 0) === x;
38 | }
39 |
40 | /**
41 | * In **contrast** to just checking `typeof` this will return `false` for `NaN`.
42 | * @returns whether the provided parameter is a JavaScript Number or not.
43 | */
44 | function isNumber(obj){
45 | return (typeof obj === 'number' && !isNaN(obj));
46 | }
47 |
48 | exports.isInt = isInt;
49 | exports.isArray = isArray;
50 | exports.isString = isString;
51 | exports.isObject = isObject;
52 | exports.isNumber = isNumber;
--------------------------------------------------------------------------------
/src/base/debounce.js:
--------------------------------------------------------------------------------
1 | const _debounce = require('lodash.debounce');
2 |
3 | /**
4 | * debounce 工具
5 | */
6 |
7 | function debounce(func, wait, options) {
8 | return _debounce(func, wait, options);
9 | }
10 |
11 | module.exports = debounce;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | const contrib = require('workbench/monaco-editor/monaco.contribution');
3 | const ColorsProvider = require('workbench/monaco-editor/colorsProvider').ColorsProvider;
4 | const language = require('workbench/monaco-editor/languageFeatures');
5 |
6 | const {FormulaEngine, WorkBookContext}= require('platform/formula/FormulaEngine');
7 |
8 | exports.ColorsProvider = ColorsProvider;
9 | exports.contrib = contrib;
10 | exports.language = language;
11 |
12 | exports.WorkBookContext = WorkBookContext;
13 | exports.FormulaEngine = FormulaEngine;
--------------------------------------------------------------------------------
/src/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "amd",
4 | "target": "es2017",
5 | "moduleResolution": "node",
6 | "baseUrl": ".",
7 | "paths": {
8 | "base/*": [
9 | "./base/*",
10 | ],
11 | "platform/*": [
12 | "./platform/*",
13 | ],
14 | "workbench/*": [
15 | "./workbench/*",
16 | ]
17 | }
18 | },
19 | "include": [
20 | "./base",
21 | "./platform",
22 | "./workbench"
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/platform/contrib/errorHandler/BaseErrorHandler.js:
--------------------------------------------------------------------------------
1 | function BaseErrorHandler() {
2 | }
3 |
4 | /**
5 | *
6 | * 处理公式的运行时错误
7 | * @param {CalculationException} e 运行时异常信息
8 | */
9 | BaseErrorHandler.prototype.handleEvaluateError = function handleEvaluateError(e) {
10 | /** noop */
11 | }
12 |
13 | /**
14 | * 处理公式解析的语法错误
15 | * TODO: 调整参数为 e
16 | */
17 | BaseErrorHandler.prototype.handleParseError = function handleParseError(input, line, column, message) {
18 | /** noop */
19 | }
20 |
21 |
22 | module.exports = BaseErrorHandler;
--------------------------------------------------------------------------------
/src/platform/contrib/errorHandler/EditorErrorHandler.js:
--------------------------------------------------------------------------------
1 | const BaseErrorHandler = require('./BaseErrorHandler');
2 |
3 | function EditorErrorHandler () {
4 | BaseErrorHandler.call(this);
5 | this._errors = [];
6 | return this;
7 | }
8 |
9 | EditorErrorHandler.prototype = Object.create(BaseErrorHandler.prototype);
10 | EditorErrorHandler.prototype.constructor = EditorErrorHandler;
11 |
12 | EditorErrorHandler.prototype.handleEvaluateError = function(e) {
13 |
14 | }
15 |
16 | EditorErrorHandler.prototype.handleParseError = function handleParseError(input, line, column, message){
17 | /** noop */
18 | this._errors.push({input, line, column, message});
19 | }
20 |
21 | /**
22 | * 将收集到的错误返回。
23 | */
24 | EditorErrorHandler.prototype.getErrors = function getErrors() {
25 | return this._errors;
26 | }
27 |
28 | /**
29 | * 清空错误缓存
30 | */
31 | EditorErrorHandler.prototype.clearErrors = function clearErrors() {
32 | this._errors = [];
33 | }
34 |
35 |
36 | module.exports = EditorErrorHandler;
--------------------------------------------------------------------------------
/src/platform/contrib/errorHandler/test/MockEditorErrorHandler.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yezhang/excel-formula-sdk/e965eca5d7db0d1e3d7a6966d26f00ea7b14a4e2/src/platform/contrib/errorHandler/test/MockEditorErrorHandler.js
--------------------------------------------------------------------------------
/src/platform/formula/FormulaLanguageService.js:
--------------------------------------------------------------------------------
1 | const FormulaCore = require('platform/formula/core/SingleFormulaCore').SingleFormulaCore;
2 | const FormulaSignatureList = require('platform/formula/intelliSense/formulaSignatureHelp').FormulaSignatureList;
3 |
4 | const formulaCoreInst = FormulaCore.INSTANCE;
5 |
6 | function LangInputModel(versionId, value) {
7 | this._versionId = versionId;
8 | this._value = value;
9 | }
10 |
11 | LangInputModel.build = function build(versionId, value) {
12 | return new LangInputModel(versionId,value);
13 | }
14 |
15 | LangInputModel.prototype.getVersionId = function getVersionId() {
16 | return this._versionId;
17 | }
18 |
19 | LangInputModel.prototype.getValue = function getValue() {
20 | return this._value;
21 | }
22 |
23 |
24 | class ILanguageService {
25 | /**
26 | * 获取句法诊断信息,包括错误。
27 | */
28 | getSyntacticDiagnostics() {}
29 | /**
30 | * 获取语义诊断信息,包括错误。
31 | */
32 | getSemanticDiagnostics() {}
33 | /**
34 | * 获取函数签名辅助信息
35 | */
36 | getSignatureHelpItems() {}
37 | /**
38 | * 获取自动补全信息
39 | */
40 | getCompletionsAtPosition() {}
41 | /**
42 | * 获取快速提示信息(Hover)
43 | */
44 | getQuickInfoAtPosition() {}
45 |
46 | /**
47 | * 获取所有的单元格地址/单元格范围的列表。
48 | * 可用于支持单元格地址高亮。
49 | */
50 | getCellAddressHighlights() {}
51 |
52 | /**
53 | * 代码错误修复
54 | */
55 | getCodeFixesAtPosition() {}
56 | dispose(){}
57 | }
58 |
59 | const DiagnosticCategory = {
60 | Warning : 0,
61 | Error : 1,
62 | Suggestion : 2,
63 | Message : 3
64 | };
65 |
66 | class ParseResult {
67 | constructor(parseTree, parseErrors){
68 | this._parseTree = parseTree;
69 | this._parseErrors = parseErrors;
70 | }
71 |
72 | getParseTree() {
73 | return this._parseTree;
74 | }
75 |
76 | getParseErrors() {
77 | return this._parseErrors;
78 | }
79 | }
80 |
81 | class ParseResultCache {
82 | constructor(version, parseResult) {
83 | this._version = version;
84 | this._parseResult = parseResult;
85 | }
86 |
87 | getParseResult() {
88 | return this._parseResult;
89 | }
90 | // 得到解析树
91 | getParseTree() {
92 | return this._parseResult.getParseTree();
93 | }
94 |
95 | getVersion() {
96 | return this._version;
97 | }
98 |
99 | getErrors() {
100 | return this._parseResult.getParseErrors();
101 | }
102 | }
103 |
104 | /**
105 | * 为公式编辑器提供语言服务:代码高亮、错误检查、自动补全。
106 | */
107 | class FormulaLanguageService {
108 | constructor() {
109 | this._parseTreeCache = undefined;
110 | }
111 |
112 | getParseTreeCache() {
113 | return this._parseTreeCache;
114 | }
115 |
116 | setParseTreeCache(version, tree, errors) {
117 | this._parseTreeCache = new ParseResultCache(version, tree, errors);
118 | }
119 |
120 | _forceParseInput(inputText) {
121 | const errors = [];
122 | formulaCoreInst.setErrorHandler({
123 | handleEvaluateError: function(e) {
124 |
125 | },
126 | handleParseError: function(rawInput, symbol, line, column, message){
127 | let charOffset = symbol.start;
128 |
129 | errors.push({
130 | category: DiagnosticCategory.Error,
131 | start: charOffset,
132 | length: symbol.text ? symbol.text.length : 0,
133 | messageText: message
134 | });
135 | }
136 | });
137 | let tree = formulaCoreInst.parse(inputText);
138 |
139 | return new ParseResult(tree, errors);
140 | }
141 |
142 | /**
143 | * 支持从缓存读取数据。
144 | */
145 | parseInputModel(inputModel) {
146 | let versionId = inputModel.getVersionId();
147 | let cache = this.getParseTreeCache();
148 | if(cache && cache.getVersion() && cache.getVersion() === versionId) {
149 | return cache.getParseResult();
150 | }
151 |
152 | let inputText = inputModel.getValue();
153 | let ret = this._forceParseInput(inputText);
154 | this.setParseTreeCache(versionId, ret);
155 |
156 | return ret;
157 | }
158 | /**
159 | * 获取句法诊断
160 | * @return {Diagnostic}
161 | *
162 | * Diagnostic {
163 | * category: DiagnosticCategory;
164 | * code: number;
165 | * file: SourceFile | undefined;
166 | * start: number | undefined;
167 | * length: number | undefined;
168 | * messageText: string | DiagnosticMessageChain;
169 | * }
170 | */
171 | getSyntacticDiagnostics(inputModel, position) {
172 | let ret = this.parseInputModel(inputModel);
173 | return ret.getParseErrors();
174 | }
175 |
176 | /**
177 | * 获取语义诊断
178 | * @param {*} input
179 | * @param {*} position
180 | */
181 | getSemanticDiagnostics(inputModel, position) {
182 |
183 | }
184 |
185 | /**
186 | * 根据当前光标位置,返回签名的提示信息:活动签名、活动参数等。
187 | * @param {position} - 光标位置。position.column = 1..n
188 | */
189 | getSignatureHelpItems(inputModel, position) {
190 | let ret = this.parseInputModel(inputModel);
191 | let token = formulaCoreInst.findArgumentRuleOnLeftOfPosition(ret.getParseTree(), position.lineNumber, position.column - 1);
192 |
193 | // 如果没有参数信息,则返回 undefined。
194 | if(!token || token.getText() === ')') {
195 | // 当返回 undefined 时,提示窗口消失。
196 | return undefined;
197 | }
198 |
199 | let argumentInfo = formulaCoreInst.getContainingArgumentInfo(token);
200 |
201 | // token.tokenType = fnIdentifier
202 | let fnName = argumentInfo.fnName;
203 |
204 | const retItems = {
205 | selectedItemIndex: 0,
206 | argumentIndex: argumentInfo.argumentIndex,
207 | items: []
208 | };
209 |
210 | // 查询函数的签名数据
211 | if(FormulaSignatureList.hasOwnProperty(fnName)){
212 | retItems.items.push(FormulaSignatureList[fnName]);
213 | }
214 |
215 | return retItems;
216 | }
217 |
218 | findFnIdentifierToken(inputTokens, siblingToken) {
219 | let fnNameToken = undefined;
220 | for (let i = siblingToken.tokenIndex; i >= 0; i--) {
221 | const token = inputTokens[i];
222 | if (token.tokenType === FormulaCore.FnTokenType) {
223 | fnNameToken = token;
224 | break;
225 | }
226 | }
227 |
228 | return fnNameToken;
229 | }
230 |
231 | /**
232 | * 用于语法高亮使用
233 | * 返回一个数组:[{
234 | * tokenType,
235 | * startColumn
236 | * }]
237 | */
238 | provideTokens(inputModel) {
239 | let ret = this.parseInputModel(inputModel);
240 | let tokens = formulaCoreInst.collectTokens(ret.getParseTree());
241 |
242 | let editorTokens = [];
243 | tokens.forEach(function (token, index) {
244 | editorTokens.push({
245 | tokenIndex: index,
246 | lineNumber: token.line,
247 | tokenType: token.tokenTypeName,
248 | startColumn: token.startIndex,
249 | stopColumn: token.stopIndex,
250 | text: token.text
251 | });
252 | });
253 |
254 | return editorTokens;
255 | }
256 |
257 | provideTokensFromCache(inputModel) {
258 | return this.provideTokens(inputModel);
259 | }
260 | }
261 |
262 |
263 | FormulaLanguageService.INSTANCE = new FormulaLanguageService();
264 |
265 | exports.FormulaLanguageService = FormulaLanguageService;
266 | exports.LangInputModel = LangInputModel;
267 |
268 |
--------------------------------------------------------------------------------
/src/platform/formula/autofix/AutoFixFormulaTool.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 自动修复公式中的单元格范围
3 | */
4 | const {SimpleCellRange} = require('platform/formula/cellAddressParts/common/CellAddressParts');
5 | const {A1ReferenceIdentifier} = require('platform/formula/core/SingleFormulaAST');
6 |
7 | class AutoFixFormulaTool {
8 |
9 | /**
10 | * 在语法树中修复单元格范围的表达方式。
11 | * 修正为左上角、右下角的表达方式。
12 | *
13 | * 支持级联调用。
14 | *
15 | * 本函数需要了解语法树的节点结构。
16 | * @see SingleFormulaAST
17 | *
18 | * 例如,
19 | * 将 A5:A1 修正为 A1:A5;
20 | * 将 B3:A1 修正为 A1:B3
21 | * 将 E4:G2 修正为 E2:G4
22 | */
23 | fixCellRangeInPlace(activeSheetName, formulaAST) {
24 | let cellRefRangeNodes = formulaAST.findAllCellRefNodes().rangeNodes;
25 |
26 | /**
27 | *
28 | * @param {CellRangeIdentifier} rangeNode
29 | */
30 | function _fixRange(rangeNode){
31 | const range = SimpleCellRange.buildFromASTNode(activeSheetName, rangeNode);
32 |
33 | const startRefId = rangeNode.startRef;
34 | const column1Id = startRefId.columnRef;
35 | const row1Id = startRefId.rowRef;
36 |
37 | const endRefId = rangeNode.endRef;
38 | const column2Id = endRefId.columnRef;
39 | const row2Id = endRefId.rowRef;
40 |
41 | const topLeft = {
42 | columnRef: range.start.column <= range.end.column ? column1Id : column2Id,
43 | rowRef: range.start.row <= range.end.row ? row1Id : row2Id
44 | }
45 |
46 | const bottomRight = {
47 | columnRef: range.start.column > range.end.column ? column1Id : column2Id,
48 | rowRef: range.start.row > range.end.row ? row1Id : row2Id
49 | }
50 |
51 | // 重新排列组合
52 | const fixStartRefId = new A1ReferenceIdentifier(topLeft.columnRef, topLeft.rowRef);
53 | const fixEndRefId = new A1ReferenceIdentifier(bottomRight.columnRef, bottomRight.rowRef);
54 |
55 | rangeNode.startRef = fixStartRefId;
56 | rangeNode.endRef = fixEndRefId;
57 | }
58 | cellRefRangeNodes.forEach(_fixRange);
59 | return this;
60 | }
61 | }
62 |
63 | exports.AutoFixFormulaTool = AutoFixFormulaTool;
--------------------------------------------------------------------------------
/src/platform/formula/cellAddressParts/test/CellAddressGrammar.test.js:
--------------------------------------------------------------------------------
1 | const antlr4 = require('antlr4');
2 | const CellAddressLexer = require('platform/formula/runtime/CellAddressLexer').CellAddressLexer;
3 | const CellAddressParser = require('platform/formula/runtime/CellAddressParser').CellAddressParser;
4 |
5 | describe('单元格地址', function () {
6 | it('验证"地址语法"解析过程-独立地址', function () {
7 | let cellAddressString = 'A1!$A$1:C1';
8 | const chars = new antlr4.InputStream(cellAddressString);
9 | const lexer = new CellAddressLexer(chars);
10 | const tokens = new antlr4.CommonTokenStream(lexer);
11 | const parser = new CellAddressParser(tokens);
12 | const tree = parser.cellReference();
13 |
14 | })
15 | });
--------------------------------------------------------------------------------
/src/platform/formula/cellAddressParts/test/CellAddressParts.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert').strict;
2 | const sinon = require('sinon');
3 |
4 | const CellAddress = require('../common/CellAddressParts').CellAddress;
5 | const CellAddressParts = require('platform/formula/cellAddressParts/common/CellAddressParts');
6 | const { A1Reference } = CellAddressParts;
7 | const { RelativeColumn, AbsoluteColumn } = CellAddressParts;
8 | const { RelativeRow, AbsoluteRow } = CellAddressParts;
9 |
10 | const convertColumnLettersToNumber = CellAddressParts.convertColumnLettersToNumber;
11 | const convertNumberToColumnLetters = CellAddressParts.convertNumberToColumnLetters;
12 |
13 | describe('单元格地址', function () {
14 | it('单元格列与数字的转换', function () {
15 | for (let n = 1; n <= 1000000; n++) {
16 | let column = convertNumberToColumnLetters(n);
17 |
18 | let convertedNumber = convertColumnLettersToNumber(column);
19 |
20 | assert.strictEqual(n, convertedNumber);
21 | }
22 | });
23 |
24 |
25 | it('单元格列转化为数字', function () {
26 | assert.strictEqual(convertColumnLettersToNumber('A'), 1);
27 | assert.strictEqual(convertColumnLettersToNumber('Z'), 26);
28 | assert.strictEqual(convertColumnLettersToNumber('A'), 1);
29 | assert.strictEqual(convertColumnLettersToNumber('Z'), 26);
30 |
31 | assert.strictEqual(convertColumnLettersToNumber('AA'), 27);
32 | assert.strictEqual(convertColumnLettersToNumber('AB'), 28);
33 | assert.strictEqual(convertColumnLettersToNumber('AZ'), 52);
34 |
35 | });
36 | });
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/CellDependencyVisitor.js:
--------------------------------------------------------------------------------
1 | const ReportFormulaParserVisitor = require('../runtime/ReportFormulaParserVisitor').ReportFormulaParserVisitor;
2 | const ReportFormulaParser = require('../runtime/ReportFormulaParser').ReportFormulaParser;
3 |
4 | /**
5 | * 收集单元格依赖的其他单元格地址。
6 | */
7 | class CellDependencyVisitor extends ReportFormulaParserVisitor {
8 | constructor() {
9 | super();
10 |
11 | this._cellAddressList = [];
12 | }
13 |
14 | collectAddress(address) {
15 | this._cellAddressList.push(address);
16 | }
17 |
18 | getCellAddressList() {
19 | return this._cellAddressList;
20 | }
21 |
22 | visitFormulaExpr(ctx) {
23 | ctx.expressionStatement().accept(this);
24 | return this._cellAddressList;
25 | }
26 |
27 | visitExpressionStatement(ctx) {
28 | return ctx.expressionSequence().accept(this);
29 | }
30 |
31 | // 多个表达式序列时,使用最后一个序列为计算结果
32 | visitExpressionSequence(ctx) {
33 | if (!ctx) {
34 | return;
35 | }
36 |
37 | if (ctx.children) {
38 | var result = undefined;
39 | for (var i = 0; i < ctx.children.length; i++) {
40 | result = ctx.children[i].accept(this);
41 | }
42 |
43 | return result;
44 | }
45 | }
46 |
47 | collectCellAddrLiteral(ctx) {
48 | let addr = ctx.getText();
49 | this.collectAddress(addr);
50 | }
51 |
52 | /**
53 | * 单元格地址:CellAddressLiteral
54 | */
55 | visitIdentifierCellAddressLiteral(ctx) {
56 | this.collectCellAddrLiteral(ctx);
57 | }
58 |
59 | /**
60 | * 单元格范围:CellRangeLiteral
61 | */
62 | visitIdentifierCellRangeLiteral(ctx) {
63 | this.collectCellAddrLiteral(ctx);
64 | }
65 |
66 | }
67 |
68 | exports.CellDependencyVisitor = CellDependencyVisitor;
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/DependencyBuilder.js:
--------------------------------------------------------------------------------
1 | const types = require('base/common/types');
2 | const buildCellRefDecorator = require('../cellAddressParts/common/CellAddressParts').buildCellRefDecorator;
3 | const SimpleCellAddress = require('platform/formula/cellAddressParts/common/CellAddressParts').SimpleCellAddress;
4 | const SingleFormulaContext = require('platform/formula/core/SingleFormulaContext').SingleFormulaContext;
5 |
6 | class DependencyError extends Error {
7 | constructor(msg) {
8 | super(msg);
9 | }
10 | }
11 |
12 | class CellDependencyBuilder {
13 | /**
14 | * @param {DependencyGraph} depGraph 依赖关系图
15 | */
16 | constructor(depGraph) {
17 | this.depGraph = depGraph;
18 | this.formulaAST = undefined;
19 | }
20 |
21 | setFormulaAST(ast) {
22 | this.formulaAST = ast;
23 | }
24 |
25 | /**
26 | * 根据 unusedSheetNames 数组中的名称,移除该表格的下的所有单元格节点。
27 | */
28 | removeSheets(activeSheetName, unusedSheetNames){
29 | if(types.isArray(unusedSheetNames)) {
30 | this.depGraph.removeSheets(unusedSheetNames);
31 | }
32 | }
33 |
34 | renameSheet(activeSheetName, oldSheetName, newSheetName) {
35 | if(oldSheetName && newSheetName) {
36 | this.depGraph.renameSheet(oldSheetName, newSheetName);
37 | }
38 | }
39 |
40 | /**
41 | * 清空当前工作单元格的依赖
42 | */
43 | clear(activeSheetName, workingCellRefAddr) {
44 | let simpleCellAddress = SimpleCellAddress.build(activeSheetName, workingCellRefAddr.column, workingCellRefAddr.row);
45 | this._removeDependencies(simpleCellAddress);
46 | }
47 |
48 | build(activeSheetName, workingCellRefAddr) {
49 | let simpleCellAddress = SimpleCellAddress.build(activeSheetName, workingCellRefAddr.column, workingCellRefAddr.row);
50 |
51 | // 收集受影响的单元格
52 | // Array[ CellAddressIdentifier|CellRangeIdentifier]
53 | let dependenciesList = this.formulaAST.findAllCellRefNodes();
54 | this.addOrUpdateDependencies(simpleCellAddress, dependenciesList);
55 | }
56 |
57 | check() {
58 | this._checkCycleDepencies();
59 | }
60 |
61 | /**
62 | * 将 "单元格引用字符串" 对象化、去除重复的依赖引用、简化地址表示法。
63 | *
64 | * 每次遇到一个单元格的公式,将公式放置到图结构中。
65 | * 对单元格地址格式进行规范化处理。
66 | *
67 | * 如果传入重复的 workingCellAddress,则执行覆盖,并更新图的关系。
68 | * @param {SimpleCellAddress} workingCellAddr 工作单元格地址
69 | * @param {Array[ CellAddressIdentifier|CellRangeIdentifier] } dependenciesList cellRefNodes
70 | */
71 | addOrUpdateDependencies(workingCellAddr, dependenciesList) {
72 | const _this = this;
73 | let activeSheetName = workingCellAddr.sheet;
74 | if(!activeSheetName) {
75 | throw new DependencyError('当前活动单元格未设置表格名称');
76 | }
77 |
78 | // 包括单元格地址和单元格范围
79 | const depMap = {};
80 | dependenciesList.forEach(function (dep) {
81 | let cellCarry = _this._buildCellAddress(activeSheetName, dep);
82 | let simpleCell = cellCarry.toSimpleAddress();
83 | let hashcode = simpleCell.hashcode();
84 |
85 | if (depMap.hasOwnProperty(hashcode)) {
86 | let depDetail = depMap[hashcode];
87 | let depListValue = depDetail.deps;
88 |
89 | depListValue.push(cellCarry);
90 | } else {
91 | let depDetail = {
92 | simple: simpleCell,
93 | deps: [cellCarry]
94 | };
95 | depMap[hashcode] = depDetail;
96 | }
97 | });
98 |
99 | this._removeDependencies(workingCellAddr);
100 | this._addDependencies(workingCellAddr, depMap);
101 | }
102 |
103 | _checkCycleDepencies() {
104 | this.depGraph.sort();
105 | }
106 |
107 | /**
108 | * 移除从 cellAddress 发出的有向边(只移除出度)。
109 | * @param {SimpleCellAddress} cellAddress
110 | */
111 | _removeDependencies(cellAddress) {
112 | this.depGraph.removeCellDependencies(cellAddress);
113 | }
114 |
115 |
116 | /**
117 | * 建立从 cellAddress 指向 dependencyMap.keys() 的有向边。
118 | * @param {SimpleCellAddress} cellAddress
119 | */
120 | _addDependencies(cellAddress, dependencyMap) {
121 | this.depGraph.addCellDependencies(cellAddress, this.formulaAST, dependencyMap);
122 | }
123 |
124 | // 将单元格地址转化为标准对象
125 | _buildCellAddress(sheetName, cellAddressOrCellRange) {
126 | let cellRef = buildCellRefDecorator(cellAddressOrCellRange);
127 | let context = new SingleFormulaContext();
128 | context.activeSheetName = sheetName;
129 | cellRef.setWorkingContext(context);
130 | return cellRef;
131 | }
132 |
133 | getDependencyGraph() {
134 | return this.depGraph;
135 | }
136 | }
137 |
138 |
139 | exports.DependencyBuilder = CellDependencyBuilder;
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/DependencyFinder.js:
--------------------------------------------------------------------------------
1 | const {SimpleCellAddress} = require('platform/formula/cellAddressParts/common/CellAddressParts');
2 |
3 | /**
4 | * 从依赖图中取值。
5 | */
6 | class DependencyFinder {
7 | constructor(depGraph) {
8 | this.depGraph = depGraph;
9 | }
10 |
11 | /**
12 | * @param {String} activeSheetName 活动单元格名称
13 | * @param {Object} cellAddr 单元格地址对象 {column:<1..n>, row:<1..n>}
14 | */
15 | getCellFormula(activeSheetName, cellAddr){
16 | let simpleAddr = SimpleCellAddress.build(activeSheetName, cellAddr.column, cellAddr.row);
17 | return this.depGraph.getCellFormula(activeSheetName, simpleAddr);
18 | }
19 | }
20 |
21 | exports.DependencyFinder = DependencyFinder;
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/FormulaAstVisitor.js:
--------------------------------------------------------------------------------
1 | const ReportFormulaParserVisitor = require('../runtime/ReportFormulaParserVisitor').ReportFormulaParserVisitor;
2 | const CellAddressVisitor = require('platform/formula/runtime/CellAddressVisitor').CellAddressVisitor;
3 |
4 | class CellAddressAstVisitor extends CellAddressVisitor {
5 |
6 | }
7 |
8 | /**
9 | * 用于将解析树转换为语言模型。
10 | */
11 | class FormulaAstVisitor extends ReportFormulaParserVisitor {
12 |
13 | }
14 |
15 | exports.FormulaAstVisitor = FormulaAstVisitor;
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/common/ElementaryCircuitsSearch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 寻找连通图
3 | */
4 | const StrongConnectedComponents = require('./StrongConnectedComponents');
5 |
6 | /**
7 | * 给图中的所有顶点生成一个整数索引,方便图的搜索算法。
8 | * 顶点索引 从 0 开始。
9 | * @param {Graph} graph
10 | */
11 | function GenIdForGraph(graph) {
12 | if (!graph) {
13 | return;
14 | }
15 |
16 | let nodes = graph.nodes();
17 | for (let i = 0; i < nodes.length; i++) {
18 | nodes[i].id = i;
19 | }
20 | }
21 |
22 | /**
23 | * 将图结构转换为邻接表的表示法
24 | */
25 | function _convertGraph(graph) {
26 | let adjListOrigin = [];
27 | let nodes = graph.nodes();
28 | for (let i = 0; i < nodes.length; i++) {
29 | const vAdjList = [];
30 | for (const { toNode } of nodes[i].outgoing.values()) {
31 | vAdjList.push(toNode.id);
32 | }
33 | adjListOrigin[nodes[i].id] = vAdjList;
34 | }
35 | return adjListOrigin;
36 | }
37 |
38 |
39 | /**
40 | * 在有向图中查找所有基本环路。
41 | * 具体的算法实现与图的结构实现无关,只需要一个邻接表表示图中的各边即可。
42 | * 图中的节点在算法中采用整数表示索引。节点索引是大于等于 0 的。
43 | * 搜索算法返回一个列表,列表的元素表示一个环。每个环表示为一个数组,数组中的元素是顶点索引。
44 | *
45 | * 本算法实现使用了 Donald B. Johnson 提出的搜索基本环路的算法。
46 | * 原文见:Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph.
47 | * SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.
48 | * https://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
49 | *
50 | * Johnson 算法的是基于强连通分量搜索的。强连通分量的搜索参考:
51 | * Robert Tarjan: Depth-first search and linear graph algorithms.
52 | * In: SIAM Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.
53 | *
54 | * 对有向图的表示方式(邻接矩阵、或邻接表)进行剥离,保留算法结构。
55 | *
56 | * @author zhangyef_at_yonyou_dot_com
57 | */
58 | class ElementaryCircuitsSearch {
59 | constructor(graphData) {
60 | this.graphData = graphData; // 存储原始业务数据节点
61 |
62 | this.B = {};
63 | this.blockedTags = {};
64 | this.s = undefined;
65 |
66 | this.stack = [];
67 |
68 | this.circuits = [];
69 | }
70 |
71 | unblock(u, blockedTags) {
72 | const that = this;
73 | blockedTags[u] = false;
74 | let elementsFromB = this.B[u];
75 | while (elementsFromB.length > 0) {
76 | let w = elementsFromB[0];
77 | elementsFromB.shift();
78 | if (blockedTags[w]) {
79 | that.unblock(w, blockedTags);
80 | }
81 | }
82 | }
83 |
84 |
85 | /**
86 | * v 表示顶点
87 | * @return 找到环路,返回 true;否则返回 false。
88 | */
89 | findCircuit(v, s, adjList) {
90 | const that = this;
91 | let f = false;
92 |
93 | this.stack.push(v);
94 | this.blockedTags[v] = true;
95 | adjList[v].forEach(function (w) {
96 | if (w === s) {
97 | // output circuit
98 | let cycle = [];
99 | for (let j = 0; j < that.stack.length; j++) {
100 | cycle.push(that.stack[j]); //TODO 存放索引,也可以变更为存储真实的节点
101 | }
102 | that.circuits.push(cycle);
103 |
104 | f = true;
105 | } else if (!that.blockedTags[w]) {
106 | if (that.findCircuit(w, s, adjList)) {
107 | f = true;
108 | }
109 | }
110 | });
111 | if (f) {
112 | this.unblock(v, this.blockedTags);
113 | } else {
114 | adjList[v].forEach(function (w) {
115 | if (that.B[w].indexOf(v) == -1) {
116 | that.B[w].push(v);
117 | }
118 | });
119 | }
120 |
121 | this.stack.pop();
122 |
123 | return f;
124 | }
125 |
126 | /**
127 | * 根据环路的顶点索引,恢复图的原始顶点数。
128 | */
129 | _restoreGraphNodeFromId(circuits, graph) {
130 | let nodes = graph.nodes();
131 | let nodeIdMap = {};
132 | for(let i = 0; i < nodes.length; i++) {
133 | nodeIdMap[nodes[i].id] = nodes[i];
134 | }
135 |
136 | let restoredCircuits = [];
137 | for(let i = 0; i < circuits.length; i++) {
138 | let circuit = circuits[i];
139 | let restoredCircuit = [];
140 | for(let j = 0; j < circuit.length; j++) {
141 | restoredCircuit.push(nodeIdMap[circuit[j]].data);
142 | }
143 |
144 | restoredCircuits.push(restoredCircuit);
145 | }
146 |
147 | return restoredCircuits;
148 | }
149 |
150 | // 根据邻接表查找自己引用自己的节点
151 | _findSelfCircuit(adjList) {
152 | let circuits = [];
153 | for(let from = 0; from < adjList.length; from++) {
154 | let edges = adjList[from];
155 | for(let i = 0; i < edges.length; i++) {
156 | let to = edges[i];
157 | if(to === from) {
158 | // self cycle
159 | circuits.push([from]);
160 | break;
161 | }
162 | }
163 | }
164 |
165 | return circuits;
166 | }
167 |
168 | /**
169 | * 算法的入口
170 | */
171 | run() {
172 | this.stack = [];
173 | this.circuits = [];
174 | GenIdForGraph(this.graphData);
175 | this.adjList = _convertGraph(this.graphData);
176 | this.blockedTags = new Array(this.adjList.length);
177 | this.B = new Array(this.adjList.length);
178 |
179 | let sccs = new StrongConnectedComponents(this.adjList);
180 |
181 | let s = 0;
182 | while (true) {
183 | let sccResult = sccs.getAdjacencyList(s);
184 | if (sccResult && sccResult.getAdjList()) {
185 | let scc = sccResult.getAdjList();
186 | s = sccResult.getLowestNodeId();
187 | for (let j = 0; j < scc.length; j++) {
188 | if (scc[j] && scc[j].length > 0) {
189 | this.blockedTags[j] = false;
190 | this.B[j] = [];
191 | }
192 | }
193 |
194 | this.findCircuit(s, s, scc);
195 | s++;
196 | continue;
197 | }
198 | break;
199 | }
200 |
201 | let selfCircuits = this._findSelfCircuit(this.adjList);
202 | this.circuits = selfCircuits.concat(this.circuits);
203 |
204 | return this._restoreGraphNodeFromId(this.circuits, this.graphData);
205 | }
206 | }
207 |
208 | exports.ElementaryCircuitsSearch = ElementaryCircuitsSearch;
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/common/Graph.js:
--------------------------------------------------------------------------------
1 | const Map = require('core-js-pure/features/map');
2 |
3 | /**
4 | * 图结构: 有向图
5 | */
6 |
7 | class Node {
8 | constructor(data) {
9 | this.data = data;
10 |
11 | this.incoming = new Map(); //TODO: 考虑 Map 类型的兼容性
12 | this.outgoing = new Map();
13 | }
14 |
15 | clone() {
16 | function cloneMap(map) {
17 | const copy = new Map();
18 | map.forEach(function (v, k) {
19 | copy.set(k, v);
20 | });
21 |
22 | return copy;
23 | }
24 | let copy = new Node(this.data);
25 | copy.incoming = cloneMap(this.incoming);
26 | copy.outgoing = cloneMap(this.outgoing);
27 | return copy;
28 | }
29 | }
30 |
31 |
32 |
33 | class Graph {
34 | constructor(hashFn) {
35 | // noop
36 | this._hashFn = hashFn;
37 | this._nodes = new Map();
38 | }
39 |
40 | clone() {
41 | /**
42 | * 拷贝 key,拷贝 value(value中保存着 Node)
43 | */
44 | function cloneNodes(map) {
45 | let copy = new Map();
46 | map.forEach(function (v, k) {
47 | if (v instanceof Node) {
48 | copy.set(k, v.clone());
49 | }
50 | });
51 | return copy;
52 | }
53 | let copy = new Graph(this._hashFn);
54 | copy._nodes = cloneNodes(this._nodes);
55 | return copy;
56 | }
57 |
58 | roots() {
59 | const ret = [];
60 | for (let node of this._nodes.values()) {
61 | if (node.outgoing.size === 0) {
62 | ret.push(node);
63 | }
64 | }
65 |
66 | return ret;
67 | }
68 |
69 | /**
70 | * 返回所有顶点
71 | */
72 | nodes() {
73 | const ret = [];
74 | for (let node of this._nodes.values()) {
75 | ret.push(node);
76 | }
77 |
78 | return ret;
79 | }
80 |
81 | /**
82 | * 返回所有顶点的 key
83 | */
84 | nodeKeys() {
85 | const ret = [];
86 | for (let node of this._nodes.keys()) {
87 | ret.push(node);
88 | }
89 |
90 | return ret;
91 | }
92 |
93 | /**
94 | * 返回所有顶点的原始数据
95 | */
96 | nodeDatas() {
97 | const ret = [];
98 | for (let node of this._nodes.values()) {
99 | ret.push(node.data);
100 | }
101 |
102 | return ret;
103 | }
104 |
105 | // 边上可以携带属性 props 对象。
106 | insertEdge(from, to, props) {
107 | const fromNode = this.lookupOrInsertNode(from);
108 |
109 | const toNode = this.lookupOrInsertNode(to);
110 |
111 | fromNode.outgoing.set(this._hashFn(to), { toNode, props });
112 | toNode.incoming.set(this._hashFn(from), { fromNode, props });
113 | }
114 |
115 |
116 | _refreshNodeEdges(node) {
117 | const that = this;
118 | function _valueList(map) {
119 | const ret = [];
120 | for (let v of map.values()) {
121 | ret.push(v);
122 | }
123 |
124 | return ret;
125 | }
126 |
127 |
128 | // 刷新入度
129 | const incomingList = _valueList(node.incoming);
130 | const syncedInComing = new Map();
131 | incomingList.forEach(function (incoming) {
132 | let fromNode = incoming.fromNode;
133 | syncedInComing.set(that._hashFn(fromNode.data), incoming);
134 | })
135 |
136 | node.incoming.clear();
137 | node.incoming = syncedInComing;
138 |
139 | // 刷新出度
140 | const outgoingList = _valueList(node.outgoing);
141 | const syncedOutgoing = new Map();
142 | outgoingList.forEach(function (outgoing) {
143 | let toNode = outgoing.toNode;
144 | syncedOutgoing.set(that._hashFn(toNode.data), outgoing);
145 | })
146 |
147 | node.outgoing.clear();
148 | node.outgoing = syncedOutgoing;
149 | }
150 |
151 | /**
152 | * 刷新图的状态:用于根据图中的节点,更新图的 hashkey。
153 | *
154 | * 假设图中节点持有的数据都已经更新。
155 | */
156 | refreshNodes() {
157 | const that = this;
158 | let nodes = this.nodes();
159 | let syncedNodes = new Map();
160 | nodes.forEach(function (node) {
161 | syncedNodes.set(that._hashFn(node.data), node);
162 |
163 | // 刷新节点的入度、出度
164 | that._refreshNodeEdges(node);
165 | });
166 |
167 | this._nodes.clear();
168 | this._nodes = syncedNodes;
169 | }
170 |
171 | /**
172 | * 当 data 对象的部分数据域发生变更时,需要更新这部分数据。
173 | * @param {*} data
174 | */
175 | updateNode(data) {
176 | let node = this.lookup(data);
177 | if (node) {
178 | node.data = data;
179 | return true;
180 | }
181 |
182 | return false;
183 | }
184 |
185 | /**
186 | * 移除 oldKey 位置处的数据,移动到新位置。
187 | *
188 | * 当节点的数据发生更新后,图中节点的 key 由于会受到 data 的影响,也需要更新。
189 | * 此时,使用本方法移动节点。在移动后,节点的真实数据不变。
190 | * @param {Object} oldData 节点的旧数据
191 | * @param {Object} data 节点数据
192 | */
193 | moveNode(oldData, data) {
194 | let oldNode = this.lookup(oldData);
195 | let oldKey = this.lookupKey(oldData);
196 | this._nodes.delete(oldKey);
197 |
198 | const newKey = this._hashFn(data);
199 | this._nodes.set(newKey, oldNode);
200 | }
201 |
202 | removeNode(data) {
203 | const key = this._hashFn(data);
204 | this._nodes.delete(key);
205 | for (let node of this._nodes.values()) {
206 | node.outgoing.delete(key);
207 | node.incoming.delete(key);
208 | }
209 | }
210 |
211 | /**
212 | * 移除从 data 节点出发的所有有向边
213 | */
214 | removeNodeOutgoings(data) {
215 | const node = this.lookup(data);
216 | if (!node) {
217 | return;
218 | }
219 | const fromKey = this._hashFn(data);
220 | const outgoings = node.outgoing;
221 | for (let { toNode } of outgoings.values()) {
222 | toNode.incoming.delete(fromKey);
223 | const toKey = this._hashFn(toNode.data);
224 | node.outgoing.delete(toKey);
225 | }
226 | }
227 |
228 | /**
229 | * 移除孤立节点:没有入度、没有出度。
230 | */
231 | clearIsolatedNodes() {
232 | // 如果生成了孤立节点,则移除孤立的节点
233 | let nodes = this.nodes();
234 | for (let i = 0; i < nodes.length; i++) {
235 | let n = nodes[i];
236 | if (n.outgoing.size === 0 && n.incoming.size === 0) {
237 | this.removeNode(n.data);
238 | }
239 | }
240 | }
241 |
242 | lookupOrInsertNode(data) {
243 | const key = this._hashFn(data);
244 | let node = this._nodes.get(key);
245 |
246 | if (!node) {
247 | node = new Node(data);
248 | this._nodes.set(key, node);
249 | }
250 |
251 | return node;
252 | }
253 |
254 | lookup(data) {
255 | return this._nodes.get(this._hashFn(data));
256 | }
257 |
258 | lookupKey(data) {
259 | return this._hashFn(data);
260 | }
261 |
262 | isEmpty() {
263 | return this._nodes.size === 0;
264 | }
265 |
266 | toString() {
267 | let data = [];
268 | for (const entry of this._nodes) {
269 | let key = entry[0];
270 | let value = entry[1];
271 | data.push(`${key}, (incoming)[${[...value.incoming.keys()].join(', ')}], (outgoing)[${[...value.outgoing.keys()].join(', ')}]`)
272 | }
273 |
274 | return data.join('\n');
275 | }
276 |
277 | /**
278 | * 输出 DOT 语言的有向图结构。
279 | * @see https://graphviz.org/doc/info/lang.html
280 | * @see https://dreampuf.github.io/GraphvizOnline/
281 | */
282 | toDotString() {
283 | let edges = [];
284 | for (const entry of this._nodes) {
285 | let fromkey = entry[0];
286 | let value = entry[1];
287 | let outgoing = value.outgoing.keys();
288 | for (const going of outgoing) {
289 | edges.push(`\"${fromkey}\" -> \"${going}\"`);
290 | }
291 | }
292 | return `digraph g {` +
293 | `${edges.join(';')}` +
294 | `}`;
295 | }
296 | }
297 |
298 | exports.Node = Node;
299 | exports.Graph = Graph;
300 |
301 |
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/common/StrongConnectedComponents.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 强连通组件
3 | */
4 | class SCCResult {
5 | /**
6 | * @param {Array[Array]} adjList
7 | */
8 | constructor(adjList, lowestNodeId) {
9 | this.adjList = adjList;
10 | this.lowestNodeId = lowestNodeId;
11 | this.nodeIDsOfSCC = {}; // ID 的集合
12 | if (this.adjList) {
13 | for (let i = this.lowestNodeId; i < this.adjList.length; i++) {
14 | if (this.adjList[i].length > 0) {
15 | this.nodeIDsOfSCC[i] = i;
16 | }
17 | }
18 | }
19 | }
20 |
21 | getAdjList() {
22 | return this.adjList;
23 | }
24 |
25 | getLowestNodeId() {
26 | return this.lowestNodeId;
27 | }
28 | }
29 |
30 |
31 |
32 | class StrongConnectedComponents {
33 | constructor(adjListOriginal) {
34 | this.adjListOriginal = adjListOriginal;
35 | this.adjList = undefined; //当前可视子图的的邻接表
36 | this.visited = undefined; // 辅助属性, [boolean]
37 | this.stack = undefined; // 辅助属性, []
38 | this.lowlink = undefined; //辅助属性 [int]
39 | this.number = undefined; // 辅助属性 []
40 | this.sccCounter = 0; // 辅助属性
41 | this.currentSCCs = undefined; //辅助属性 []
42 | }
43 |
44 |
45 |
46 | /**
47 | * 本函数返回强连通分量的邻接表结构,该强连通分量包含子图的最少顶点数。
48 | * 顶点分别为 {s, s + 1, ..., n},s 是给定顶点。
49 | * 注意,只有一个顶点的的强连通分量无法返回。
50 | * TODO: 考虑如何处理只有一个顶点的情况。
51 | * @param {int} node s,顶点 s
52 | * @return SCCResult
53 | */
54 | getAdjacencyList(node) {
55 | let nodeNum = this.adjListOriginal.length;
56 | this.visited = new Array(nodeNum);
57 | this.lowlink = new Array(nodeNum);
58 | this.number = new Array(nodeNum);
59 | this.stack = [];
60 | this.currentSCCs = [];
61 |
62 | this.makeAdjListSubgraph(node);
63 |
64 | for (let i = node; i < nodeNum; i++) {
65 | if (!this.visited[i]) {
66 | this.getStrongConnectedComponents(i);
67 | let nodes = this.getLowestIdComponent();
68 | if (nodes && nodes.indexOf(node) == -1 && nodes.indexOf(node + 1) == -1) {
69 | return this.getAdjacencyList(node + 1);
70 | }
71 |
72 | let adjacencyList = this.getAdjList(nodes);
73 | if (adjacencyList) {
74 | for (let j = 0; j < nodeNum; j++) {
75 | if (adjacencyList[j].length > 0) {
76 | return new SCCResult(adjacencyList, j);
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
83 | return undefined;
84 | }
85 |
86 | /**
87 | * 根据包含指定顶点的子图构建邻接表。
88 | * 指定顶点都 >= 给定的顶点索引。
89 | */
90 | makeAdjListSubgraph(node) {
91 | this.adjList = new Array(this.adjListOriginal.length);
92 | for (let i = 0; i < this.adjList.length; i++) {
93 | this.adjList[i] = [];
94 | }
95 |
96 | for (let i = node; i < this.adjList.length; i++) {
97 | let successors = [];
98 | for (let j = 0; j < this.adjListOriginal[i].length; j++) {
99 | if (this.adjListOriginal[i][j] >= node) {
100 | successors.push(this.adjListOriginal[i][j]);
101 | }
102 | }
103 | if (successors.length > 0) {
104 | this.adjList[i] = successors;
105 | }
106 | }
107 | }
108 |
109 | /**
110 | * 从给定节点搜索强连通分量
111 | * @param {int} root 开始搜索的顶点
112 | */
113 | getStrongConnectedComponents(root) {
114 | this.sccCounter++;
115 | this.lowlink[root] = this.sccCounter;
116 | this.number[root] = this.sccCounter
117 | this.visited[root] = true;
118 | this.stack.push(root);
119 |
120 | for (let i = 0; i < this.adjList[root].length; i++) {
121 | let w = this.adjList[root][i];
122 | if (!this.visited[w]) {
123 | this.getStrongConnectedComponents(w);
124 | this.lowlink[root] = Math.min(this.lowlink[root], this.lowlink[w]);
125 | } else if ((this.stack.indexOf(w) != -1) && this.number[w] < this.lowlink[root]) {
126 | this.lowlink[root] = this.number[w];
127 | }
128 | }
129 |
130 | // 找到强连通分量
131 | if ((this.lowlink[root] === this.number[root]) && this.stack.length > 0) {
132 | let next = -1;
133 | let scc = [];
134 | do {
135 | next = this.stack.pop();
136 | scc.push(next);
137 | } while (this.number[next] > this.number[root]);
138 |
139 | // 只有一个节点的简单连通分量不会被添加
140 | if (scc.length > 1) {
141 | this.currentSCCs.push(scc);
142 | }
143 | }
144 | }
145 |
146 | /**
147 | * 根据联通分量集合,计算“包含最小顶点索引”的强连通分量。
148 | */
149 | getLowestIdComponent() {
150 | let min = this.adjList.length;
151 | let currScc = undefined;
152 | for (let i = 0; i < this.currentSCCs.length; i++) {
153 | let scc = this.currentSCCs[i];
154 | for (let j = 0; j < scc.length; j++) {
155 | let node = scc[j];
156 | if (node < min) {
157 | currScc = scc;
158 | min = node;
159 | }
160 | }
161 | }
162 | return currScc;
163 | }
164 |
165 | /**
166 | * 返回强连通分量的邻接表
167 | */
168 | getAdjList(nodes) {
169 | let lowestIdAdjacencyList = undefined;
170 | if (nodes) {
171 | lowestIdAdjacencyList = new Array(this.adjList.length);
172 | for (let i = 0; i < lowestIdAdjacencyList.length; i++) {
173 | lowestIdAdjacencyList[i] = [];
174 | }
175 | for (let i = 0; i < nodes.length; i++) {
176 | let node = nodes[i];
177 | for (let j = 0; j < this.adjList[node].length; j++) {
178 | let succ = this.adjList[node][j];
179 | if (nodes.indexOf(succ) != -1) {
180 | lowestIdAdjacencyList[node].push(succ);
181 | }
182 | }
183 | }
184 | }
185 |
186 | return lowestIdAdjacencyList
187 | }
188 | }
189 |
190 | module.exports = StrongConnectedComponents;
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/test/ElementaryCircuitsSearch.test.js:
--------------------------------------------------------------------------------
1 | const expect = require('chai').expect;
2 | const Graph = require('platform/formula/cellDependency/common/Graph').Graph;
3 | const Search = require('platform/formula/cellDependency/common/ElementaryCircuitsSearch').ElementaryCircuitsSearch;
4 |
5 |
6 | describe('有向图算法', function () {
7 | let graph = undefined;
8 |
9 | function fillEdges(edges, g) {
10 | if (!edges) {
11 | return;
12 | }
13 |
14 | edges.forEach(function (e) {
15 | g.insertEdge(e[0], e[1]);
16 | })
17 | }
18 |
19 | beforeEach(function () {
20 | graph = new Graph(function (v) {
21 | return v;
22 | })
23 | })
24 | it('应找到一个环路', function () {
25 | // ┌───┐ ┌───┐ ┌───┐
26 | // │ 1 │◀────────│ 2 │─────────▶│ 3 │
27 | // └───┘ └───┘ └───┘
28 | // │ ▲
29 | // │ │
30 | // ▼ │
31 | // ┌───┐ ┌───┐
32 | // │ 4 │────────▶│ 5 │
33 | // └───┘ └───┘
34 | fillEdges([
35 | [1, 4],
36 | [2, 1], [2, 3],
37 | [4, 5],
38 | [5, 2]
39 | ], graph);
40 |
41 | let search = new Search(graph);
42 | let ret = search.run();
43 | expect(ret).to.has.lengthOf(1);
44 | });
45 |
46 | // 用于处理 A1=A1 的循环引用
47 | it('应找到指向自己的环路(A1=A1)', function () {
48 | // ┌────┐
49 | // │ │
50 | // ▼ │
51 | // ┌───┐ │
52 | // │ 1 │──┘
53 | // └───┘
54 | fillEdges([
55 | [1, 1]
56 | ], graph);
57 |
58 | let search = new Search(graph);
59 | let ret = search.run();
60 | expect(ret).to.has.lengthOf(1);
61 | });
62 |
63 | it('应找到全部环路', function () {
64 | // ┌───┐ ┌───┐ ┌───┐
65 | // │ 1 │◀────────│ 2 │─────────▶│ 3 │
66 | // └───┘ └───┘ └───┘
67 | // │ ▲ │
68 | // │ │ │
69 | // ▼ │ ▼
70 | // ┌───┐ ┌───┐ ┌───┐
71 | // │ 4 │────────▶│ 5 │◀─────────│ 6 │
72 | // └───┘ └───┘ └───┘
73 | // ▲ │ ▲
74 | // │ │ │
75 | // │ ▼ │
76 | // ┌───┐ ┌───┐ ┌───┐
77 | // │ 7 │◀────────│ 8 │─────────▶│ 9 │
78 | // └───┘ └───┘ └───┘
79 |
80 | fillEdges([
81 | [1, 4],
82 | [2, 1], [2, 3],
83 | [3, 6],
84 | [4, 5],
85 | [5, 2], [5, 8],
86 | [6, 5],
87 | [7, 4],
88 | [8, 7], [8, 9],
89 | [9, 6]
90 | ], graph);
91 |
92 | let search = new Search(graph);
93 | let ret = search.run();
94 | expect(ret).to.has.lengthOf(4);
95 | });
96 |
97 | it('应找到一个环路(A1=B1, B1=A1)', function () {
98 | // ┌──────────┐
99 | // ▼ │
100 | // ┌───┐ ┌───┐
101 | // │ 1 │ │ 2 │
102 | // └───┘ └───┘
103 | // │ ▲
104 | // └──────────┘
105 | fillEdges([
106 | [1, 2],
107 | [2, 1]
108 | ], graph);
109 |
110 | let search = new Search(graph);
111 | let ret = search.run();
112 | expect(ret).to.has.lengthOf(1);
113 | });
114 |
115 | it('应找不到环路', function () {
116 |
117 | // ┌───┐ ┌───┐ ┌───┐
118 | // │ 0 │◀────────│ 2 │─────────▶│ 3 │
119 | // └───┘ └───┘ └───┘
120 | // │
121 | // │
122 | // ▼
123 | // ┌───┐
124 | // │ 1 │
125 | // └───┘
126 |
127 | fillEdges([
128 | [0, 1],
129 | [2, 0],
130 | [2, 3]
131 | ], graph);
132 |
133 | let search = new Search(graph);
134 | let ret = search.run();
135 | expect(ret).to.has.lengthOf(0);
136 | });
137 | })
--------------------------------------------------------------------------------
/src/platform/formula/cellDependency/test/Graph.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 图数据结构测试。
3 | */
4 | const expect = require('chai').expect;
5 | const Graph = require('platform/formula/cellDependency/common/Graph').Graph;
6 |
7 | describe('图', function () {
8 | it('应正确构造一个图', function () {
9 | let g = new Graph(function(v){
10 | return v;
11 | });
12 |
13 | // 1 -> 2
14 | // 2 -> 3
15 | // 2 -> 4
16 | // 3 -> 4
17 | g.insertEdge(1, 2);
18 | g.insertEdge(2, 3);
19 | g.insertEdge(2, 4);
20 | g.insertEdge(3, 4);
21 |
22 | let nodes = g.nodes(); // expect 1, 2, 3, 4
23 | let roots = g.roots(); // expect 4
24 |
25 | expect(nodes).to.has.lengthOf(4);
26 | expect(roots).to.has.lengthOf(1);
27 | expect(roots[0].data).to.equal(4);
28 | })
29 | });
--------------------------------------------------------------------------------
/src/platform/formula/cellEvaluation/CellValueProviderProxy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 单元格数据的读写,不做公式计算。
3 | */
4 |
5 | class CellValueProviderProxy {
6 | /**
7 | * 外部组件提供的单元格值的获取器
8 | */
9 | constructor(cellValueProvider) {
10 | this.cellValueProvider = cellValueProvider;
11 | // 自定义函数
12 | this.customFns = cellValueProvider.customFns;
13 | }
14 |
15 | /**
16 | * 获取单元格值。
17 | * @param {SimpleCellAddress} cellAddress
18 | */
19 | getCellValue(cellAddress) {
20 | // 转换为外部的单元格值获取接口参数
21 | return this.cellValueProvider.getCellValue(cellAddress);
22 | }
23 |
24 | /**
25 | * 获取单元格范围的值,是一个数组。
26 | * @param {SimpleCellRange} cellRange
27 | */
28 | getCellRangeValues(cellRange) {
29 | return this.cellValueProvider.getCellRangeValues(cellRange);
30 | }
31 |
32 | /**
33 | * 获取数据对象(公式中的特定常量、特定档案标识等)
34 | */
35 | fetchDataObject(object, property){
36 | return this.cellValueProvider.fetchDataObject(object, property)
37 | }
38 |
39 | /**
40 | * 获取浮动单元格范围的值,是一个数组。
41 | * @param {SimpleCellRange} cellRange
42 | */
43 | getCellFloatRangeValues(cellRange) {
44 | return this.cellValueProvider.getCellFloatRangeValues(cellRange);
45 | }
46 |
47 | /**
48 | * 获取单元格的公式
49 | */
50 | getCellFormula(cellAddress) {
51 | return this.cellValueProvider.getCellFormula(cellAddress);
52 | }
53 |
54 | /**
55 | * 设置单元格值
56 | */
57 | setCellValue(cellAddress, newValue) {
58 | return this.cellValueProvider.setCellValue(cellAddress, newValue);
59 | }
60 |
61 | /**
62 | * 设置单元格的公式
63 | */
64 | setCellFormula(cellAddress, newFormula) {
65 | return this.cellValueProvider.setCellFormula(cellAddress, newFormula);
66 | }
67 |
68 | /**
69 | * 返回单元格范围的值的集合
70 | */
71 | getCellRangeValues(cellRange) {
72 | return this.cellValueProvider.getCellRangeValues(cellRange);
73 | }
74 | }
75 |
76 | module.exports = CellValueProviderProxy;
--------------------------------------------------------------------------------
/src/platform/formula/cellEvaluation/EvaluationErrors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 公式计算错误:
3 | * 共 7 种。
4 | * (1) 除数为 0 的错误。 DivideByZeroError
5 | * (2) 数值类型错误。 NotANumberError
6 | * (3) 非法的符号。 NameError
7 | * (4) 空错误。 NullError
8 | * (5) 参数错误。 NumberError
9 | * (6) 单元格地址错误。 RefError
10 | * (7) 单元格数据类型错误。 ValueError
11 | *
12 | * https://support.microsoft.com/zh-cn/office/检测公式中的错误-3a8acca5-1d61-4702-80e0-99a36a2822c1
13 | */
14 |
15 | /**
16 | * 所有计算错误的基类
17 | */
18 | class EvaluationError extends Error {
19 | constructor(msg) {
20 | super(msg);
21 | this.wrongResult = undefined; // 异常发生时的计算结果。该结果可以用于外部显示使用。
22 | }
23 |
24 | /**
25 | * 返回计算错误时的结果。
26 | */
27 | getResult() {
28 | return this.wrongResult;
29 | }
30 | }
31 |
32 | /**
33 | * 除数为 0 的错误
34 | */
35 | class DivideByZeroError extends EvaluationError{
36 | constructor(msg){
37 | super(msg);
38 | this.wrongResult = '#DIV/0!';
39 | }
40 | }
41 |
42 | /**
43 | * 当某个值不可用于函数或公式时,将显示此错误。
44 | */
45 | class NotANumberError extends EvaluationError{
46 | constructor(msg){
47 | super(msg);
48 | this.wrongResult = '#N/A'
49 | }
50 | }
51 |
52 | /**
53 | * 当无法识别公式中的文本时,将显示此错误。 例如,区域名称或函数名称可能拼写错误。
54 | */
55 | class NameError extends EvaluationError{
56 | constructor(msg){
57 | super(msg);
58 | this.wrongResult = '#NAME?'
59 | }
60 | }
61 |
62 | /**
63 | * 当你指定两个不相交的区域的交集时,Excel 将显示此错误。 交集运算符是分隔公式中的引用的空格字符。
64 | */
65 | class NullError extends EvaluationError{
66 | constructor(msg){
67 | super(msg);
68 | this.wrongResult = '#NULL!'
69 | }
70 | }
71 |
72 | /**
73 | * 当公式或函数包含无效数值时,将显示此错误。
74 | * 你使用的是循环访问的函数 (如 IRR 或 RATE) 吗?如果是, 则 #NUM!错误可能是由于函数无法找到结果而导致的。
75 | */
76 | class NumberError extends EvaluationError{
77 | constructor(msg){
78 | super(msg);
79 | this.wrongResult = '#NUM!'
80 | }
81 | }
82 |
83 | /**
84 | * 当单元格引用无效时,将显示此错误。
85 | * 例如,你可能删除了其他公式所引用的单元格,或者可能将所移动的单元格粘贴到其他公式所引用的单元格上。
86 | */
87 | class RefError extends EvaluationError{
88 | constructor(msg){
89 | super(msg);
90 | this.wrongResult = '#REF!'
91 | }
92 | }
93 |
94 | /**
95 | * 如果公式中包含含有不同数据类型的单元格,则 Excel 会显示此错误。
96 | */
97 | class ValueError extends EvaluationError {
98 | constructor(msg){
99 | super(msg);
100 | this.wrongResult = '#VALUE!'
101 | }
102 | }
103 |
104 | exports.EvaluationError = EvaluationError;
105 |
106 | exports.DivideByZeroError = DivideByZeroError;
107 | exports.NotANumberError = NotANumberError;
108 | exports.NameError = NameError;
109 | exports.NullError = NullError;
110 | exports.NumberError = NumberError;
111 | exports.RefError = RefError;
112 | exports.ValueError = ValueError;
--------------------------------------------------------------------------------
/src/platform/formula/cellEvaluation/Evaluator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 公式求值器,计算公式的具体数值。
3 | */
4 | const types = require('base/common/types');
5 |
6 | const CellValueProviderProxy = require('platform/formula/cellEvaluation/CellValueProviderProxy');
7 | const FormulaEvaluationVisitor = require('platform/formula/cellEvaluation/FormulaEvaluationVisitor').FormulaEvaluationVisitor;
8 | const { SimpleCellAddress } = require('platform/formula/cellAddressParts/common/CellAddressParts');
9 |
10 | /**
11 | * 根据依赖图对单元格求值。
12 | * 本“求值器”依赖于单元格依赖关系构建器。
13 | * @see DependencyBuilder
14 | */
15 | class Evaluator {
16 | constructor(depGraph, tableCellValueProvider) {
17 | this.depGraph = depGraph;
18 | this.cellValueProxy = new CellValueProviderProxy(tableCellValueProvider);
19 | }
20 |
21 | /**
22 | * 对某个单元格的公式求值。
23 | * @param {String} activeSheetName 工作表名称
24 | * @param {Object} cellAddr 单元格地址对象 {column:<1..n>, row:<1..n>}
25 | * @return 公式计算后的值。
26 | */
27 | evaluate(activeSheetName, cellAddr) {
28 | let simpleAddr = SimpleCellAddress.build(activeSheetName, cellAddr.column, cellAddr.row);
29 | let formulaAST = this.depGraph.getCellFormulaAST(activeSheetName, simpleAddr);
30 | // 如果没有公式,则直接从单元格地址取值
31 | if (!formulaAST) {
32 | return this.cellValueProxy.getCellValue(simpleAddr);
33 | }
34 |
35 | return this.evaluateAST(formulaAST);
36 | }
37 |
38 | evaluateAST(formulaAST) {
39 | let ownerSheetName = formulaAST.ownerSheetName;
40 | return formulaAST.accept(new FormulaEvaluationVisitor(this.cellValueProxy, ownerSheetName));
41 | }
42 |
43 | /**
44 | * 重新计算受影响的单元格
45 | */
46 | reEvaluateAll(activeSheetName, fromCellAddr) {
47 | const that = this;
48 | let simpleAddr = SimpleCellAddress.build(activeSheetName, fromCellAddr.column, fromCellAddr.row);
49 |
50 | // fromCellAddr 可能是依赖图中的一个单元格顶点,也可能是一个单元格范围顶点的一部分。
51 | let sortedList = this.depGraph.sortSubgraph(simpleAddr);
52 | return sortedList.forEach(function(sorted){
53 | // fromCellAddr 表示当前手工输入数据的单元格,此时,如果 fromCellAddr 包含公式,则不执行自身公式。
54 | // 如果当前计算节点是 fromCellAddr,表明 sorted 节点不需要执行自身的公式。
55 | // 支持的场景:自动计算的公式被用户手工修改了计算结果。
56 | // sorted 表示一个图,由排序后的节点数组表示。
57 | let filterStartNodeList = sorted.filter(function(cellData) {
58 | let cellAddress = cellData.cellAddress;
59 | if(simpleAddr.equals(cellAddress)){
60 | return false;
61 | }
62 | return true;
63 | });
64 | return that._evaluateOneByOne(filterStartNodeList);
65 | })
66 | }
67 |
68 | _evaluateOneByOne(sorted) {
69 | if (!sorted) {
70 | return undefined;
71 | }
72 | let that = this;
73 | if (types.isArray(sorted)) {
74 | sorted.forEach(function (cellData) {
75 |
76 | // 根据语法树求值
77 | let formula = cellData.getFormulaAST();
78 |
79 | // 如果有公式,需要根据公式求值,如果没有公式,则直接跳过
80 | if (formula) {
81 | // 在求值过程中,会同步调用 cellValueProxy 获取单元格数据
82 | let ret = that.evaluateAST(formula);
83 |
84 | // TODO: 如果发生计算异常,则缓存成输出的值,反馈外部。
85 |
86 | // 在求值完毕后,会同步调用 cellValueProxy 设置单元格值,因为后续单元格会依赖该值的计算
87 | that.cellValueProxy.setCellValue(cellData.cellAddress, ret);
88 | }
89 | });
90 | }
91 | }
92 |
93 | /**
94 | * 执行全部单元格的重新计算
95 | */
96 | evaluateAll() {
97 | let sorted = this.depGraph.sort();
98 | return this._evaluateOneByOne(sorted);
99 | }
100 | }
101 |
102 |
103 | exports.Evaluator = Evaluator;
--------------------------------------------------------------------------------
/src/platform/formula/cellEvaluation/FormulaEvaluationVisitor.js:
--------------------------------------------------------------------------------
1 | const formulaFns = require('@formulajs/formulajs');
2 |
3 | const types = require('base/common/types');
4 | const {
5 | SimpleCellAddress,
6 | SimpleCellRange
7 | } = require('platform/formula/cellAddressParts/common/CellAddressParts');
8 | const StringUtils = require('../../../base/StringUtils').StringUtils;
9 |
10 | const EvaluationErrors = require('platform/formula/cellEvaluation/EvaluationErrors');
11 |
12 | /**
13 | * 针对单个公式求值。
14 | *
15 | * 与 {SingleFormulaAST} 对象 配合使用。
16 | */
17 | class FormulaEvaluationVisitor {
18 | /**
19 | *
20 | * @param {*} cellValueProxy 单元格/函数 求值代理
21 | * @param {String} ownerSheetName 当前公式所属的工作表名称。
22 | */
23 | constructor(cellValueProxy, ownerSheetName) {
24 | this.cellValueProxy = cellValueProxy;
25 | this.ownerSheetName = ownerSheetName;
26 | }
27 |
28 | setCellValueProvider(provider) {
29 | if (!provider) {
30 | return;
31 | }
32 | this.cellValueProxy = provider;
33 | }
34 |
35 | getCellValueProvider() {
36 | return this.cellValueProxy;
37 | }
38 |
39 | visitFormulaProgram(node) {
40 | return node.body.accept(this);
41 | }
42 |
43 | visitExpressionStatement(node) {
44 | return node.expression.accept(this);
45 | }
46 |
47 | /**
48 | * 对于函数调用求值。
49 | *
50 | * TODO: 针对 IF 函数的短路运算?
51 | * @param {*} node
52 | */
53 | visitCallExpression(node) {
54 | const that = this;
55 | let customFns = that.getCellValueProvider().customFns;
56 | let fnName = node.callee.toString();
57 | let args = node.arguments;
58 | let argList = args.map(function (arg) {
59 | return arg.accept(that);
60 | });
61 | const fnNameUpper = fnName.toUpperCase();
62 |
63 | if (customFns && fnName in customFns) {
64 | return customFns[fnName].apply(null, argList);
65 | }
66 |
67 | if (fnNameUpper in formulaFns) {
68 | return formulaFns[fnNameUpper].apply(null, argList);
69 | }
70 |
71 | throw new EvaluationErrors.NameError(`非法的函数 ${fnName},无法识别`);
72 | }
73 |
74 | /**
75 | * 对普通括号表达式求值
76 | */
77 | visitParenthesizedExpression(node) {
78 | let expression = node.expression;
79 | return expression.accept(this);
80 | }
81 |
82 | visitPlainTextIdentifier(node) {
83 | // return node.name;
84 | // TODO: 支持自定义变量取数
85 |
86 | throw new EvaluationErrors.NameError(`无法识别的符号 ${node.name}`);
87 | }
88 |
89 | /**
90 | * @param {RefItemIdentifier} node
91 | */
92 | visitRefItemIdentifier(node) {
93 | return node.name;
94 | }
95 |
96 | /**
97 | * 计算的语法:a[b], a.b
98 | *
99 | * @param {MemberExpression} node
100 | */
101 | visitMemberExpression(node) {
102 | let objectName = node.object.name;
103 | let property = node.property.name;
104 | if (!this.cellValueProxy.fetchDataObject) {
105 | throw new EvaluationError('请提供 fetchDataObject 函数,用于获取数据对象的值')
106 | }
107 | try {
108 | return this.cellValueProxy.fetchDataObject(objectName, property);
109 | } catch (e) {
110 | throw new EvaluationErrors.RefError(`无法获取数据对象的值:${node.toString()}`);
111 | }
112 | }
113 |
114 | /**
115 | * 将语法树节点转换为 SimpleCellAddress
116 | * @param {CellAddressIdentifier} node
117 | */
118 | _buildSimpleCellAddress(node) {
119 | // AST 与用户输入的文本保持一致,其中的节点可能有 sheetName,也可能没有 sheetName
120 | return SimpleCellAddress.buildFromASTNode(this.ownerSheetName, node);
121 | }
122 |
123 | _buildSimpleCellRange(node) {
124 | return SimpleCellRange.buildFromASTNode(this.ownerSheetName, node);
125 | }
126 |
127 | /**
128 | * 单元格地址:CellAddressIdentifier
129 | */
130 | visitCellAddressIdentifier(node) {
131 | if (node.isLost()) {
132 | throw EvaluationErrors.RefError(`单元格无法找到: ${node.toString()}`)
133 | }
134 | // 转换为 SimpleCellAddress
135 | let addr = this._buildSimpleCellAddress(node);
136 | // 根据 SimpleCellAddress 从单元格取值
137 | let cellValue = undefined;
138 | try {
139 | cellValue = this.cellValueProxy.getCellValue(addr);
140 | if (typeof cellValue === 'undefined') {
141 | throw new EvaluationErrors.ValueError(`无法获取单元格的值: ${node.toString()}`);
142 | }
143 | } catch (e) {
144 | throw new EvaluationErrors.ValueError(`无法获取单元格的值: ${node.toString()}`);
145 | }
146 | return cellValue;
147 | }
148 |
149 | __visitCellRangeValue(node, valueFetchFn) {
150 | // 转换为 SimpleCellRange
151 | let addr = this._buildSimpleCellRange(node);
152 | // 根据 SimpleCellRange 从单元格范围取值
153 | let cellValue = undefined;
154 | try {
155 | cellValue = valueFetchFn(addr);
156 | if (typeof cellValue === 'undefined') {
157 | throw new EvaluationErrors.ValueError(`无法获取单元格范围的值: ${node.toString()}`);
158 | }
159 | } catch (e) {
160 | throw new EvaluationErrors.ValueError(`无法获取单元格范围的值: ${node.toString()}`);
161 | }
162 | return cellValue;
163 | }
164 | /**
165 | * 单元格范围:CellRangeIdentifier
166 | */
167 | visitCellRangeIdentifier(node) {
168 | let _this = this;
169 | return this.__visitCellRangeValue(node, function (simpleCellRangeAddr) {
170 | return _this.cellValueProxy.getCellRangeValues(simpleCellRangeAddr);
171 | });
172 | }
173 |
174 | /**
175 | * 浮动单元格范围:CellFloatRangeIdentifier
176 | */
177 | visitCellFloatRangeIdentifier(node) {
178 | let _this = this;
179 | return this.__visitCellRangeValue(node, function (simpleCellRangeAddr) {
180 | return _this.cellValueProxy.getCellFloatRangeValues(simpleCellRangeAddr);
181 | });
182 | }
183 |
184 | visitA1ReferenceIdentifier(node) {
185 |
186 | }
187 |
188 | visitRelativeColumnIdentifier(node) {
189 |
190 | }
191 |
192 | visitAbsoluteColumnIdentifier(node) {
193 |
194 | }
195 |
196 | visitRelativeRowIdentifier(node) {
197 |
198 | }
199 |
200 | visitAbsoluteRowIdentifier(node) {
201 |
202 | }
203 |
204 | /**
205 | * 一元运算符: -,+
206 | */
207 | visitUnaryExpression(node) {
208 | let argValue = node.argument.accept(this);
209 | switch (node.operator) {
210 | case '+':
211 | return argValue;
212 | case '-':
213 | return -1 * argValue;
214 | }
215 |
216 | throw new Error('非法的运算符');
217 | }
218 |
219 | visitBinaryExpression(node) {
220 | let op = node.operator;
221 | let leftValue = node.left.accept(this);
222 | let rightValue = node.right.accept(this);
223 | switch (op) {
224 | case '**':
225 | return Math.pow(leftValue, rightValue);
226 | case '*':
227 | return leftValue * rightValue;
228 | case '/':
229 | if (rightValue === 0) {
230 | throw new EvaluationErrors.DivideByZeroError(`${leftValue} / 0`);
231 | }
232 | return leftValue / rightValue;
233 | case '%':
234 | return leftValue % rightValue;
235 | case '+':
236 | return leftValue + rightValue;
237 | case '-':
238 | return leftValue - rightValue;
239 | case '>':
240 | return leftValue > rightValue;
241 | case '>=':
242 | return leftValue >= rightValue;
243 | case '<':
244 | return leftValue < rightValue;
245 | case '<=':
246 | return leftValue <= rightValue;
247 | case '==':
248 | return leftValue == rightValue;
249 | case '!=':
250 | return leftValue != rightValue;
251 | }
252 |
253 | throw new Error('非法的二元运算符:' + op);
254 | }
255 |
256 | visitLogicalExpression(node) {
257 | let op = node.operator;
258 | let leftValue = node.left.accept(this);
259 | let rightValue = node.right.accept(this);
260 | switch (op) {
261 | case '&&':
262 | return leftValue && rightValue;
263 | case '||':
264 | return leftValue || rightValue;
265 | }
266 |
267 | throw new Error('非法的逻辑运算符:' + op);
268 | }
269 |
270 | visitConditionalExpression(node) {
271 | let testValue = node.test.accept(this);
272 | if (testValue) {
273 | return node.consequent.accept(this);
274 | }
275 |
276 | return node.alternate.accept(this);
277 | }
278 |
279 | visitAssignmentExpression(node) {
280 | // not implemented yet
281 | }
282 |
283 | /**
284 | * 通过 antlr 解析树,转换得到语法树节点 node。
285 | * node.value 的值已经是具体数据类型了。
286 | *
287 | * null, boolean, string(包括字符串两侧的双引号或单引号), number
288 | */
289 | visitLiteral(node) {
290 | if (types.isString(node.value)) {
291 | // 字符串类型包含所有字符串两侧的单引号或双引号
292 | return StringUtils.unwrapText(node.value);
293 | }
294 | return node.value;
295 | }
296 |
297 | /**
298 | * 百分比数字
299 | */
300 | visitPercentageLiteral(node) {
301 | return node.value / 100.0;
302 | }
303 |
304 |
305 | visitArrayExpression(node) {
306 | // not implemented yet
307 | }
308 |
309 | visitObjectExpression(node) {
310 | // not implemented yet
311 | }
312 |
313 | visitSequenceExpression(node) {
314 | // not implemented yet
315 | }
316 |
317 | visitProperty(node) {
318 | // not implemented yet
319 | }
320 |
321 |
322 | // visitChildren(ctx) {
323 | // if (!ctx) {
324 | // return;
325 | // }
326 | // if (ctx.children) {
327 | // return ctx.children.map(child => {
328 | // if (child.children && child.children.length != 0) {
329 | // return child.accept(this);
330 | // } else {
331 | // return child.getText();
332 | // }
333 | // });
334 | // }
335 | // }
336 | }
337 |
338 | exports.FormulaEvaluationVisitor = FormulaEvaluationVisitor;
--------------------------------------------------------------------------------
/src/platform/formula/core/ASTWalker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 遍历语法树
3 | */
4 |
5 | const OPTIONS = {
6 | BREAK: 'break',
7 | CONTINUE: 'continue'
8 | }
9 | /**
10 | * 语法树的遍历。
11 | *
12 | * 本函数可以提供外部使用。
13 | */
14 | function traverse(node, func) {
15 | let option = func(node);
16 | if(option === OPTIONS.BREAK){
17 | // 不在执行孩子节点的访问。
18 | return;
19 | }
20 | for (var key in node) {
21 | if (node.hasOwnProperty(key)) {
22 | var child = node[key];
23 | if (typeof child === 'object' && child !== null) {
24 |
25 | if (Array.isArray(child)) {
26 | child.forEach(function (node) {
27 | traverse(node, func);
28 | });
29 | } else {
30 | traverse(child, func);
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 |
38 |
39 | exports.OPTIONS = OPTIONS;
40 | exports.traverse = traverse;
--------------------------------------------------------------------------------
/src/platform/formula/core/EditorTokensVisitor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 用于生成公式编辑器需要的 token。
3 | * 生成的 token 用于编辑器高亮。
4 | * 需要高亮的 token 有:字面量、单元格地址、函数名称。
5 | */
6 | const ReportFormulaParser = require('../runtime/ReportFormulaParser').ReportFormulaParser;
7 | const ReportFormulaParserVisitor = require('../runtime/ReportFormulaParserVisitor').ReportFormulaParserVisitor;
8 |
9 | function EditorTokensState() {
10 | this._tokenList = [];
11 | }
12 |
13 | EditorTokensState.prototype.push = function (token) {
14 | this._tokenList.push(token);
15 | }
16 |
17 | EditorTokensState.prototype.clear = function () {
18 | this._tokenList = [];
19 | }
20 |
21 | EditorTokensState.prototype.getTokenList = function () {
22 | return this._tokenList;
23 | }
24 |
25 | /**
26 | * 用于支持编辑器的功能
27 | */
28 | class EditorTokensVisitor extends ReportFormulaParserVisitor {
29 | constructor() {
30 | super();
31 | this.tokenSink = new EditorTokensState();
32 |
33 | // token 缓存,用于支持根据坐标查询 token。
34 | // {lineNumber: tokenList}
35 | this.lineTokensIndex = {};
36 | }
37 |
38 | getTokenList() {
39 | return this.tokenSink.getTokenList();
40 | }
41 |
42 | /**
43 | * 根据当前位置获取最近的 token。
44 | * @param {int} line - line=1..n
45 | * @param {int} column - column=0..n-1
46 | */
47 | findTerminalNodeAtCaret(line, column) {
48 | if (column < 0) {
49 | return undefined;
50 | }
51 |
52 | if (this.lineTokensIndex.hasOwnProperty(line)) {
53 | let tokenNodeList = this.lineTokensIndex[line];
54 | if (tokenNodeList.length < 1) {
55 | return undefined;
56 | }
57 |
58 | let currentNode = undefined;
59 | for (let i = 0; i < tokenNodeList.length; i++) {
60 | let n = tokenNodeList[i];
61 | let token = n.getSymbol();
62 | if (token) {
63 | if (token.column <= column && (token.column + token.text.length - 1) >= column) {
64 | currentNode = n;
65 | break;
66 | }
67 | }
68 | }
69 | if (!currentNode) {
70 | // 如果没有找到,找到左侧 token 集合中,最右侧的(距离 column 最近)
71 | for (let i = tokenNodeList.length - 1; i >= 0; i--) {
72 | const n = tokenNodeList[i];
73 | const token = n.getSymbol();
74 | if (token) {
75 | if(token.stop <= column) {
76 | return n;
77 | }
78 | }
79 | }
80 | }
81 |
82 | return currentNode;
83 | }
84 |
85 | return undefined;
86 | }
87 |
88 | tryCollect(node) {
89 | let token = node.getSymbol();
90 | if (token && token.type !== ReportFormulaParser.EOF) {
91 | let nodes = [];
92 | if (this.lineTokensIndex.hasOwnProperty(token.line)) {
93 | nodes = this.lineTokensIndex[token.line];
94 | } else {
95 | this.lineTokensIndex[token.line] = nodes;
96 | }
97 |
98 | nodes.push(node);
99 | this.tokenSink.push(token);
100 | }
101 | }
102 | visitTerminal(node) {
103 | this.tryCollect(node);
104 | }
105 |
106 | visitErrorNode(node) {
107 | this.tryCollect(node);
108 | }
109 |
110 |
111 | }
112 |
113 | exports.EditorTokensVisitor = EditorTokensVisitor;
--------------------------------------------------------------------------------
/src/platform/formula/core/SingleFormulaContext.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 单个单元格公式的上下文信息,包括活动表格、活动单元格信息等。
3 | */
4 |
5 | class SingleFormulaContext {
6 | constructor() {
7 | this.activeSheetName = undefined;
8 | }
9 |
10 | setActiveSheetName(activeSheetName) {
11 | this.activeSheetName = activeSheetName;
12 | }
13 | }
14 |
15 | exports.SingleFormulaContext = SingleFormulaContext;
--------------------------------------------------------------------------------
/src/platform/formula/core/SingleFormulaCore.js:
--------------------------------------------------------------------------------
1 | const antlr4 = require('antlr4');
2 |
3 | const FormulaParser = require('../runtime/ReportFormulaParser').ReportFormulaParser;
4 | const FormulaLexer = require('../runtime/ReportFormulaLexer').ReportFormulaLexer;
5 |
6 | const EditorTokensVisitor = require('./EditorTokensVisitor').EditorTokensVisitor;
7 |
8 | const ParserErrorListener = require('../error/ParserErrorListener');
9 | const LexerErrorListener = require('../error/LexerErrorListener');
10 |
11 | const EditorErrorHandler = require('../../contrib/errorHandler/EditorErrorHandler');
12 |
13 | class NotImplementedError extends Error {
14 | constructor(){
15 | super('未实现的功能');
16 | }
17 |
18 | }
19 | /**
20 | * 公式引擎核心,解析公式并计算
21 | */
22 |
23 | function SingleFormulaCore(errorHandler, cellValueProvider) {
24 | this.setErrorHandler(errorHandler);
25 | this.setCellValueProvider(cellValueProvider);
26 | }
27 |
28 | SingleFormulaCore.FnTokenType = 'fnIdentifier';
29 |
30 | SingleFormulaCore.prototype.setErrorHandler = function setErrorHandler(errorHandler) {
31 | this.sharedErrorHandler = errorHandler;
32 |
33 | return this;
34 | }
35 |
36 | SingleFormulaCore.prototype.removeErrorHandler = function removeErrorHandler() {
37 | this.sharedErrorHandler = null;
38 |
39 | return this;
40 | }
41 |
42 | SingleFormulaCore.prototype.getErrors = function getErrors() {
43 | return this.sharedErrorHandler.getErrors();
44 | }
45 |
46 | SingleFormulaCore.prototype.setCellValueProvider = function setCellValueProvider(valueProvider) {
47 | if (!valueProvider) {
48 | return this;
49 | }
50 |
51 | this.cellValueProvider = valueProvider;
52 |
53 | return this;
54 | }
55 |
56 | SingleFormulaCore.prototype.getCellValueProvider = function getCellValueProvider() {
57 | return this.cellValueProvider;
58 | }
59 |
60 | SingleFormulaCore.prototype.removeCellValueProvider = function removeCellValueProvider() {
61 | this.cellValueProvider = null;
62 | return this;
63 | }
64 |
65 | SingleFormulaCore.prototype.createErrorListener = function createErrorListener(errorHandler) {
66 | const defaultLexerErrListener = new LexerErrorListener();
67 | const defaultParserErrListener = new ParserErrorListener();
68 |
69 | defaultLexerErrListener.setErrorHandler(errorHandler);
70 | defaultParserErrListener.setErrorHandler(errorHandler);
71 |
72 | return {
73 | lexerErrorListener: defaultLexerErrListener,
74 | parserErrorListener: defaultParserErrListener
75 | }
76 | }
77 |
78 | /**
79 | * 解析单元格地址的值
80 | */
81 | SingleFormulaCore.prototype.evaluateCellAddress = function (cellAddress) {
82 | // 第一步,解析单元格地址为地址对象
83 |
84 | // 第二部,根据地址对象,获取单元格的值
85 | return 0;
86 | }
87 |
88 | /**
89 | * @param {caretColumn} - caretColumn = 0..n-1
90 | */
91 | SingleFormulaCore.prototype.findTokenOnLeftOfPosition = function (tokenTree, line, caretColumn) {
92 | let tokenVisitor = new EditorTokensVisitor();
93 | tokenTree.accept(tokenVisitor);
94 |
95 | // caret column 表示光标右侧的列,需要减 1。
96 | return tokenVisitor.findTerminalNodeAtCaret(line, caretColumn - 1);
97 | }
98 |
99 | SingleFormulaCore.prototype.findArgumentRuleOnLeftOfPosition = function(tokenTree, line, caretColumn) {
100 | let leftNode = this.findTokenOnLeftOfPosition(tokenTree, line, caretColumn);
101 | let ret = leftNode;
102 | if(!ret){
103 | return undefined;
104 | }
105 |
106 | while(ret.parentCtx){
107 | if(ret.getText() === ')') {
108 | ret = ret.parentCtx;
109 | continue;
110 | }
111 | if(ret.parentCtx instanceof FormulaParser.ArgumentsContext) {
112 | break;
113 | }
114 | ret = ret.parentCtx;
115 | }
116 |
117 | return ret.parentCtx ? ret : leftNode;
118 | }
119 |
120 | function getFunctionName(argumentsRule) {
121 | let argumentsExpr = argumentsRule.parentCtx;
122 | let fnName = argumentsExpr.singleExpression().getText();
123 | return fnName;
124 | }
125 | function getArgumentList(argumentsRule) {
126 | let argumentsList = argumentsRule.children;
127 | let endIndex = argumentsList.length;
128 | if(argumentsList[argumentsList.length-1].getText() === ')') {
129 | endIndex--;
130 | }
131 | if(endIndex <= 1) {
132 | return [];
133 | }
134 | // 第一个元素是“开括号”
135 | return argumentsList.slice(1, endIndex);
136 | }
137 |
138 | // 查找 startingNode 左侧的逗号数量,根据非逗号数量,判断当前的活动参数
139 | function getArgumentIndex(argumentsList, node) {
140 | var argumentIndex = 0;
141 | for (var _i = 0, _a = argumentsList; _i < _a.length; _i++) {
142 | var child = _a[_i];
143 | if (child === node) {
144 | break;
145 | }
146 | if (child.getText() !== ',' /* CommaToken */) {
147 | argumentIndex++;
148 | }
149 | }
150 | return argumentIndex;
151 | }
152 |
153 | // 根据解析树,一直向上查找,直到找到一个参数节点。
154 | function getArgumentsRule(startingNode) {
155 | let n = startingNode;
156 | do{
157 | if(n instanceof FormulaParser.ArgumentsContext) {
158 | return n;
159 | }
160 | n = n.parentCtx;
161 | }while(n);
162 |
163 | return undefined;
164 | }
165 |
166 | SingleFormulaCore.prototype.getContainingArgumentInfo = function (startingNode) {
167 |
168 | let argumentsRuleNode = getArgumentsRule(startingNode);
169 |
170 | if (argumentsRuleNode) {
171 | let fnName = getFunctionName(argumentsRuleNode);
172 | let argumentIndex = getArgumentIndex(getArgumentList(argumentsRuleNode), startingNode);
173 |
174 | return {
175 | fnName: fnName,
176 | argumentIndex: argumentIndex
177 | };
178 | }
179 |
180 | return {
181 | fnName: '', //函数名称
182 | argumentIndex: 0 //参数索引位置 0..n-1
183 | };
184 | }
185 |
186 | /**
187 | * 收集所有的 token,用于支持语法高亮。
188 | * 支持输入为多行文本。
189 | * @param {*} tokenTree 解析树
190 | * @return {{line,startIndex,stopIndex,text,tokenTypeName}[]} tokenList - token 清单
191 | */
192 | SingleFormulaCore.prototype.collectTokens = function (tokenTree) {
193 | if (!tokenTree) {
194 | return [];
195 | }
196 | let tokenVisitor = new EditorTokensVisitor();
197 | tokenTree.accept(tokenVisitor);
198 | let tokenList = tokenVisitor.getTokenList();
199 |
200 | return tokenList.map(function (token) {
201 | return {
202 | line: token.line,
203 | text: token.text,
204 | startIndex: token.column,
205 | stopIndex: token.column + token.text.length - 1,
206 | tokenTypeName: FormulaLexer.prototype.symbolicNames[token.type]
207 | }
208 | });
209 | }
210 |
211 | /**
212 | * 生成的解析树中,允许包括错误。
213 | * @param {String} input 输入字符串
214 | * @return {Object} 解析树(ParseTree)。如果语法有错误,则抛出解析错误(词法错误、语法错误)
215 | */
216 | SingleFormulaCore.prototype.parse = function parse(input) {
217 | const parser = this.buildParser(input);
218 | // 启动公式解析,遇到错误会触发 ErrorListener。
219 | return parser.formulaExpr();
220 | }
221 |
222 | /**
223 | * 构建解析器
224 | */
225 | SingleFormulaCore.prototype.buildParser = function parser(input) {
226 | const errorListenerObj = this.createErrorListener(this.sharedErrorHandler);
227 |
228 | this._innerErrorListeners = errorListenerObj;
229 |
230 | const chars = new antlr4.InputStream(input);
231 | const lexer = new FormulaLexer(chars);
232 | lexer.removeErrorListeners();
233 | lexer.addErrorListener(errorListenerObj.lexerErrorListener);
234 |
235 | const tokens = new antlr4.CommonTokenStream(lexer);
236 | const parser = new FormulaParser(tokens);
237 | // 在创建解析器后,执行解析器前定义错误监听。
238 | parser.removeErrorListeners(); // 移除默认的 ConsoleErrorListener
239 | parser.addErrorListener(errorListenerObj.parserErrorListener);
240 |
241 | return parser;
242 | }
243 |
244 | SingleFormulaCore.prototype.hasErrors = function hasErrors() {
245 | let hasLexerErrors = this._innerErrorListeners.lexerErrorListener.hasErrors();
246 | let hasParseErrors = this._innerErrorListeners.parserErrorListener.hasErrors();
247 | return hasLexerErrors || hasParseErrors;
248 | }
249 |
250 | /**
251 | * 将所有单元格地址字面量提取出来。
252 | * @return 单元格地址,或者单元格范围。
253 | */
254 | SingleFormulaCore.prototype.collectCellAddresses = function(parseTree) {
255 | throw new NotImplementedError();
256 | }
257 |
258 |
259 | SingleFormulaCore.createInstance = function () {
260 | return new SingleFormulaCore(new EditorErrorHandler());
261 | }
262 |
263 | SingleFormulaCore.INSTANCE = SingleFormulaCore.createInstance();
264 |
265 | exports.SingleFormulaCore = SingleFormulaCore;
266 | exports.INSTANCE = SingleFormulaCore.INSTANCE;
--------------------------------------------------------------------------------
/src/platform/formula/core/syntax.js:
--------------------------------------------------------------------------------
1 | exports.Syntax = {
2 | Identifier: 'Identifier',
3 | RefItemIdentifier: 'RefItemIdentifier',
4 | CellAddressIdentifier: 'CellAddressIdentifier',
5 | SheetNameIdentifier: 'SheetNameIdentifier',
6 | A1ReferenceIdentifier: 'A1ReferenceIdentifier',
7 | AbsoluteColumnIdentifier: 'AbsoluteColumnIdentifier',
8 | RelativeColumnIdentifier: 'RelativeColumnIdentifier',
9 | AbsoluteRowIdentifier: 'AbsoluteRowIdentifier',
10 | RelativeRowIdentifier: 'RelativeRowIdentifier',
11 | CellRangeIdentifier: 'CellRangeIdentifier',
12 | CellFloatRangeIdentifier: 'CellFloatRangeIdentifier',
13 | PlainTextIdentifier: 'PlainTextIdentifier',
14 | Literal: 'Literal',
15 | PercentageLiteral: 'PercentageLiteral',
16 | FormulaProgram: 'FormulaProgram',
17 | ExpressionStatement: 'ExpressionStatement',
18 | ArrayExpression: 'ArrayExpression',
19 | ObjectExpression: 'ObjectExpression',
20 | Property: 'Property',
21 | UnaryExpression: 'UnaryExpression',
22 | BinaryExpression: 'BinaryExpression',
23 | AssignmentExpression: 'AssignmentExpression',
24 | LogicalExpression: 'LogicalExpression',
25 | MemberExpression: 'MemberExpression',
26 | ConditionalExpression: 'ConditionalExpression',
27 | CallExpression: 'CallExpression',
28 | ParenthesizedExpression: 'ParenthesizedExpression',
29 | SequenceExpression: 'SequenceExpression',
30 | REF_ERROR: '#REF!'
31 | };
--------------------------------------------------------------------------------
/src/platform/formula/error/BaseErrorListener.js:
--------------------------------------------------------------------------------
1 | const antlr4 = require('antlr4');
2 | const { ParseCancellationException } = require('antlr4/error/Errors');
3 |
4 | class SyntaxErrorItem {
5 | constructor(symbol, line, column, message, e) {
6 | this.offendingSymbol = symbol;
7 | this.line = line;
8 | this.column = column;
9 | this.message = message;
10 | this.exception = e;
11 | }
12 | }
13 | class BaseErrorListener extends antlr4.error.ErrorListener {
14 | constructor(){
15 | super();
16 |
17 | this.errorItems = [];
18 | }
19 |
20 | setErrorHandler(errorHandler) {
21 | this.errorHandler = errorHandler;
22 | }
23 |
24 | removeErrorHandler() {
25 | this.errorHandler = {
26 | handleParseError: function(){/** noop */},
27 | handleEvaluateError: function(){/** noop */}
28 | }
29 | }
30 |
31 | getErrorHandler() {
32 | return this.errorHandler;
33 | }
34 |
35 | recordError(recognizer, symbol, line, column, message, exception) {
36 | this.errorItems.push(new SyntaxErrorItem(symbol, line, column, message, exception));
37 | }
38 | /**
39 | * 检查文法错误
40 | *
41 | * @param {object} recognizer 必要的解析支持代码. 大多时候是错误恢复内容。
42 | * @param {object} symbol 非法符号
43 | * @param {int} line 非法符号的行号
44 | * @param {int} column 非法符号的列号
45 | * @param {string} message 错误信息
46 | * @param {RecognitionException} exception 异常信息
47 | */
48 | syntaxError(recognizer, symbol, line, column, message, exception) {
49 | this.recordError(recognizer, symbol, line, column, message, exception);
50 |
51 | var tokens = recognizer.getInputStream();
52 | var rawText = tokens.tokenSource.inputStream.toString();
53 |
54 | var errHandler = this.getErrorHandler();
55 | if(errHandler && errHandler.handleParseError instanceof Function) {
56 | errHandler.handleParseError(rawText, symbol, line, column, message);
57 | }
58 | }
59 |
60 | hasErrors() {
61 | return this.errorItems.length > 0;
62 | }
63 |
64 | clearErrors() {
65 | this.errorItems = [];
66 | }
67 | }
68 |
69 | module.exports = BaseErrorListener;
--------------------------------------------------------------------------------
/src/platform/formula/error/FormulaExceptions.js:
--------------------------------------------------------------------------------
1 | function FormulaException() {
2 | Error.call(this);
3 | return this;
4 | }
5 |
6 | FormulaException.prototype = Object.create(Error.prototype);
7 | FormulaException.prototype.constructor = FormulaException;
8 |
9 | /**
10 | * 语法解析错误
11 | */
12 | function ParseException(input, line, column, message) {
13 | FormulaException.call(this);
14 |
15 | this.input = input;
16 | this.line = line;
17 | this.column = column;
18 | this.message = message;
19 |
20 | return this;
21 | }
22 |
23 | ParseException.prototype = Object.create(FormulaException.prototype);
24 | ParseException.prototype.constructor = ParseException;
25 |
26 | function CalculationException() {
27 | FormulaException.call(this);
28 | return this;
29 | }
30 |
31 | CalculationException.prototype = Object.create(FormulaException.prototype);
32 | CalculationException.prototype.constructor = CalculationException;
33 |
34 |
35 |
36 |
37 | exports.FormulaException = FormulaException;
38 | exports.ParseException = ParseException;
39 | exports.CalculationException = CalculationException;
--------------------------------------------------------------------------------
/src/platform/formula/error/LexerErrorListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 识别文法错误。
3 | */
4 |
5 |
6 | const BaseErrorListener = require('./BaseErrorListener');
7 |
8 | class LexerErrorListener extends BaseErrorListener {
9 |
10 |
11 | /**
12 | * 检查文法错误
13 | *
14 | * @param {object} recognizer 必要的解析支持代码. 大多时候是错误恢复内容。
15 | * @param {object} symbol 非法符号
16 | * @param {int} line 非法符号的行号
17 | * @param {int} column 非法符号的列号
18 | * @param {string} message 错误信息
19 | * @param {RecognitionException} exception 异常信息
20 | */
21 | syntaxError(recognizer, symbol, line, column, message, exception) {
22 | super.recordError(recognizer, symbol, line, column, message, exception);
23 |
24 | var rawText = recognizer.inputStream.toString();
25 |
26 | var errHandler = this.getErrorHandler();
27 | if(errHandler && errHandler.handle instanceof Function) {
28 | errHandler.handle(rawText, symbol, line, column, '无法识别的符号');
29 | }
30 | }
31 | }
32 |
33 | module.exports = LexerErrorListener;
--------------------------------------------------------------------------------
/src/platform/formula/error/ParserErrorListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | const BaseErrorListener = require('./BaseErrorListener');
5 |
6 | /**
7 | * 解析器错误监听器
8 | *
9 | * @returns {object}
10 | */
11 | class ParserErrorListener extends BaseErrorListener {
12 | /**
13 | * 检查语法错误
14 | *
15 | * @param {object} recognizer 必要的解析支持代码. 大多时候是错误恢复内容。
16 | * @param {object} symbol 非法符号
17 | * @param {int} line 非法符号的行号
18 | * @param {int} column 非法符号的列号
19 | * @param {string} message 错误信息
20 | * @param {RecognitionException} exception 异常信息
21 | */
22 | syntaxError(recognizer, symbol, line, column, message, exception) {
23 | super.syntaxError(recognizer, symbol, line, column, message, exception);
24 | }
25 | }
26 |
27 | module.exports = ParserErrorListener;
--------------------------------------------------------------------------------
/src/platform/formula/generation/AutoFillTransformer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 用于表格自动填充公式时,生成填充后的公式。
3 | */
4 | const types = require('base/common/types');
5 | const {buildRelativeCellRefCarrier} = require('platform/formula/cellAddressParts/common/CellAddressParts');
6 | const SingleFormulaContext = require('platform/formula/core/SingleFormulaContext').SingleFormulaContext;
7 |
8 |
9 | class AutoFillTransformer {
10 | constructor(ast, activeSheetName) {
11 | this.formulaAST = ast;
12 |
13 | const that = this;
14 | // 收集受影响的单元格
15 | // Array[ CellAddressIdentifier|CellRangeIdentifier]
16 | let dependenciesList = this.formulaAST.findAllCellRefNodes();
17 | const cellRefCarriers = [];
18 | dependenciesList.forEach(function (dep) {
19 | cellRefCarriers.push(that._buildCellAddress(activeSheetName, dep));
20 | });
21 |
22 | this._cellRefs = cellRefCarriers;
23 | }
24 |
25 | // 将单元格地址转化为标准对象
26 | _buildCellAddress(sheetName, cellAddressOrCellRange) {
27 | let cellRef = buildRelativeCellRefCarrier(cellAddressOrCellRange);
28 | let context = new SingleFormulaContext();
29 | context.activeSheetName = sheetName;
30 | cellRef.setWorkingContext(context);
31 | return cellRef;
32 | }
33 |
34 | _doFormulaTranslate(translateDirectionFn) {
35 | if(types.isArray(this._cellRefs)){
36 | this._cellRefs.forEach(function(cellRefCarry){
37 | translateDirectionFn(cellRefCarry);
38 | });
39 | }
40 | return this.formulaAST.toString();
41 | }
42 |
43 | getFormulaAfterFillingDown(step){
44 | return this._doFormulaTranslate(function(cellRefCarry){
45 | cellRefCarry.translateDown(step);
46 | });
47 | }
48 |
49 | getFormulaAfterFillingUp(step){
50 | return this._doFormulaTranslate(function(cellRefCarry){
51 | cellRefCarry.translateUp(step);
52 | });
53 | }
54 |
55 | getFormulaAfterFillingLeft(step){
56 | return this._doFormulaTranslate(function(cellRefCarry){
57 | cellRefCarry.translateLeft(step);
58 | });
59 | }
60 |
61 | getFormulaAfterFillingRight(step){
62 | return this._doFormulaTranslate(function(cellRefCarry){
63 | cellRefCarry.translateRight(step);
64 | });
65 | }
66 | }
67 |
68 | exports.AutoFillTransformer = AutoFillTransformer;
--------------------------------------------------------------------------------
/src/platform/formula/grammar/.antlr/CellAddress.interp:
--------------------------------------------------------------------------------
1 | token literal names:
2 | null
3 | null
4 | null
5 | null
6 | ':'
7 | '$'
8 | '!'
9 | '->'
10 |
11 | token symbolic names:
12 | null
13 | WorkSheetPrefix
14 | CellColumnAddress
15 | CellRowAddress
16 | Colon
17 | Dollar
18 | ExclamationMark
19 | ArrowRight
20 |
21 | rule names:
22 | cellReference
23 | a1Reference
24 | a1Column
25 | a1Row
26 | a1RelativeColumn
27 | a1AbsoluteColumn
28 | a1RelativeRow
29 | a1AbsoluteRow
30 |
31 |
32 | atn:
33 | [3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 9, 64, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 3, 2, 5, 2, 20, 10, 2, 3, 2, 3, 2, 3, 2, 3, 2, 5, 2, 26, 10, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 5, 2, 34, 10, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 5, 2, 41, 10, 2, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 5, 4, 48, 10, 4, 3, 5, 3, 5, 5, 5, 52, 10, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 9, 2, 2, 10, 2, 4, 6, 8, 10, 12, 14, 16, 2, 2, 2, 62, 2, 40, 3, 2, 2, 2, 4, 42, 3, 2, 2, 2, 6, 47, 3, 2, 2, 2, 8, 51, 3, 2, 2, 2, 10, 53, 3, 2, 2, 2, 12, 55, 3, 2, 2, 2, 14, 58, 3, 2, 2, 2, 16, 60, 3, 2, 2, 2, 18, 20, 7, 3, 2, 2, 19, 18, 3, 2, 2, 2, 19, 20, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 22, 5, 4, 3, 2, 22, 23, 7, 2, 2, 3, 23, 41, 3, 2, 2, 2, 24, 26, 7, 3, 2, 2, 25, 24, 3, 2, 2, 2, 25, 26, 3, 2, 2, 2, 26, 27, 3, 2, 2, 2, 27, 28, 5, 4, 3, 2, 28, 29, 7, 6, 2, 2, 29, 30, 5, 4, 3, 2, 30, 31, 7, 2, 2, 3, 31, 41, 3, 2, 2, 2, 32, 34, 7, 3, 2, 2, 33, 32, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 3, 2, 2, 2, 35, 36, 5, 4, 3, 2, 36, 37, 7, 9, 2, 2, 37, 38, 5, 4, 3, 2, 38, 39, 7, 2, 2, 3, 39, 41, 3, 2, 2, 2, 40, 19, 3, 2, 2, 2, 40, 25, 3, 2, 2, 2, 40, 33, 3, 2, 2, 2, 41, 3, 3, 2, 2, 2, 42, 43, 5, 6, 4, 2, 43, 44, 5, 8, 5, 2, 44, 5, 3, 2, 2, 2, 45, 48, 5, 10, 6, 2, 46, 48, 5, 12, 7, 2, 47, 45, 3, 2, 2, 2, 47, 46, 3, 2, 2, 2, 48, 7, 3, 2, 2, 2, 49, 52, 5, 14, 8, 2, 50, 52, 5, 16, 9, 2, 51, 49, 3, 2, 2, 2, 51, 50, 3, 2, 2, 2, 52, 9, 3, 2, 2, 2, 53, 54, 7, 4, 2, 2, 54, 11, 3, 2, 2, 2, 55, 56, 7, 7, 2, 2, 56, 57, 7, 4, 2, 2, 57, 13, 3, 2, 2, 2, 58, 59, 7, 5, 2, 2, 59, 15, 3, 2, 2, 2, 60, 61, 7, 7, 2, 2, 61, 62, 7, 5, 2, 2, 62, 17, 3, 2, 2, 2, 8, 19, 25, 33, 40, 47, 51]
--------------------------------------------------------------------------------
/src/platform/formula/grammar/.antlr/CellAddressLexer.interp:
--------------------------------------------------------------------------------
1 | token literal names:
2 | null
3 | null
4 | null
5 | null
6 | ':'
7 | '$'
8 | '!'
9 | '->'
10 |
11 | token symbolic names:
12 | null
13 | WorkSheetPrefix
14 | CellColumnAddress
15 | CellRowAddress
16 | Colon
17 | Dollar
18 | ExclamationMark
19 | ArrowRight
20 |
21 | rule names:
22 | WorkSheetPrefix
23 | CellColumnAddress
24 | CellRowAddress
25 | Colon
26 | Dollar
27 | ExclamationMark
28 | ArrowRight
29 | SheetName
30 | StringLiteral
31 | DoubleStringCharacter
32 | SingleStringCharacter
33 | SheetNameCharacter
34 | EscapeSequence
35 | CharacterEscapeSequence
36 | LineContinuation
37 | HexEscapeSequence
38 | UnicodeEscapeSequence
39 | ExtendedUnicodeEscapeSequence
40 | SingleEscapeCharacter
41 | NonEscapeCharacter
42 | HexDigit
43 |
44 | channel names:
45 | DEFAULT_TOKEN_CHANNEL
46 | HIDDEN
47 |
48 | mode names:
49 | DEFAULT_MODE
50 |
51 | atn:
52 | [3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 9, 166, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 7, 3, 51, 10, 3, 12, 3, 14, 3, 54, 11, 3, 3, 4, 3, 4, 7, 4, 58, 10, 4, 12, 4, 14, 4, 61, 11, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 9, 6, 9, 73, 10, 9, 13, 9, 14, 9, 74, 3, 9, 5, 9, 78, 10, 9, 3, 10, 3, 10, 7, 10, 82, 10, 10, 12, 10, 14, 10, 85, 11, 10, 3, 10, 3, 10, 3, 10, 7, 10, 90, 10, 10, 12, 10, 14, 10, 93, 11, 10, 3, 10, 5, 10, 96, 10, 10, 3, 11, 3, 11, 3, 11, 3, 11, 5, 11, 102, 10, 11, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 108, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 5, 13, 114, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 121, 10, 14, 3, 15, 3, 15, 5, 15, 125, 10, 15, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 6, 18, 144, 10, 18, 13, 18, 14, 18, 145, 3, 18, 3, 18, 5, 18, 150, 10, 18, 3, 19, 3, 19, 3, 19, 6, 19, 155, 10, 19, 13, 19, 14, 19, 156, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 2, 2, 23, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 2, 19, 2, 21, 2, 23, 2, 25, 2, 27, 2, 29, 2, 31, 2, 33, 2, 35, 2, 37, 2, 39, 2, 41, 2, 43, 2, 3, 2, 12, 4, 2, 67, 92, 99, 124, 3, 2, 51, 59, 3, 2, 50, 59, 6, 2, 12, 12, 15, 15, 36, 36, 94, 94, 6, 2, 12, 12, 15, 15, 41, 41, 94, 94, 10, 2, 12, 12, 15, 15, 35, 36, 41, 41, 44, 45, 47, 47, 49, 49, 93, 95, 5, 2, 12, 12, 15, 15, 8234, 8235, 11, 2, 36, 36, 41, 41, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 120, 120, 14, 2, 12, 12, 15, 15, 36, 36, 41, 41, 50, 59, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 120, 122, 122, 6, 2, 50, 59, 67, 72, 97, 97, 99, 104, 2, 172, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 3, 45, 3, 2, 2, 2, 5, 48, 3, 2, 2, 2, 7, 55, 3, 2, 2, 2, 9, 62, 3, 2, 2, 2, 11, 64, 3, 2, 2, 2, 13, 66, 3, 2, 2, 2, 15, 68, 3, 2, 2, 2, 17, 77, 3, 2, 2, 2, 19, 95, 3, 2, 2, 2, 21, 101, 3, 2, 2, 2, 23, 107, 3, 2, 2, 2, 25, 113, 3, 2, 2, 2, 27, 120, 3, 2, 2, 2, 29, 124, 3, 2, 2, 2, 31, 126, 3, 2, 2, 2, 33, 129, 3, 2, 2, 2, 35, 149, 3, 2, 2, 2, 37, 151, 3, 2, 2, 2, 39, 160, 3, 2, 2, 2, 41, 162, 3, 2, 2, 2, 43, 164, 3, 2, 2, 2, 45, 46, 5, 17, 9, 2, 46, 47, 7, 35, 2, 2, 47, 4, 3, 2, 2, 2, 48, 52, 9, 2, 2, 2, 49, 51, 9, 2, 2, 2, 50, 49, 3, 2, 2, 2, 51, 54, 3, 2, 2, 2, 52, 50, 3, 2, 2, 2, 52, 53, 3, 2, 2, 2, 53, 6, 3, 2, 2, 2, 54, 52, 3, 2, 2, 2, 55, 59, 9, 3, 2, 2, 56, 58, 9, 4, 2, 2, 57, 56, 3, 2, 2, 2, 58, 61, 3, 2, 2, 2, 59, 57, 3, 2, 2, 2, 59, 60, 3, 2, 2, 2, 60, 8, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 62, 63, 7, 60, 2, 2, 63, 10, 3, 2, 2, 2, 64, 65, 7, 38, 2, 2, 65, 12, 3, 2, 2, 2, 66, 67, 7, 35, 2, 2, 67, 14, 3, 2, 2, 2, 68, 69, 7, 47, 2, 2, 69, 70, 7, 64, 2, 2, 70, 16, 3, 2, 2, 2, 71, 73, 5, 25, 13, 2, 72, 71, 3, 2, 2, 2, 73, 74, 3, 2, 2, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 78, 3, 2, 2, 2, 76, 78, 5, 19, 10, 2, 77, 72, 3, 2, 2, 2, 77, 76, 3, 2, 2, 2, 78, 18, 3, 2, 2, 2, 79, 83, 7, 36, 2, 2, 80, 82, 5, 21, 11, 2, 81, 80, 3, 2, 2, 2, 82, 85, 3, 2, 2, 2, 83, 81, 3, 2, 2, 2, 83, 84, 3, 2, 2, 2, 84, 86, 3, 2, 2, 2, 85, 83, 3, 2, 2, 2, 86, 96, 7, 36, 2, 2, 87, 91, 7, 41, 2, 2, 88, 90, 5, 23, 12, 2, 89, 88, 3, 2, 2, 2, 90, 93, 3, 2, 2, 2, 91, 89, 3, 2, 2, 2, 91, 92, 3, 2, 2, 2, 92, 94, 3, 2, 2, 2, 93, 91, 3, 2, 2, 2, 94, 96, 7, 41, 2, 2, 95, 79, 3, 2, 2, 2, 95, 87, 3, 2, 2, 2, 96, 20, 3, 2, 2, 2, 97, 102, 10, 5, 2, 2, 98, 99, 7, 94, 2, 2, 99, 102, 5, 27, 14, 2, 100, 102, 5, 31, 16, 2, 101, 97, 3, 2, 2, 2, 101, 98, 3, 2, 2, 2, 101, 100, 3, 2, 2, 2, 102, 22, 3, 2, 2, 2, 103, 108, 10, 6, 2, 2, 104, 105, 7, 94, 2, 2, 105, 108, 5, 27, 14, 2, 106, 108, 5, 31, 16, 2, 107, 103, 3, 2, 2, 2, 107, 104, 3, 2, 2, 2, 107, 106, 3, 2, 2, 2, 108, 24, 3, 2, 2, 2, 109, 114, 10, 7, 2, 2, 110, 111, 7, 94, 2, 2, 111, 114, 5, 27, 14, 2, 112, 114, 5, 31, 16, 2, 113, 109, 3, 2, 2, 2, 113, 110, 3, 2, 2, 2, 113, 112, 3, 2, 2, 2, 114, 26, 3, 2, 2, 2, 115, 121, 5, 29, 15, 2, 116, 121, 7, 50, 2, 2, 117, 121, 5, 33, 17, 2, 118, 121, 5, 35, 18, 2, 119, 121, 5, 37, 19, 2, 120, 115, 3, 2, 2, 2, 120, 116, 3, 2, 2, 2, 120, 117, 3, 2, 2, 2, 120, 118, 3, 2, 2, 2, 120, 119, 3, 2, 2, 2, 121, 28, 3, 2, 2, 2, 122, 125, 5, 39, 20, 2, 123, 125, 5, 41, 21, 2, 124, 122, 3, 2, 2, 2, 124, 123, 3, 2, 2, 2, 125, 30, 3, 2, 2, 2, 126, 127, 7, 94, 2, 2, 127, 128, 9, 8, 2, 2, 128, 32, 3, 2, 2, 2, 129, 130, 7, 122, 2, 2, 130, 131, 5, 43, 22, 2, 131, 132, 5, 43, 22, 2, 132, 34, 3, 2, 2, 2, 133, 134, 7, 119, 2, 2, 134, 135, 5, 43, 22, 2, 135, 136, 5, 43, 22, 2, 136, 137, 5, 43, 22, 2, 137, 138, 5, 43, 22, 2, 138, 150, 3, 2, 2, 2, 139, 140, 7, 119, 2, 2, 140, 141, 7, 125, 2, 2, 141, 143, 5, 43, 22, 2, 142, 144, 5, 43, 22, 2, 143, 142, 3, 2, 2, 2, 144, 145, 3, 2, 2, 2, 145, 143, 3, 2, 2, 2, 145, 146, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 148, 7, 127, 2, 2, 148, 150, 3, 2, 2, 2, 149, 133, 3, 2, 2, 2, 149, 139, 3, 2, 2, 2, 150, 36, 3, 2, 2, 2, 151, 152, 7, 119, 2, 2, 152, 154, 7, 125, 2, 2, 153, 155, 5, 43, 22, 2, 154, 153, 3, 2, 2, 2, 155, 156, 3, 2, 2, 2, 156, 154, 3, 2, 2, 2, 156, 157, 3, 2, 2, 2, 157, 158, 3, 2, 2, 2, 158, 159, 7, 127, 2, 2, 159, 38, 3, 2, 2, 2, 160, 161, 9, 9, 2, 2, 161, 40, 3, 2, 2, 2, 162, 163, 10, 10, 2, 2, 163, 42, 3, 2, 2, 2, 164, 165, 9, 11, 2, 2, 165, 44, 3, 2, 2, 2, 18, 2, 52, 59, 74, 77, 83, 91, 95, 101, 107, 113, 120, 124, 145, 149, 156, 2]
--------------------------------------------------------------------------------
/src/platform/formula/grammar/.antlr/ReportFormulaParser.interp:
--------------------------------------------------------------------------------
1 | token literal names:
2 | null
3 | null
4 | null
5 | '('
6 | ')'
7 | '['
8 | ']'
9 | '{'
10 | '}'
11 | ';'
12 | ','
13 | '='
14 | '?'
15 | ':'
16 | '$'
17 | '@'
18 | '.'
19 | '++'
20 | '--'
21 | '+'
22 | '-'
23 | '!'
24 | '*'
25 | '/'
26 | '%'
27 | '**'
28 | '#'
29 | '<'
30 | '>'
31 | '<='
32 | '>='
33 | '=='
34 | '!='
35 | '&&'
36 | '||'
37 | '->'
38 | 'if'
39 | null
40 | 'null'
41 | null
42 | null
43 | null
44 | null
45 | null
46 | null
47 | null
48 | null
49 | null
50 | null
51 | null
52 | null
53 | null
54 | null
55 | null
56 | null
57 | null
58 | null
59 |
60 | token symbolic names:
61 | null
62 | MultiLineComment
63 | SingleLineComment
64 | OpenParen
65 | CloseParen
66 | OpenBracket
67 | CloseBracket
68 | OpenBrace
69 | CloseBrace
70 | SemiColon
71 | Comma
72 | Assign
73 | QuestionMark
74 | Colon
75 | Dollar
76 | At
77 | Dot
78 | PlusPlus
79 | MinusMinus
80 | Plus
81 | Minus
82 | Not
83 | Multiply
84 | Divide
85 | Modulus
86 | Power
87 | Hashtag
88 | LessThan
89 | MoreThan
90 | LessThanEquals
91 | GreaterThanEquals
92 | Equals_
93 | NotEquals
94 | And
95 | Or
96 | ArrowRight
97 | If
98 | BooleanLiteral
99 | NullLiteral
100 | CellRangeLiteral
101 | CellFloatRangeLiteral
102 | CellAddressLiteral
103 | BasicNumberLiteral
104 | DecimalLiteral
105 | HexIntegerLiteral
106 | OctalIntegerLiteral
107 | OctalIntegerLiteral2
108 | BinaryIntegerLiteral
109 | BigHexIntegerLiteral
110 | BigOctalIntegerLiteral
111 | BigBinaryIntegerLiteral
112 | BigDecimalIntegerLiteral
113 | Identifier
114 | StringLiteral
115 | WhiteSpaces
116 | LineTerminator
117 | UnexpectedCharacter
118 |
119 | rule names:
120 | formulaExpr
121 | expressionStatement
122 | expressionSequence
123 | singleExpression
124 | arguments
125 | argument
126 | objectLiteral
127 | arrayLiteral
128 | elementList
129 | arrayElement
130 | propertyAssignment
131 | propertyName
132 | identifierName
133 | identifier
134 | refItemCode
135 | literal
136 | numericLiteral
137 | percentageLiteral
138 | reservedWord
139 | keyword
140 | eos
141 |
142 |
143 | atn:
144 | [3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 58, 245, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 3, 2, 5, 2, 46, 10, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 68, 10, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 107, 10, 5, 3, 5, 3, 5, 5, 5, 111, 10, 5, 3, 5, 3, 5, 3, 5, 7, 5, 116, 10, 5, 12, 5, 14, 5, 119, 11, 5, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 125, 10, 6, 12, 6, 14, 6, 128, 11, 6, 3, 6, 5, 6, 131, 10, 6, 5, 6, 133, 10, 6, 3, 6, 3, 6, 3, 7, 3, 7, 5, 7, 139, 10, 7, 3, 8, 3, 8, 3, 8, 3, 8, 7, 8, 145, 10, 8, 12, 8, 14, 8, 148, 11, 8, 5, 8, 150, 10, 8, 3, 8, 5, 8, 153, 10, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 9, 3, 10, 7, 10, 162, 10, 10, 12, 10, 14, 10, 165, 11, 10, 3, 10, 5, 10, 168, 10, 10, 3, 10, 6, 10, 171, 10, 10, 13, 10, 14, 10, 172, 3, 10, 7, 10, 176, 10, 10, 12, 10, 14, 10, 179, 11, 10, 3, 10, 7, 10, 182, 10, 10, 12, 10, 14, 10, 185, 11, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 199, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 5, 13, 208, 10, 13, 3, 14, 3, 14, 5, 14, 212, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 219, 10, 15, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 5, 17, 228, 10, 17, 3, 18, 3, 18, 5, 18, 232, 10, 18, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 5, 20, 239, 10, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 2, 3, 8, 23, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 2, 7, 3, 2, 24, 26, 3, 2, 21, 22, 3, 2, 29, 32, 3, 2, 33, 34, 3, 3, 11, 11, 2, 270, 2, 45, 3, 2, 2, 2, 4, 50, 3, 2, 2, 2, 6, 52, 3, 2, 2, 2, 8, 67, 3, 2, 2, 2, 10, 120, 3, 2, 2, 2, 12, 138, 3, 2, 2, 2, 14, 140, 3, 2, 2, 2, 16, 156, 3, 2, 2, 2, 18, 163, 3, 2, 2, 2, 20, 186, 3, 2, 2, 2, 22, 198, 3, 2, 2, 2, 24, 207, 3, 2, 2, 2, 26, 211, 3, 2, 2, 2, 28, 218, 3, 2, 2, 2, 30, 220, 3, 2, 2, 2, 32, 227, 3, 2, 2, 2, 34, 231, 3, 2, 2, 2, 36, 233, 3, 2, 2, 2, 38, 238, 3, 2, 2, 2, 40, 240, 3, 2, 2, 2, 42, 242, 3, 2, 2, 2, 44, 46, 7, 13, 2, 2, 45, 44, 3, 2, 2, 2, 45, 46, 3, 2, 2, 2, 46, 47, 3, 2, 2, 2, 47, 48, 5, 4, 3, 2, 48, 49, 7, 2, 2, 3, 49, 3, 3, 2, 2, 2, 50, 51, 5, 6, 4, 2, 51, 5, 3, 2, 2, 2, 52, 53, 5, 8, 5, 2, 53, 7, 3, 2, 2, 2, 54, 55, 8, 5, 1, 2, 55, 56, 7, 21, 2, 2, 56, 68, 5, 8, 5, 18, 57, 58, 7, 22, 2, 2, 58, 68, 5, 8, 5, 17, 59, 68, 5, 28, 15, 2, 60, 68, 5, 32, 17, 2, 61, 68, 5, 16, 9, 2, 62, 68, 5, 14, 8, 2, 63, 64, 7, 5, 2, 2, 64, 65, 5, 6, 4, 2, 65, 66, 7, 6, 2, 2, 66, 68, 3, 2, 2, 2, 67, 54, 3, 2, 2, 2, 67, 57, 3, 2, 2, 2, 67, 59, 3, 2, 2, 2, 67, 60, 3, 2, 2, 2, 67, 61, 3, 2, 2, 2, 67, 62, 3, 2, 2, 2, 67, 63, 3, 2, 2, 2, 68, 117, 3, 2, 2, 2, 69, 70, 12, 16, 2, 2, 70, 71, 7, 27, 2, 2, 71, 116, 5, 8, 5, 16, 72, 73, 12, 15, 2, 2, 73, 74, 9, 2, 2, 2, 74, 116, 5, 8, 5, 16, 75, 76, 12, 14, 2, 2, 76, 77, 9, 3, 2, 2, 77, 116, 5, 8, 5, 15, 78, 79, 12, 13, 2, 2, 79, 80, 9, 4, 2, 2, 80, 116, 5, 8, 5, 14, 81, 82, 12, 12, 2, 2, 82, 83, 9, 5, 2, 2, 83, 116, 5, 8, 5, 13, 84, 85, 12, 11, 2, 2, 85, 86, 7, 35, 2, 2, 86, 116, 5, 8, 5, 12, 87, 88, 12, 10, 2, 2, 88, 89, 7, 36, 2, 2, 89, 116, 5, 8, 5, 11, 90, 91, 12, 9, 2, 2, 91, 92, 7, 14, 2, 2, 92, 93, 5, 8, 5, 2, 93, 94, 7, 15, 2, 2, 94, 95, 5, 8, 5, 10, 95, 116, 3, 2, 2, 2, 96, 97, 12, 8, 2, 2, 97, 98, 7, 13, 2, 2, 98, 116, 5, 8, 5, 8, 99, 100, 12, 21, 2, 2, 100, 101, 7, 7, 2, 2, 101, 102, 5, 6, 4, 2, 102, 103, 7, 8, 2, 2, 103, 116, 3, 2, 2, 2, 104, 106, 12, 20, 2, 2, 105, 107, 7, 14, 2, 2, 106, 105, 3, 2, 2, 2, 106, 107, 3, 2, 2, 2, 107, 108, 3, 2, 2, 2, 108, 110, 7, 18, 2, 2, 109, 111, 7, 28, 2, 2, 110, 109, 3, 2, 2, 2, 110, 111, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 116, 5, 26, 14, 2, 113, 114, 12, 19, 2, 2, 114, 116, 5, 10, 6, 2, 115, 69, 3, 2, 2, 2, 115, 72, 3, 2, 2, 2, 115, 75, 3, 2, 2, 2, 115, 78, 3, 2, 2, 2, 115, 81, 3, 2, 2, 2, 115, 84, 3, 2, 2, 2, 115, 87, 3, 2, 2, 2, 115, 90, 3, 2, 2, 2, 115, 96, 3, 2, 2, 2, 115, 99, 3, 2, 2, 2, 115, 104, 3, 2, 2, 2, 115, 113, 3, 2, 2, 2, 116, 119, 3, 2, 2, 2, 117, 115, 3, 2, 2, 2, 117, 118, 3, 2, 2, 2, 118, 9, 3, 2, 2, 2, 119, 117, 3, 2, 2, 2, 120, 132, 7, 5, 2, 2, 121, 126, 5, 12, 7, 2, 122, 123, 7, 12, 2, 2, 123, 125, 5, 12, 7, 2, 124, 122, 3, 2, 2, 2, 125, 128, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 130, 3, 2, 2, 2, 128, 126, 3, 2, 2, 2, 129, 131, 7, 12, 2, 2, 130, 129, 3, 2, 2, 2, 130, 131, 3, 2, 2, 2, 131, 133, 3, 2, 2, 2, 132, 121, 3, 2, 2, 2, 132, 133, 3, 2, 2, 2, 133, 134, 3, 2, 2, 2, 134, 135, 7, 6, 2, 2, 135, 11, 3, 2, 2, 2, 136, 139, 5, 8, 5, 2, 137, 139, 5, 28, 15, 2, 138, 136, 3, 2, 2, 2, 138, 137, 3, 2, 2, 2, 139, 13, 3, 2, 2, 2, 140, 149, 7, 9, 2, 2, 141, 146, 5, 22, 12, 2, 142, 143, 7, 12, 2, 2, 143, 145, 5, 22, 12, 2, 144, 142, 3, 2, 2, 2, 145, 148, 3, 2, 2, 2, 146, 144, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 150, 3, 2, 2, 2, 148, 146, 3, 2, 2, 2, 149, 141, 3, 2, 2, 2, 149, 150, 3, 2, 2, 2, 150, 152, 3, 2, 2, 2, 151, 153, 7, 12, 2, 2, 152, 151, 3, 2, 2, 2, 152, 153, 3, 2, 2, 2, 153, 154, 3, 2, 2, 2, 154, 155, 7, 10, 2, 2, 155, 15, 3, 2, 2, 2, 156, 157, 7, 7, 2, 2, 157, 158, 5, 18, 10, 2, 158, 159, 7, 8, 2, 2, 159, 17, 3, 2, 2, 2, 160, 162, 7, 12, 2, 2, 161, 160, 3, 2, 2, 2, 162, 165, 3, 2, 2, 2, 163, 161, 3, 2, 2, 2, 163, 164, 3, 2, 2, 2, 164, 167, 3, 2, 2, 2, 165, 163, 3, 2, 2, 2, 166, 168, 5, 20, 11, 2, 167, 166, 3, 2, 2, 2, 167, 168, 3, 2, 2, 2, 168, 177, 3, 2, 2, 2, 169, 171, 7, 12, 2, 2, 170, 169, 3, 2, 2, 2, 171, 172, 3, 2, 2, 2, 172, 170, 3, 2, 2, 2, 172, 173, 3, 2, 2, 2, 173, 174, 3, 2, 2, 2, 174, 176, 5, 20, 11, 2, 175, 170, 3, 2, 2, 2, 176, 179, 3, 2, 2, 2, 177, 175, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2, 178, 183, 3, 2, 2, 2, 179, 177, 3, 2, 2, 2, 180, 182, 7, 12, 2, 2, 181, 180, 3, 2, 2, 2, 182, 185, 3, 2, 2, 2, 183, 181, 3, 2, 2, 2, 183, 184, 3, 2, 2, 2, 184, 19, 3, 2, 2, 2, 185, 183, 3, 2, 2, 2, 186, 187, 5, 8, 5, 2, 187, 21, 3, 2, 2, 2, 188, 189, 5, 24, 13, 2, 189, 190, 7, 15, 2, 2, 190, 191, 5, 8, 5, 2, 191, 199, 3, 2, 2, 2, 192, 193, 7, 7, 2, 2, 193, 194, 5, 8, 5, 2, 194, 195, 7, 8, 2, 2, 195, 196, 7, 15, 2, 2, 196, 197, 5, 8, 5, 2, 197, 199, 3, 2, 2, 2, 198, 188, 3, 2, 2, 2, 198, 192, 3, 2, 2, 2, 199, 23, 3, 2, 2, 2, 200, 208, 5, 26, 14, 2, 201, 208, 7, 55, 2, 2, 202, 208, 5, 34, 18, 2, 203, 204, 7, 7, 2, 2, 204, 205, 5, 8, 5, 2, 205, 206, 7, 8, 2, 2, 206, 208, 3, 2, 2, 2, 207, 200, 3, 2, 2, 2, 207, 201, 3, 2, 2, 2, 207, 202, 3, 2, 2, 2, 207, 203, 3, 2, 2, 2, 208, 25, 3, 2, 2, 2, 209, 212, 5, 28, 15, 2, 210, 212, 5, 38, 20, 2, 211, 209, 3, 2, 2, 2, 211, 210, 3, 2, 2, 2, 212, 27, 3, 2, 2, 2, 213, 219, 5, 30, 16, 2, 214, 219, 7, 43, 2, 2, 215, 219, 7, 41, 2, 2, 216, 219, 7, 42, 2, 2, 217, 219, 7, 54, 2, 2, 218, 213, 3, 2, 2, 2, 218, 214, 3, 2, 2, 2, 218, 215, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 217, 3, 2, 2, 2, 219, 29, 3, 2, 2, 2, 220, 221, 7, 17, 2, 2, 221, 222, 7, 54, 2, 2, 222, 31, 3, 2, 2, 2, 223, 228, 7, 40, 2, 2, 224, 228, 7, 39, 2, 2, 225, 228, 7, 55, 2, 2, 226, 228, 5, 34, 18, 2, 227, 223, 3, 2, 2, 2, 227, 224, 3, 2, 2, 2, 227, 225, 3, 2, 2, 2, 227, 226, 3, 2, 2, 2, 228, 33, 3, 2, 2, 2, 229, 232, 5, 36, 19, 2, 230, 232, 7, 44, 2, 2, 231, 229, 3, 2, 2, 2, 231, 230, 3, 2, 2, 2, 232, 35, 3, 2, 2, 2, 233, 234, 7, 44, 2, 2, 234, 235, 7, 26, 2, 2, 235, 37, 3, 2, 2, 2, 236, 239, 5, 40, 21, 2, 237, 239, 7, 39, 2, 2, 238, 236, 3, 2, 2, 2, 238, 237, 3, 2, 2, 2, 239, 39, 3, 2, 2, 2, 240, 241, 7, 38, 2, 2, 241, 41, 3, 2, 2, 2, 242, 243, 9, 6, 2, 2, 243, 43, 3, 2, 2, 2, 27, 45, 67, 106, 110, 115, 117, 126, 130, 132, 138, 146, 149, 152, 163, 167, 172, 177, 183, 198, 207, 211, 218, 227, 231, 238]
--------------------------------------------------------------------------------
/src/platform/formula/grammar/CellAddress.g4:
--------------------------------------------------------------------------------
1 | grammar CellAddress;
2 |
3 | cellReference
4 | : WorkSheetPrefix? a1Reference EOF #CellAddress
5 | | WorkSheetPrefix? startRef=a1Reference ':' endRef=a1Reference EOF #CellRange
6 | | WorkSheetPrefix? startRef=a1Reference '->' endRef=a1Reference EOF #CellFloatRange
7 | ;
8 |
9 | // 不包括 sheet 名称的单元格地址
10 | a1Reference: a1Column a1Row;
11 |
12 | a1Column: a1RelativeColumn | a1AbsoluteColumn;
13 | a1Row: a1RelativeRow | a1AbsoluteRow;
14 |
15 | a1RelativeColumn: CellColumnAddress;
16 | a1AbsoluteColumn: '$' CellColumnAddress;
17 |
18 | a1RelativeRow: CellRowAddress;
19 | a1AbsoluteRow: '$' CellRowAddress;
20 |
21 | WorkSheetPrefix
22 | : SheetName '!'
23 | ;
24 |
25 | CellColumnAddress: [A-Za-z] [A-Za-z]*;
26 | CellRowAddress: [1-9] [0-9]*;
27 |
28 | Colon: ':';
29 | Dollar: '$';
30 | ExclamationMark: '!';
31 |
32 | ArrowRight: '->';
33 |
34 | // 表格名称
35 | fragment SheetName
36 | : SheetNameCharacter+
37 | | StringLiteral
38 | ;
39 |
40 | /// String Literals
41 | fragment StringLiteral
42 | :'"' DoubleStringCharacter* '"'
43 | |'\'' SingleStringCharacter* '\''
44 | ;
45 |
46 | fragment DoubleStringCharacter
47 | : ~["\\\r\n]
48 | | '\\' EscapeSequence
49 | | LineContinuation
50 | ;
51 |
52 | fragment SingleStringCharacter
53 | : ~['\\\r\n]
54 | | '\\' EscapeSequence
55 | | LineContinuation
56 | ;
57 |
58 | // 用户可输入的字符串
59 | fragment SheetNameCharacter
60 | : ~('\\' | '\r' | '\n' | '+' | '-' | '*' | '/' | '[' | ']' | '!' | '\'' | '"')
61 | | '\\' EscapeSequence
62 | | LineContinuation
63 | ;
64 |
65 | fragment EscapeSequence
66 | : CharacterEscapeSequence
67 | | '0' // no digit ahead! TODO
68 | | HexEscapeSequence
69 | | UnicodeEscapeSequence
70 | | ExtendedUnicodeEscapeSequence
71 | ;
72 | fragment CharacterEscapeSequence
73 | : SingleEscapeCharacter
74 | | NonEscapeCharacter
75 | ;
76 |
77 | fragment LineContinuation
78 | : '\\' [\r\n\u2028\u2029]
79 | ;
80 |
81 | fragment HexEscapeSequence
82 | : 'x' HexDigit HexDigit
83 | ;
84 |
85 | fragment UnicodeEscapeSequence
86 | : 'u' HexDigit HexDigit HexDigit HexDigit
87 | | 'u' '{' HexDigit HexDigit+ '}'
88 | ;
89 |
90 | fragment ExtendedUnicodeEscapeSequence
91 | : 'u' '{' HexDigit+ '}'
92 | ;
93 |
94 | fragment SingleEscapeCharacter
95 | : ['"\\bfnrtv]
96 | ;
97 |
98 | fragment NonEscapeCharacter
99 | : ~['"\\bfnrtv0-9xu\r\n]
100 | ;
101 |
102 | fragment HexDigit
103 | : [_0-9a-fA-F]
104 | ;
--------------------------------------------------------------------------------
/src/platform/formula/grammar/ReportFormulaParser.g4:
--------------------------------------------------------------------------------
1 | parser grammar ReportFormulaParser;
2 |
3 | options {
4 | tokenVocab = ReportFormulaLexer;
5 | }
6 |
7 | formulaExpr : '='? expressionStatement EOF; // EOF 终止符需要添加,保证所有字符都会解析
8 | expressionStatement: expressionSequence;
9 |
10 | expressionSequence : singleExpression ; //(',' singleExpression)*
11 |
12 | singleExpression
13 | : singleExpression '[' expressionSequence ']' # MemberIndexExpression
14 | | singleExpression '?'? '.' '#'? identifierName # MemberDotExpression
15 | | singleExpression arguments # ArgumentsExpression
16 | | '+' singleExpression # UnaryPlusExpression
17 | | '-' singleExpression # UnaryMinusExpression
18 | | singleExpression op='**' singleExpression # PowerExpression
19 | | singleExpression op=('*' | '/' | '%') singleExpression # MultiplicativeExpression
20 | | singleExpression op=('+' | '-') singleExpression # AdditiveExpression
21 | | singleExpression op=('<' | '>' | '<=' | '>=') singleExpression # RelationalExpression
22 | | singleExpression op=('==' | '!=' ) singleExpression # EqualityExpression
23 | | singleExpression '&&' singleExpression # LogicalAndExpression
24 | | singleExpression '||' singleExpression # LogicalOrExpression
25 | | singleExpression '?' singleExpression ':' singleExpression # TernaryExpression
26 | | singleExpression '=' singleExpression # AssignmentExpression
27 | | identifier # IdentifierExpression
28 | | literal # LiteralExpression
29 | | arrayLiteral # ArrayLiteralExpression
30 | | objectLiteral # ObjectLiteralExpression
31 | | '(' expressionSequence ')' # ParenthesizedExpression
32 | ;
33 |
34 | arguments
35 | : '('(argument (',' argument)* ','?)?')'
36 | ;
37 |
38 | argument
39 | : singleExpression
40 | | identifier
41 | ;
42 |
43 | objectLiteral
44 | : '{' (propertyAssignment (',' propertyAssignment)*)? ','? '}'
45 | ;
46 |
47 | arrayLiteral
48 | : ('[' elementList ']')
49 | ;
50 |
51 | elementList
52 | : ','* arrayElement? (','+ arrayElement)* ','* // Yes, everything is optional
53 | ;
54 |
55 | arrayElement
56 | : singleExpression
57 | ;
58 |
59 | propertyAssignment
60 | : propertyName ':' singleExpression # PropertyExpressionAssignment
61 | | '[' singleExpression ']' ':' singleExpression # ComputedPropertyExpressionAssignment
62 | ;
63 |
64 | propertyName
65 | : identifierName
66 | | StringLiteral
67 | | numericLiteral
68 | | '[' singleExpression ']'
69 | ;
70 |
71 | identifierName
72 | : identifier
73 | | reservedWord
74 | ;
75 |
76 |
77 | // 标识符
78 |
79 | identifier
80 | : refItemCode #IdentifierRefItemCode
81 | | CellAddressLiteral #IdentifierCellAddress
82 | | CellRangeLiteral #IdentifierCellRange
83 | | CellFloatRangeLiteral #IdentifierCellFloatRange
84 | | Identifier #IdentifierPlainText
85 | ;
86 |
87 | refItemCode: '@' Identifier; //报表项的标识符,浮动行的标识符
88 |
89 | // 字面量
90 | literal
91 | : NullLiteral #NullLiteralExpression
92 | | BooleanLiteral #BooleanLiteralExpression
93 | | StringLiteral #StringLiteralExpression
94 | | numericLiteral #NumericLiteralExpression
95 | ;
96 |
97 |
98 | numericLiteral
99 | : percentageLiteral #PercentageLiteralExpression
100 | | BasicNumberLiteral #BasicNumberLiteralExpression
101 | ;
102 |
103 | percentageLiteral
104 | : BasicNumberLiteral '%';
105 |
106 | reservedWord
107 | : keyword
108 | | BooleanLiteral
109 | ;
110 |
111 | keyword
112 | : If
113 | ;
114 |
115 | eos
116 | : SemiColon
117 | | EOF
118 | ;
--------------------------------------------------------------------------------
/src/platform/formula/grammar/formulatree.md:
--------------------------------------------------------------------------------
1 | 本文档描述了核心的公式语法树(FTree AST)节点类型。
2 |
3 | 基本的定义符合 [ESTree 语法规范](https://github.com/estree/estree/blob/master/es5.md)。
4 |
5 |
6 |
7 |
8 |
9 | - [FNode 类型](#fnode-%E7%B1%BB%E5%9E%8B)
10 | - [标识符](#%E6%A0%87%E8%AF%86%E7%AC%A6)
11 | - [报表项引用](#%E6%8A%A5%E8%A1%A8%E9%A1%B9%E5%BC%95%E7%94%A8)
12 | - [单元格地址](#%E5%8D%95%E5%85%83%E6%A0%BC%E5%9C%B0%E5%9D%80)
13 | - [单元格范围](#%E5%8D%95%E5%85%83%E6%A0%BC%E8%8C%83%E5%9B%B4)
14 | - [一般标识符](#%E4%B8%80%E8%88%AC%E6%A0%87%E8%AF%86%E7%AC%A6)
15 | - [字面量](#%E5%AD%97%E9%9D%A2%E9%87%8F)
16 | - [公式](#%E5%85%AC%E5%BC%8F)
17 | - [语句](#%E8%AF%AD%E5%8F%A5)
18 | - [表达式语句](#%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AF%AD%E5%8F%A5)
19 | - [表达式](#%E8%A1%A8%E8%BE%BE%E5%BC%8F)
20 | - [数组表达式](#%E6%95%B0%E7%BB%84%E8%A1%A8%E8%BE%BE%E5%BC%8F)
21 | - [对象表达式](#%E5%AF%B9%E8%B1%A1%E8%A1%A8%E8%BE%BE%E5%BC%8F)
22 | - [一元操作](#%E4%B8%80%E5%85%83%E6%93%8D%E4%BD%9C)
23 | - [一元操作符表达式](#%E4%B8%80%E5%85%83%E6%93%8D%E4%BD%9C%E7%AC%A6%E8%A1%A8%E8%BE%BE%E5%BC%8F)
24 | - [一元操作符](#%E4%B8%80%E5%85%83%E6%93%8D%E4%BD%9C%E7%AC%A6)
25 | - [二元操作](#%E4%BA%8C%E5%85%83%E6%93%8D%E4%BD%9C)
26 | - [二元表达式](#%E4%BA%8C%E5%85%83%E8%A1%A8%E8%BE%BE%E5%BC%8F)
27 | - [二元运算符](#%E4%BA%8C%E5%85%83%E8%BF%90%E7%AE%97%E7%AC%A6)
28 | - [赋值表达式](#%E8%B5%8B%E5%80%BC%E8%A1%A8%E8%BE%BE%E5%BC%8F)
29 | - [赋值运算符](#%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6)
30 | - [逻辑表达式](#%E9%80%BB%E8%BE%91%E8%A1%A8%E8%BE%BE%E5%BC%8F)
31 | - [逻辑运算符](#%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97%E7%AC%A6)
32 | - [条件表达式](#%E6%9D%A1%E4%BB%B6%E8%A1%A8%E8%BE%BE%E5%BC%8F)
33 | - [调用表达式](#%E8%B0%83%E7%94%A8%E8%A1%A8%E8%BE%BE%E5%BC%8F)
34 | - [表达式序列调用](#%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%BA%8F%E5%88%97%E8%B0%83%E7%94%A8)
35 |
36 |
37 |
38 | # FNode 类型
39 |
40 | FTree 节点通过 FNode 表示,该节点将会有不同继承方式,其接口定义如下:
41 | ```js
42 | interface FNode {
43 | type: string;
44 | loc: SourceLocation | null;
45 | }
46 | ```
47 | type 字段表示 AST 的不同类型。每个 FNode 子类型的 type 字段,具有特定字符串,详细信息将在下文描述。
48 | 可以使用 type 字段确定某个节点实现的具体接口。
49 | loc 字段表示节点的源码位置。如果节点没有源码位置信息,则为 null。如果有位置信息,则通过一个对象表示源码位置。
50 | 该对象包含一个开始位置(被解析的源码区域的第一个字符位置)和一个结束位置(被解析源码区域后面的第一个字符位置)。
51 |
52 | ```js
53 | interface SourceLocation {
54 | source: string | null;
55 | start: Position;
56 | end: Position;
57 | }
58 | ```
59 | 每个 Position 对象包含一个 line 数字(起始索引是 1)和一个 column 数字(起始索引是 0)。
60 | ```js
61 | interface Position {
62 | line: number; // >= 1
63 | column: number; // >= 0
64 | }
65 | ```
66 |
67 | # 标识符
68 |
69 | ```js
70 | interface Identifier <: Expression {
71 | type: "Identifier";
72 | name: string;
73 | }
74 | ```
75 | 一个标识符。注意一个表示符可能是一个表达式。
76 |
77 | ## 报表项引用
78 |
79 | ```js
80 | interface RefItemIdentifier <: Identifier {
81 | type: "RefItemIdentifier";
82 | name: string;
83 | ```
84 |
85 | ## 单元格地址
86 |
87 | 单元格引用的基类。
88 | ```js
89 | interface CellRef <:Identifier {}
90 | ```
91 |
92 | ```js
93 | interface CellAddressIdentifier <: CellRef {
94 | type: "CellAddressIdentifier";
95 | sheetName: SheetNameIdentifier;
96 | a1Reference: A1ReferenceIdentifier;
97 | }
98 | ```
99 |
100 | 表格的引用.
101 |
102 | ```js
103 | interface SheetNameIdentifier <:Identifier {
104 | type: "SheetNameIdentifier";
105 | name: string;
106 | }
107 | ```
108 |
109 | 单元格地址的引用方式。
110 | ```js
111 | interface A1ReferenceIdentifier {
112 | type: "A1ReferenceIdentifier";
113 | columnRef: AbsoluteColumn | RelativeColumn;
114 | rowRef: AbsoluteRow | RelativeRow;
115 | }
116 | ```
117 |
118 | ```js
119 | interface CellColumn {
120 | text: string;
121 | }
122 | ```
123 |
124 | ```js
125 | interface AbsoluteColumnIdentifier <: CellColumn {
126 | type: "AbsoluteColumnIdentifier";
127 | }
128 | ```
129 |
130 | ```js
131 | interface RelativeColumnIdentifier <: CellColumn {
132 | type: "RelativeColumnIdentifier";
133 | }
134 | ```
135 |
136 | ```js
137 | interface CellRow {
138 | line: number;
139 | }
140 | ```
141 | ```js
142 | interface AbsoluteRowIdentifier <:CellRow {
143 | type: "AbsoluteRowIdentifier";
144 | }
145 | ```
146 | ```js
147 | interface RelativeRowIdentifier <:CellRow {
148 | type: "RelativeRowIdentifier";
149 | }
150 | ```
151 |
152 | ## 单元格范围
153 |
154 | ```js
155 | interface CellRangeIdentifier <: CellRef {
156 | type: "CellRangeIdentifier";
157 | sheetName: SheetName;
158 | startRef: A1Reference;
159 | endRef: A1Reference;
160 | }
161 | ```
162 |
163 | sheetName 字段表示单元格名称。
164 | startRef 字段表示其实单元格地址的引用;endRef 表示结束单元格地址的引用。
165 |
166 | ## 浮动单元格范围
167 |
168 | ```js
169 | interface CellFloatRangeIdentifier <: CellRangeIdentifier {
170 | type: "CellFloatRangeIdentifier";
171 | sheetName: SheetName;
172 | startRef: A1Reference;
173 | endRef: A1Reference;
174 | }
175 | ```
176 |
177 | sheetName 字段表示单元格名称。
178 | startRef 字段表示其实单元格地址的引用;endRef 表示结束单元格地址的引用。
179 |
180 | ## 一般标识符
181 |
182 | ```js
183 | interface PlainTextIdentifier <: Expression {
184 | type: "PlainTextIdentifier";
185 | name: string;
186 | }
187 | ```
188 |
189 | # 字面量
190 |
191 | ```js
192 | interface Literal <: Expression {
193 | type: "Literal";
194 | value: string | boolean | null | number;
195 | }
196 | ```
197 |
198 | # 公式
199 | ```js
200 | interface FormulaProgram <: FNode {
201 | type: "FormulaProgram";
202 | body: [ Statement ];
203 | }
204 | ```
205 |
206 | # 语句
207 |
208 | ```js
209 | interface Statement <: FNode { }
210 | ```
211 |
212 | ## 表达式语句
213 |
214 | ```js
215 | interface ExpressionStatement <: Statement {
216 | type: "ExpressionStatement";
217 | expression: Expression;
218 | }
219 | ```
220 |
221 | 一个表达式语句,包含一个表达式。
222 |
223 | # 表达式
224 | ```js
225 | interface Expression <: FNode { }
226 | ```
227 | 任一的表达式节点。
228 |
229 | ## 数组表达式
230 | ```js
231 | interface ArrayExpression <: Expression {
232 | type: "ArrayExpression";
233 | elements: [ Expression | null ];
234 | }
235 | ```
236 | 一个数组表达式。如果一个元素表示一个稀疏数组中的空位,则该元素为 null。例如 `[1,,2]`。
237 |
238 | ## 对象表达式
239 |
240 | ```js
241 | interface ObjectExpression <: Expression {
242 | type: "ObjectExpression";
243 | properties: [ Property ];
244 | }
245 | ```
246 |
247 | 一个对象表达式。
248 | ```js
249 | interface Property <: FNode {
250 | type: "Property";
251 | key: Literal | Identifier;
252 | value: Expression;
253 | kind: "init" | "get" | "set";
254 | }
255 | ```
256 |
257 |
258 | ## 一元操作
259 |
260 | ### 一元操作符表达式
261 |
262 | ```js
263 | interface UnaryExpression <: Expression {
264 | type: "UnaryExpression";
265 | operator: UnaryOperator;
266 | prefix: boolean;
267 | argument: Expression;
268 | }
269 | ```
270 | 一元操作符表达式。
271 |
272 | #### 一元操作符
273 | ```js
274 | enum UnaryOperator {
275 | "-" | "+" | "!" | "~"
276 | }
277 | ```
278 | 一元操作符符号。
279 |
280 | ## 二元操作
281 |
282 | ### 二元表达式
283 |
284 | ```js
285 | interface BinaryExpression <: Expression {
286 | type: "BinaryExpression";
287 | operator: BinaryOperator;
288 | left: Expression;
289 | right: Expression;
290 | }
291 | ```
292 | 二元运算符表达式。
293 |
294 | #### 二元运算符
295 |
296 | ```js
297 | enum BinaryOperator {
298 | "==" | "!="
299 | | "<" | "<=" | ">" | ">="
300 | | "<<" | ">>" | ">>>"
301 | | "+" | "-" | "*" | "/" | "%"
302 | | "**"
303 | }
304 | ```
305 | 二元运算符符号。
306 |
307 | ### 赋值表达式
308 |
309 | ```js
310 | interface AssignmentExpression <: Expression {
311 | type: "AssignmentExpression";
312 | operator: AssignmentOperator;
313 | left: Pattern | Expression;
314 | right: Expression;
315 | }
316 | ```
317 | 一个赋值运算符表达式。
318 |
319 | #### 赋值运算符
320 |
321 | ```js
322 | enum AssignmentOperator {
323 | "="
324 | }
325 | ```
326 |
327 | ### 逻辑表达式
328 |
329 | ```js
330 | interface LogicalExpression <: Expression {
331 | type: "LogicalExpression";
332 | operator: LogicalOperator;
333 | left: Expression;
334 | right: Expression;
335 | }
336 | ```
337 | 逻辑运算符表达式。
338 |
339 | #### 逻辑运算符
340 |
341 | ```js
342 | enum LogicalOperator {
343 | "||" | "&&"
344 | }
345 | ```
346 |
347 | ## 条件表达式
348 |
349 | ```js
350 | interface ConditionalExpression <: Expression {
351 | type: "ConditionalExpression";
352 | test: Expression;
353 | alternate: Expression;
354 | consequent: Expression;
355 | }
356 | ```
357 | 条件表达式:test ? consequent : alternate
358 |
359 |
360 | ## 调用表达式
361 |
362 | ```js
363 | interface CallExpression <: Expression {
364 | type: "CallExpression";
365 | callee: Expression;
366 | arguments: [ Expression ];
367 | }
368 | ```
369 |
370 | 函数调用表达式。
371 |
372 | ## 表达式序列调用
373 |
374 | ```js
375 | interface SequenceExpression <: Expression {
376 | type: "SequenceExpression";
377 | expressions: [ Expression ];
378 | }
379 | ```
380 | 序列表达式,例如,逗号分割的表达式序列。
--------------------------------------------------------------------------------
/src/platform/formula/intelliSense/formulaSignatureHelp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 函数自动补全的信息。
3 | *
4 | * @see https://support.google.com/docs/answer/3093364
5 | * @see https://support.google.com/docs/answer/3093440
6 | * @see https://support.google.com/docs/table/25273?hl=zh-Hans&ref_topic=9054531
7 | */
8 | let FormulaSignatureList = {
9 | 'IF': {
10 | prefixDisplayParts: 'IF(',
11 | suffixDisplayParts: ')',
12 | separatorDisplayParts: ',',
13 | documentation: '当逻辑表达式的值为 TRUE 时返回一个值,而当其为 FALSE 时返回另一个值。',
14 | example: 'IF(A2,"A2 was true","A2 was false")',
15 | parameters: [
16 | {
17 | displayParts: '逻辑表达式',
18 | documentation: '一个表达式或对包含表达式的单元格的引用,该表达式代表某种逻辑值,即 TRUE 或 FALSE。',
19 | },
20 | {
21 | displayParts: '为 TRUE 时的返回值',
22 | documentation: '当“逻辑表达式”为 TRUE 时的返回值。',
23 | },
24 | {
25 | displayParts: '为 FAISE 时的返回值',
26 | documentation: '当“逻辑表达式”为 FAISE 时的返回值。',
27 | }
28 | ]
29 | },
30 | 'MIN': {
31 | prefixDisplayParts: 'MIN(',
32 | suffixDisplayParts: ')',
33 | separatorDisplayParts: ',',
34 | documentation: '返回数值数据集中的最小值。',
35 | example: 'MIN(A2:A100,B2:B100,4,26)',
36 | parameters: [
37 | {
38 | displayParts: '值1',
39 | documentation: '计算最小值时所用的第一个值或范围。',
40 | },
41 | {
42 | displayParts: '[值2, ...]',
43 | documentation: '[可选] - 在计算最小值时要考虑的其他数值或范围。',
44 | }
45 | ]
46 | },
47 | 'ROUND': {
48 | prefixDisplayParts: 'ROUND(',
49 | suffixDisplayParts: ')',
50 | separatorDisplayParts: ',',
51 | documentation: '按标准规则,将数值的指定小数位之后的部分四舍五入。',
52 | example: 'ROUND(A2)',
53 | parameters: [
54 | {
55 | displayParts: '值',
56 | documentation: '要四舍五入为位数位小数的数值。',
57 | },
58 | {
59 | displayParts: '位数',
60 | documentation: '[可选 - 默认值为0] - 要舍入到的小数位数。位数可以取负值,在这种情况下会将值的小数点左侧部分舍入到指定的位数。',
61 | }
62 | ]
63 | }
64 | };
65 |
66 | exports.FormulaSignatureList = FormulaSignatureList;
--------------------------------------------------------------------------------
/src/platform/formula/runtime/CellAddress.tokens:
--------------------------------------------------------------------------------
1 | WorkSheetPrefix=1
2 | CellColumnAddress=2
3 | CellRowAddress=3
4 | Colon=4
5 | Dollar=5
6 | ExclamationMark=6
7 | ArrowRight=7
8 | ':'=4
9 | '$'=5
10 | '!'=6
11 | '->'=7
12 |
--------------------------------------------------------------------------------
/src/platform/formula/runtime/CellAddressLexer.js:
--------------------------------------------------------------------------------
1 | // Generated from /Users/zhangye/Documents/code/code_yonyou/FormulaTextArea/src/platform/formula/grammar/CellAddress.g4 by ANTLR 4.8
2 | // jshint ignore: start
3 | var antlr4 = require('antlr4/index');
4 |
5 |
6 |
7 | var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964",
8 | "\u0002\t\u00a6\b\u0001\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004",
9 | "\u0004\t\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0004\u0007\t",
10 | "\u0007\u0004\b\t\b\u0004\t\t\t\u0004\n\t\n\u0004\u000b\t\u000b\u0004",
11 | "\f\t\f\u0004\r\t\r\u0004\u000e\t\u000e\u0004\u000f\t\u000f\u0004\u0010",
12 | "\t\u0010\u0004\u0011\t\u0011\u0004\u0012\t\u0012\u0004\u0013\t\u0013",
13 | "\u0004\u0014\t\u0014\u0004\u0015\t\u0015\u0004\u0016\t\u0016\u0003\u0002",
14 | "\u0003\u0002\u0003\u0002\u0003\u0003\u0003\u0003\u0007\u00033\n\u0003",
15 | "\f\u0003\u000e\u00036\u000b\u0003\u0003\u0004\u0003\u0004\u0007\u0004",
16 | ":\n\u0004\f\u0004\u000e\u0004=\u000b\u0004\u0003\u0005\u0003\u0005\u0003",
17 | "\u0006\u0003\u0006\u0003\u0007\u0003\u0007\u0003\b\u0003\b\u0003\b\u0003",
18 | "\t\u0006\tI\n\t\r\t\u000e\tJ\u0003\t\u0005\tN\n\t\u0003\n\u0003\n\u0007",
19 | "\nR\n\n\f\n\u000e\nU\u000b\n\u0003\n\u0003\n\u0003\n\u0007\nZ\n\n\f",
20 | "\n\u000e\n]\u000b\n\u0003\n\u0005\n`\n\n\u0003\u000b\u0003\u000b\u0003",
21 | "\u000b\u0003\u000b\u0005\u000bf\n\u000b\u0003\f\u0003\f\u0003\f\u0003",
22 | "\f\u0005\fl\n\f\u0003\r\u0003\r\u0003\r\u0003\r\u0005\rr\n\r\u0003\u000e",
23 | "\u0003\u000e\u0003\u000e\u0003\u000e\u0003\u000e\u0005\u000ey\n\u000e",
24 | "\u0003\u000f\u0003\u000f\u0005\u000f}\n\u000f\u0003\u0010\u0003\u0010",
25 | "\u0003\u0010\u0003\u0011\u0003\u0011\u0003\u0011\u0003\u0011\u0003\u0012",
26 | "\u0003\u0012\u0003\u0012\u0003\u0012\u0003\u0012\u0003\u0012\u0003\u0012",
27 | "\u0003\u0012\u0003\u0012\u0003\u0012\u0006\u0012\u0090\n\u0012\r\u0012",
28 | "\u000e\u0012\u0091\u0003\u0012\u0003\u0012\u0005\u0012\u0096\n\u0012",
29 | "\u0003\u0013\u0003\u0013\u0003\u0013\u0006\u0013\u009b\n\u0013\r\u0013",
30 | "\u000e\u0013\u009c\u0003\u0013\u0003\u0013\u0003\u0014\u0003\u0014\u0003",
31 | "\u0015\u0003\u0015\u0003\u0016\u0003\u0016\u0002\u0002\u0017\u0003\u0003",
32 | "\u0005\u0004\u0007\u0005\t\u0006\u000b\u0007\r\b\u000f\t\u0011\u0002",
33 | "\u0013\u0002\u0015\u0002\u0017\u0002\u0019\u0002\u001b\u0002\u001d\u0002",
34 | "\u001f\u0002!\u0002#\u0002%\u0002\'\u0002)\u0002+\u0002\u0003\u0002",
35 | "\f\u0004\u0002C\\c|\u0003\u00023;\u0003\u00022;\u0006\u0002\f\f\u000f",
36 | "\u000f$$^^\u0006\u0002\f\f\u000f\u000f))^^\n\u0002\f\f\u000f\u000f#",
37 | "$)),-//11]_\u0005\u0002\f\f\u000f\u000f\u202a\u202b\u000b\u0002$$))",
38 | "^^ddhhppttvvxx\u000e\u0002\f\f\u000f\u000f$$))2;^^ddhhppttvxzz\u0006",
39 | "\u00022;CHaach\u0002\u00ac\u0002\u0003\u0003\u0002\u0002\u0002\u0002",
40 | "\u0005\u0003\u0002\u0002\u0002\u0002\u0007\u0003\u0002\u0002\u0002\u0002",
41 | "\t\u0003\u0002\u0002\u0002\u0002\u000b\u0003\u0002\u0002\u0002\u0002",
42 | "\r\u0003\u0002\u0002\u0002\u0002\u000f\u0003\u0002\u0002\u0002\u0003",
43 | "-\u0003\u0002\u0002\u0002\u00050\u0003\u0002\u0002\u0002\u00077\u0003",
44 | "\u0002\u0002\u0002\t>\u0003\u0002\u0002\u0002\u000b@\u0003\u0002\u0002",
45 | "\u0002\rB\u0003\u0002\u0002\u0002\u000fD\u0003\u0002\u0002\u0002\u0011",
46 | "M\u0003\u0002\u0002\u0002\u0013_\u0003\u0002\u0002\u0002\u0015e\u0003",
47 | "\u0002\u0002\u0002\u0017k\u0003\u0002\u0002\u0002\u0019q\u0003\u0002",
48 | "\u0002\u0002\u001bx\u0003\u0002\u0002\u0002\u001d|\u0003\u0002\u0002",
49 | "\u0002\u001f~\u0003\u0002\u0002\u0002!\u0081\u0003\u0002\u0002\u0002",
50 | "#\u0095\u0003\u0002\u0002\u0002%\u0097\u0003\u0002\u0002\u0002\'\u00a0",
51 | "\u0003\u0002\u0002\u0002)\u00a2\u0003\u0002\u0002\u0002+\u00a4\u0003",
52 | "\u0002\u0002\u0002-.\u0005\u0011\t\u0002./\u0007#\u0002\u0002/\u0004",
53 | "\u0003\u0002\u0002\u000204\t\u0002\u0002\u000213\t\u0002\u0002\u0002",
54 | "21\u0003\u0002\u0002\u000236\u0003\u0002\u0002\u000242\u0003\u0002\u0002",
55 | "\u000245\u0003\u0002\u0002\u00025\u0006\u0003\u0002\u0002\u000264\u0003",
56 | "\u0002\u0002\u00027;\t\u0003\u0002\u00028:\t\u0004\u0002\u000298\u0003",
57 | "\u0002\u0002\u0002:=\u0003\u0002\u0002\u0002;9\u0003\u0002\u0002\u0002",
58 | ";<\u0003\u0002\u0002\u0002<\b\u0003\u0002\u0002\u0002=;\u0003\u0002",
59 | "\u0002\u0002>?\u0007<\u0002\u0002?\n\u0003\u0002\u0002\u0002@A\u0007",
60 | "&\u0002\u0002A\f\u0003\u0002\u0002\u0002BC\u0007#\u0002\u0002C\u000e",
61 | "\u0003\u0002\u0002\u0002DE\u0007/\u0002\u0002EF\u0007@\u0002\u0002F",
62 | "\u0010\u0003\u0002\u0002\u0002GI\u0005\u0019\r\u0002HG\u0003\u0002\u0002",
63 | "\u0002IJ\u0003\u0002\u0002\u0002JH\u0003\u0002\u0002\u0002JK\u0003\u0002",
64 | "\u0002\u0002KN\u0003\u0002\u0002\u0002LN\u0005\u0013\n\u0002MH\u0003",
65 | "\u0002\u0002\u0002ML\u0003\u0002\u0002\u0002N\u0012\u0003\u0002\u0002",
66 | "\u0002OS\u0007$\u0002\u0002PR\u0005\u0015\u000b\u0002QP\u0003\u0002",
67 | "\u0002\u0002RU\u0003\u0002\u0002\u0002SQ\u0003\u0002\u0002\u0002ST\u0003",
68 | "\u0002\u0002\u0002TV\u0003\u0002\u0002\u0002US\u0003\u0002\u0002\u0002",
69 | "V`\u0007$\u0002\u0002W[\u0007)\u0002\u0002XZ\u0005\u0017\f\u0002YX\u0003",
70 | "\u0002\u0002\u0002Z]\u0003\u0002\u0002\u0002[Y\u0003\u0002\u0002\u0002",
71 | "[\\\u0003\u0002\u0002\u0002\\^\u0003\u0002\u0002\u0002][\u0003\u0002",
72 | "\u0002\u0002^`\u0007)\u0002\u0002_O\u0003\u0002\u0002\u0002_W\u0003",
73 | "\u0002\u0002\u0002`\u0014\u0003\u0002\u0002\u0002af\n\u0005\u0002\u0002",
74 | "bc\u0007^\u0002\u0002cf\u0005\u001b\u000e\u0002df\u0005\u001f\u0010",
75 | "\u0002ea\u0003\u0002\u0002\u0002eb\u0003\u0002\u0002\u0002ed\u0003\u0002",
76 | "\u0002\u0002f\u0016\u0003\u0002\u0002\u0002gl\n\u0006\u0002\u0002hi",
77 | "\u0007^\u0002\u0002il\u0005\u001b\u000e\u0002jl\u0005\u001f\u0010\u0002",
78 | "kg\u0003\u0002\u0002\u0002kh\u0003\u0002\u0002\u0002kj\u0003\u0002\u0002",
79 | "\u0002l\u0018\u0003\u0002\u0002\u0002mr\n\u0007\u0002\u0002no\u0007",
80 | "^\u0002\u0002or\u0005\u001b\u000e\u0002pr\u0005\u001f\u0010\u0002qm",
81 | "\u0003\u0002\u0002\u0002qn\u0003\u0002\u0002\u0002qp\u0003\u0002\u0002",
82 | "\u0002r\u001a\u0003\u0002\u0002\u0002sy\u0005\u001d\u000f\u0002ty\u0007",
83 | "2\u0002\u0002uy\u0005!\u0011\u0002vy\u0005#\u0012\u0002wy\u0005%\u0013",
84 | "\u0002xs\u0003\u0002\u0002\u0002xt\u0003\u0002\u0002\u0002xu\u0003\u0002",
85 | "\u0002\u0002xv\u0003\u0002\u0002\u0002xw\u0003\u0002\u0002\u0002y\u001c",
86 | "\u0003\u0002\u0002\u0002z}\u0005\'\u0014\u0002{}\u0005)\u0015\u0002",
87 | "|z\u0003\u0002\u0002\u0002|{\u0003\u0002\u0002\u0002}\u001e\u0003\u0002",
88 | "\u0002\u0002~\u007f\u0007^\u0002\u0002\u007f\u0080\t\b\u0002\u0002\u0080",
89 | " \u0003\u0002\u0002\u0002\u0081\u0082\u0007z\u0002\u0002\u0082\u0083",
90 | "\u0005+\u0016\u0002\u0083\u0084\u0005+\u0016\u0002\u0084\"\u0003\u0002",
91 | "\u0002\u0002\u0085\u0086\u0007w\u0002\u0002\u0086\u0087\u0005+\u0016",
92 | "\u0002\u0087\u0088\u0005+\u0016\u0002\u0088\u0089\u0005+\u0016\u0002",
93 | "\u0089\u008a\u0005+\u0016\u0002\u008a\u0096\u0003\u0002\u0002\u0002",
94 | "\u008b\u008c\u0007w\u0002\u0002\u008c\u008d\u0007}\u0002\u0002\u008d",
95 | "\u008f\u0005+\u0016\u0002\u008e\u0090\u0005+\u0016\u0002\u008f\u008e",
96 | "\u0003\u0002\u0002\u0002\u0090\u0091\u0003\u0002\u0002\u0002\u0091\u008f",
97 | "\u0003\u0002\u0002\u0002\u0091\u0092\u0003\u0002\u0002\u0002\u0092\u0093",
98 | "\u0003\u0002\u0002\u0002\u0093\u0094\u0007\u007f\u0002\u0002\u0094\u0096",
99 | "\u0003\u0002\u0002\u0002\u0095\u0085\u0003\u0002\u0002\u0002\u0095\u008b",
100 | "\u0003\u0002\u0002\u0002\u0096$\u0003\u0002\u0002\u0002\u0097\u0098",
101 | "\u0007w\u0002\u0002\u0098\u009a\u0007}\u0002\u0002\u0099\u009b\u0005",
102 | "+\u0016\u0002\u009a\u0099\u0003\u0002\u0002\u0002\u009b\u009c\u0003",
103 | "\u0002\u0002\u0002\u009c\u009a\u0003\u0002\u0002\u0002\u009c\u009d\u0003",
104 | "\u0002\u0002\u0002\u009d\u009e\u0003\u0002\u0002\u0002\u009e\u009f\u0007",
105 | "\u007f\u0002\u0002\u009f&\u0003\u0002\u0002\u0002\u00a0\u00a1\t\t\u0002",
106 | "\u0002\u00a1(\u0003\u0002\u0002\u0002\u00a2\u00a3\n\n\u0002\u0002\u00a3",
107 | "*\u0003\u0002\u0002\u0002\u00a4\u00a5\t\u000b\u0002\u0002\u00a5,\u0003",
108 | "\u0002\u0002\u0002\u0012\u00024;JMS[_ekqx|\u0091\u0095\u009c\u0002"].join("");
109 |
110 |
111 | var atn = new antlr4.atn.ATNDeserializer().deserialize(serializedATN);
112 |
113 | var decisionsToDFA = atn.decisionToState.map( function(ds, index) { return new antlr4.dfa.DFA(ds, index); });
114 |
115 | function CellAddressLexer(input) {
116 | antlr4.Lexer.call(this, input);
117 | this._interp = new antlr4.atn.LexerATNSimulator(this, atn, decisionsToDFA, new antlr4.PredictionContextCache());
118 | return this;
119 | }
120 |
121 | CellAddressLexer.prototype = Object.create(antlr4.Lexer.prototype);
122 | CellAddressLexer.prototype.constructor = CellAddressLexer;
123 |
124 | Object.defineProperty(CellAddressLexer.prototype, "atn", {
125 | get : function() {
126 | return atn;
127 | }
128 | });
129 |
130 | CellAddressLexer.EOF = antlr4.Token.EOF;
131 | CellAddressLexer.WorkSheetPrefix = 1;
132 | CellAddressLexer.CellColumnAddress = 2;
133 | CellAddressLexer.CellRowAddress = 3;
134 | CellAddressLexer.Colon = 4;
135 | CellAddressLexer.Dollar = 5;
136 | CellAddressLexer.ExclamationMark = 6;
137 | CellAddressLexer.ArrowRight = 7;
138 |
139 | CellAddressLexer.prototype.channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ];
140 |
141 | CellAddressLexer.prototype.modeNames = [ "DEFAULT_MODE" ];
142 |
143 | CellAddressLexer.prototype.literalNames = [ null, null, null, null, "':'",
144 | "'$'", "'!'", "'->'" ];
145 |
146 | CellAddressLexer.prototype.symbolicNames = [ null, "WorkSheetPrefix", "CellColumnAddress",
147 | "CellRowAddress", "Colon",
148 | "Dollar", "ExclamationMark",
149 | "ArrowRight" ];
150 |
151 | CellAddressLexer.prototype.ruleNames = [ "WorkSheetPrefix", "CellColumnAddress",
152 | "CellRowAddress", "Colon", "Dollar",
153 | "ExclamationMark", "ArrowRight",
154 | "SheetName", "StringLiteral", "DoubleStringCharacter",
155 | "SingleStringCharacter", "SheetNameCharacter",
156 | "EscapeSequence", "CharacterEscapeSequence",
157 | "LineContinuation", "HexEscapeSequence",
158 | "UnicodeEscapeSequence", "ExtendedUnicodeEscapeSequence",
159 | "SingleEscapeCharacter", "NonEscapeCharacter",
160 | "HexDigit" ];
161 |
162 | CellAddressLexer.prototype.grammarFileName = "CellAddress.g4";
163 |
164 |
165 | exports.CellAddressLexer = CellAddressLexer;
166 |
167 |
--------------------------------------------------------------------------------
/src/platform/formula/runtime/CellAddressLexer.tokens:
--------------------------------------------------------------------------------
1 | WorkSheetPrefix=1
2 | CellColumnAddress=2
3 | CellRowAddress=3
4 | Colon=4
5 | Dollar=5
6 | ExclamationMark=6
7 | ArrowRight=7
8 | ':'=4
9 | '$'=5
10 | '!'=6
11 | '->'=7
12 |
--------------------------------------------------------------------------------
/src/platform/formula/runtime/CellAddressListener.js:
--------------------------------------------------------------------------------
1 | // Generated from /Users/zhangye/Documents/code/code_yonyou/FormulaTextArea/src/platform/formula/grammar/CellAddress.g4 by ANTLR 4.8
2 | // jshint ignore: start
3 | var antlr4 = require('antlr4/index');
4 |
5 | // This class defines a complete listener for a parse tree produced by CellAddressParser.
6 | function CellAddressListener() {
7 | antlr4.tree.ParseTreeListener.call(this);
8 | return this;
9 | }
10 |
11 | CellAddressListener.prototype = Object.create(antlr4.tree.ParseTreeListener.prototype);
12 | CellAddressListener.prototype.constructor = CellAddressListener;
13 |
14 | // Enter a parse tree produced by CellAddressParser#CellAddress.
15 | CellAddressListener.prototype.enterCellAddress = function(ctx) {
16 | };
17 |
18 | // Exit a parse tree produced by CellAddressParser#CellAddress.
19 | CellAddressListener.prototype.exitCellAddress = function(ctx) {
20 | };
21 |
22 |
23 | // Enter a parse tree produced by CellAddressParser#CellRange.
24 | CellAddressListener.prototype.enterCellRange = function(ctx) {
25 | };
26 |
27 | // Exit a parse tree produced by CellAddressParser#CellRange.
28 | CellAddressListener.prototype.exitCellRange = function(ctx) {
29 | };
30 |
31 |
32 | // Enter a parse tree produced by CellAddressParser#CellFloatRange.
33 | CellAddressListener.prototype.enterCellFloatRange = function(ctx) {
34 | };
35 |
36 | // Exit a parse tree produced by CellAddressParser#CellFloatRange.
37 | CellAddressListener.prototype.exitCellFloatRange = function(ctx) {
38 | };
39 |
40 |
41 | // Enter a parse tree produced by CellAddressParser#a1Reference.
42 | CellAddressListener.prototype.enterA1Reference = function(ctx) {
43 | };
44 |
45 | // Exit a parse tree produced by CellAddressParser#a1Reference.
46 | CellAddressListener.prototype.exitA1Reference = function(ctx) {
47 | };
48 |
49 |
50 | // Enter a parse tree produced by CellAddressParser#a1Column.
51 | CellAddressListener.prototype.enterA1Column = function(ctx) {
52 | };
53 |
54 | // Exit a parse tree produced by CellAddressParser#a1Column.
55 | CellAddressListener.prototype.exitA1Column = function(ctx) {
56 | };
57 |
58 |
59 | // Enter a parse tree produced by CellAddressParser#a1Row.
60 | CellAddressListener.prototype.enterA1Row = function(ctx) {
61 | };
62 |
63 | // Exit a parse tree produced by CellAddressParser#a1Row.
64 | CellAddressListener.prototype.exitA1Row = function(ctx) {
65 | };
66 |
67 |
68 | // Enter a parse tree produced by CellAddressParser#a1RelativeColumn.
69 | CellAddressListener.prototype.enterA1RelativeColumn = function(ctx) {
70 | };
71 |
72 | // Exit a parse tree produced by CellAddressParser#a1RelativeColumn.
73 | CellAddressListener.prototype.exitA1RelativeColumn = function(ctx) {
74 | };
75 |
76 |
77 | // Enter a parse tree produced by CellAddressParser#a1AbsoluteColumn.
78 | CellAddressListener.prototype.enterA1AbsoluteColumn = function(ctx) {
79 | };
80 |
81 | // Exit a parse tree produced by CellAddressParser#a1AbsoluteColumn.
82 | CellAddressListener.prototype.exitA1AbsoluteColumn = function(ctx) {
83 | };
84 |
85 |
86 | // Enter a parse tree produced by CellAddressParser#a1RelativeRow.
87 | CellAddressListener.prototype.enterA1RelativeRow = function(ctx) {
88 | };
89 |
90 | // Exit a parse tree produced by CellAddressParser#a1RelativeRow.
91 | CellAddressListener.prototype.exitA1RelativeRow = function(ctx) {
92 | };
93 |
94 |
95 | // Enter a parse tree produced by CellAddressParser#a1AbsoluteRow.
96 | CellAddressListener.prototype.enterA1AbsoluteRow = function(ctx) {
97 | };
98 |
99 | // Exit a parse tree produced by CellAddressParser#a1AbsoluteRow.
100 | CellAddressListener.prototype.exitA1AbsoluteRow = function(ctx) {
101 | };
102 |
103 |
104 |
105 | exports.CellAddressListener = CellAddressListener;
--------------------------------------------------------------------------------
/src/platform/formula/runtime/CellAddressVisitor.js:
--------------------------------------------------------------------------------
1 | // Generated from /Users/zhangye/Documents/code/code_yonyou/FormulaTextArea/src/platform/formula/grammar/CellAddress.g4 by ANTLR 4.8
2 | // jshint ignore: start
3 | var antlr4 = require('antlr4/index');
4 |
5 | // This class defines a complete generic visitor for a parse tree produced by CellAddressParser.
6 |
7 | function CellAddressVisitor() {
8 | antlr4.tree.ParseTreeVisitor.call(this);
9 | return this;
10 | }
11 |
12 | CellAddressVisitor.prototype = Object.create(antlr4.tree.ParseTreeVisitor.prototype);
13 | CellAddressVisitor.prototype.constructor = CellAddressVisitor;
14 |
15 | // Visit a parse tree produced by CellAddressParser#CellAddress.
16 | CellAddressVisitor.prototype.visitCellAddress = function(ctx) {
17 | return this.visitChildren(ctx);
18 | };
19 |
20 |
21 | // Visit a parse tree produced by CellAddressParser#CellRange.
22 | CellAddressVisitor.prototype.visitCellRange = function(ctx) {
23 | return this.visitChildren(ctx);
24 | };
25 |
26 |
27 | // Visit a parse tree produced by CellAddressParser#CellFloatRange.
28 | CellAddressVisitor.prototype.visitCellFloatRange = function(ctx) {
29 | return this.visitChildren(ctx);
30 | };
31 |
32 |
33 | // Visit a parse tree produced by CellAddressParser#a1Reference.
34 | CellAddressVisitor.prototype.visitA1Reference = function(ctx) {
35 | return this.visitChildren(ctx);
36 | };
37 |
38 |
39 | // Visit a parse tree produced by CellAddressParser#a1Column.
40 | CellAddressVisitor.prototype.visitA1Column = function(ctx) {
41 | return this.visitChildren(ctx);
42 | };
43 |
44 |
45 | // Visit a parse tree produced by CellAddressParser#a1Row.
46 | CellAddressVisitor.prototype.visitA1Row = function(ctx) {
47 | return this.visitChildren(ctx);
48 | };
49 |
50 |
51 | // Visit a parse tree produced by CellAddressParser#a1RelativeColumn.
52 | CellAddressVisitor.prototype.visitA1RelativeColumn = function(ctx) {
53 | return this.visitChildren(ctx);
54 | };
55 |
56 |
57 | // Visit a parse tree produced by CellAddressParser#a1AbsoluteColumn.
58 | CellAddressVisitor.prototype.visitA1AbsoluteColumn = function(ctx) {
59 | return this.visitChildren(ctx);
60 | };
61 |
62 |
63 | // Visit a parse tree produced by CellAddressParser#a1RelativeRow.
64 | CellAddressVisitor.prototype.visitA1RelativeRow = function(ctx) {
65 | return this.visitChildren(ctx);
66 | };
67 |
68 |
69 | // Visit a parse tree produced by CellAddressParser#a1AbsoluteRow.
70 | CellAddressVisitor.prototype.visitA1AbsoluteRow = function(ctx) {
71 | return this.visitChildren(ctx);
72 | };
73 |
74 |
75 |
76 | exports.CellAddressVisitor = CellAddressVisitor;
--------------------------------------------------------------------------------
/src/platform/formula/runtime/ReportFormulaLexer.tokens:
--------------------------------------------------------------------------------
1 | MultiLineComment=1
2 | SingleLineComment=2
3 | OpenParen=3
4 | CloseParen=4
5 | OpenBracket=5
6 | CloseBracket=6
7 | OpenBrace=7
8 | CloseBrace=8
9 | SemiColon=9
10 | Comma=10
11 | Assign=11
12 | QuestionMark=12
13 | Colon=13
14 | Dollar=14
15 | At=15
16 | Dot=16
17 | PlusPlus=17
18 | MinusMinus=18
19 | Plus=19
20 | Minus=20
21 | Not=21
22 | Multiply=22
23 | Divide=23
24 | Modulus=24
25 | Power=25
26 | Hashtag=26
27 | LessThan=27
28 | MoreThan=28
29 | LessThanEquals=29
30 | GreaterThanEquals=30
31 | Equals_=31
32 | NotEquals=32
33 | And=33
34 | Or=34
35 | ArrowRight=35
36 | If=36
37 | BooleanLiteral=37
38 | NullLiteral=38
39 | CellRangeLiteral=39
40 | CellFloatRangeLiteral=40
41 | CellAddressLiteral=41
42 | BasicNumberLiteral=42
43 | DecimalLiteral=43
44 | HexIntegerLiteral=44
45 | OctalIntegerLiteral=45
46 | OctalIntegerLiteral2=46
47 | BinaryIntegerLiteral=47
48 | BigHexIntegerLiteral=48
49 | BigOctalIntegerLiteral=49
50 | BigBinaryIntegerLiteral=50
51 | BigDecimalIntegerLiteral=51
52 | Identifier=52
53 | StringLiteral=53
54 | WhiteSpaces=54
55 | LineTerminator=55
56 | UnexpectedCharacter=56
57 | '('=3
58 | ')'=4
59 | '['=5
60 | ']'=6
61 | '{'=7
62 | '}'=8
63 | ';'=9
64 | ','=10
65 | '='=11
66 | '?'=12
67 | ':'=13
68 | '$'=14
69 | '@'=15
70 | '.'=16
71 | '++'=17
72 | '--'=18
73 | '+'=19
74 | '-'=20
75 | '!'=21
76 | '*'=22
77 | '/'=23
78 | '%'=24
79 | '**'=25
80 | '#'=26
81 | '<'=27
82 | '>'=28
83 | '<='=29
84 | '>='=30
85 | '=='=31
86 | '!='=32
87 | '&&'=33
88 | '||'=34
89 | '->'=35
90 | 'if'=36
91 | 'null'=38
92 |
--------------------------------------------------------------------------------
/src/platform/formula/runtime/ReportFormulaParser.tokens:
--------------------------------------------------------------------------------
1 | MultiLineComment=1
2 | SingleLineComment=2
3 | OpenParen=3
4 | CloseParen=4
5 | OpenBracket=5
6 | CloseBracket=6
7 | OpenBrace=7
8 | CloseBrace=8
9 | SemiColon=9
10 | Comma=10
11 | Assign=11
12 | QuestionMark=12
13 | Colon=13
14 | Dollar=14
15 | At=15
16 | Dot=16
17 | PlusPlus=17
18 | MinusMinus=18
19 | Plus=19
20 | Minus=20
21 | Not=21
22 | Multiply=22
23 | Divide=23
24 | Modulus=24
25 | Power=25
26 | Hashtag=26
27 | LessThan=27
28 | MoreThan=28
29 | LessThanEquals=29
30 | GreaterThanEquals=30
31 | Equals_=31
32 | NotEquals=32
33 | And=33
34 | Or=34
35 | ArrowRight=35
36 | If=36
37 | BooleanLiteral=37
38 | NullLiteral=38
39 | CellRangeLiteral=39
40 | CellFloatRangeLiteral=40
41 | CellAddressLiteral=41
42 | BasicNumberLiteral=42
43 | DecimalLiteral=43
44 | HexIntegerLiteral=44
45 | OctalIntegerLiteral=45
46 | OctalIntegerLiteral2=46
47 | BinaryIntegerLiteral=47
48 | BigHexIntegerLiteral=48
49 | BigOctalIntegerLiteral=49
50 | BigBinaryIntegerLiteral=50
51 | BigDecimalIntegerLiteral=51
52 | Identifier=52
53 | StringLiteral=53
54 | WhiteSpaces=54
55 | LineTerminator=55
56 | UnexpectedCharacter=56
57 | '('=3
58 | ')'=4
59 | '['=5
60 | ']'=6
61 | '{'=7
62 | '}'=8
63 | ';'=9
64 | ','=10
65 | '='=11
66 | '?'=12
67 | ':'=13
68 | '$'=14
69 | '@'=15
70 | '.'=16
71 | '++'=17
72 | '--'=18
73 | '+'=19
74 | '-'=20
75 | '!'=21
76 | '*'=22
77 | '/'=23
78 | '%'=24
79 | '**'=25
80 | '#'=26
81 | '<'=27
82 | '>'=28
83 | '<='=29
84 | '>='=30
85 | '=='=31
86 | '!='=32
87 | '&&'=33
88 | '||'=34
89 | '->'=35
90 | 'if'=36
91 | 'null'=38
92 |
--------------------------------------------------------------------------------
/src/platform/formula/test/DependencyBuilder.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 依赖图构建测试。
3 | */
4 |
5 | const expect = require('chai').expect;
6 | const CellDependencyBuilder = require('platform/formula/cellDependency/DependencyBuilder').DependencyBuilder;
7 | const { CyclicDependencyError, DependencyGraph } = require('platform/formula/cellDependency/DependencyGraph');
8 | const { CellAddressIdentifier, A1ReferenceIdentifier, SheetNameIdentifier,
9 | CellRangeIdentifier,
10 | AbsoluteColumnIdentifier, RelativeColumnIdentifier,
11 | AbsoluteRowIdentifier, RelativeRowIdentifier
12 | } = require('platform/formula/core/SingleFormulaAST');
13 |
14 | const { SimpleCellAddress, SimpleCellRange } = require('platform/formula/cellAddressParts/common/CellAddressParts');
15 |
16 |
17 | describe('[DependencyBuilder] 依赖图的构建', function () {
18 |
19 | function buildRelativeAddressAST(sheetName, columnText, rowLine) {
20 | return new CellAddressIdentifier(new SheetNameIdentifier(sheetName), new A1ReferenceIdentifier(
21 | new RelativeColumnIdentifier(columnText), new RelativeRowIdentifier(rowLine)
22 | ));
23 | }
24 |
25 | // 构建语法树节点
26 | function buildRelativeRangeAST(sheetName, columnText1, rowLine1, columnText2, rowLine2) {
27 | let startRef = new A1ReferenceIdentifier(
28 | new RelativeColumnIdentifier(columnText1), new RelativeRowIdentifier(rowLine1)
29 | );
30 |
31 | let endRef = new A1ReferenceIdentifier(
32 | new RelativeColumnIdentifier(columnText2), new RelativeRowIdentifier(rowLine2)
33 | );
34 |
35 | return new CellRangeIdentifier(new SheetNameIdentifier(sheetName), startRef, endRef);
36 | }
37 |
38 | function buildAbsoluteColumnAddressAST(sheetName, columnText, rowLine) {
39 | return new CellAddressIdentifier(new SheetNameIdentifier(sheetName), new A1ReferenceIdentifier(
40 | new AbsoluteColumnIdentifier(columnText), new RelativeRowIdentifier(rowLine)
41 | ));
42 | }
43 | /**
44 | * 生成一个依赖图。
45 | *
46 | * B1 = A1 + $A1 + A1 + C1
47 | */
48 | function genDepGraph(activeSheetName, existingDepGraph) {
49 | let depGraph = undefined;
50 | if (existingDepGraph) {
51 | depGraph = existingDepGraph;
52 | } else {
53 | depGraph = new DependencyGraph();
54 | }
55 |
56 | const builder = new CellDependencyBuilder(depGraph);
57 |
58 | // B1 = A1 + $A1 + A1 + C1
59 | let B1 = SimpleCellAddress.build(activeSheetName, 2, 1);
60 |
61 | let A1 = buildRelativeAddressAST(null, 'A', 1);
62 | let $A1 = buildAbsoluteColumnAddressAST(null, 'A', 1);
63 | let A1_2 = A1.clone();
64 | let C1 = buildRelativeAddressAST(null, 'C', 1);
65 |
66 | builder.addOrUpdateDependencies(B1, [A1, $A1, A1_2, C1]);
67 | return builder.getDependencyGraph();
68 | }
69 |
70 | function findIndex(simpleCellAddr, array) {
71 | return array.findIndex(function (cellData) {
72 | return cellData.cellAddress.equals(simpleCellAddr);
73 | })
74 | }
75 |
76 | it('新建依赖关系', function () {
77 | let activeSheetName = 'sheet1';
78 | let depGraph = genDepGraph(activeSheetName);
79 |
80 | let nodes = depGraph.cellList();
81 |
82 | // 包括 B1->A1, C1
83 | expect(nodes).to.have.lengthOf(3);
84 |
85 | let sorted = depGraph.sort();
86 |
87 | // 验证数组中元素的先后关系
88 | // A1 在 B1 前,C1 在 B1 签名
89 | let B1Index = findIndex(SimpleCellAddress.build(activeSheetName, 2, 1), sorted);
90 | let A1Index = findIndex(SimpleCellAddress.build(activeSheetName, 1, 1), sorted);
91 | let C1Index = findIndex(SimpleCellAddress.build(activeSheetName, 3, 1), sorted);
92 |
93 | expect(A1Index < B1Index, 'A1, B1 依赖错误');
94 | expect(C1Index < B1Index, 'C1, B1 依赖错误');
95 | })
96 |
97 | it('更新依赖关系', function () {
98 | let activeSheetName = 'sheet1';
99 | let depGraph = genDepGraph(activeSheetName);
100 |
101 | const builder = new CellDependencyBuilder(depGraph);
102 | let B1 = SimpleCellAddress.build(activeSheetName, 2, 1);
103 |
104 | let A1 = buildRelativeAddressAST(null, 'A', 1);
105 | let $A1 = buildAbsoluteColumnAddressAST(null, 'A', 1);
106 | let D2 = buildRelativeAddressAST(activeSheetName, 'D', 2);
107 |
108 | // 更新后的公式,B1 = A1 + $A1 + D2
109 | builder.addOrUpdateDependencies(B1, [A1, $A1, D2]);
110 |
111 | let nodes = depGraph.cellList();
112 | // 包括 B1->A1, D2
113 | expect(nodes).to.have.lengthOf(3);
114 |
115 | let sorted = depGraph.sort();
116 |
117 | // 验证数组中元素的先后关系
118 | // A1 在 B1 前,D2 在 B1 前
119 | let B1Index = findIndex(SimpleCellAddress.build(activeSheetName, 2, 1), sorted);
120 | let A1Index = findIndex(SimpleCellAddress.build(activeSheetName, 1, 1), sorted);
121 | let D2Index = findIndex(SimpleCellAddress.build(activeSheetName, 4, 1), sorted);
122 |
123 | expect(A1Index < B1Index, 'A1, B1 依赖错误').to.be.true;
124 | expect(D2Index < B1Index, 'D2, B1 依赖错误').to.be.true;
125 | });
126 |
127 | it('循环依赖检测', function () {
128 | // 用例描述:
129 | // Step1, B1 = A1 + $A1 + A1 + C1
130 | // Step2, C1 = B1
131 | // expect: 在 Step2 报错。
132 |
133 | let activeSheetName = 'sheet1';
134 | // Step1
135 | let depGraph = genDepGraph(activeSheetName);
136 | let builder = new CellDependencyBuilder(depGraph);
137 | let C1 = SimpleCellAddress.build(activeSheetName, 3, 1);
138 | let B1 = buildRelativeAddressAST(null, 'B', 1);
139 |
140 | expect(function () {
141 | builder.addOrUpdateDependencies(C1, [B1]);
142 | builder.check();
143 | }).to.throw(CyclicDependencyError, '单元格地址之间循环依赖');
144 | });
145 |
146 | it('删除顶点', function () {
147 | // 当用户删除某个单元格的公式时,删除依赖关系
148 | // 用例描述:
149 | // Step1, B1 = A1 + $A1 + A1 + C1
150 | // Step2, delete B1
151 | // Step3, B1 = A1 + $A1 + A1 + C1
152 | // Step4, C1 = D1 + E2
153 | // Step5, delete B1
154 |
155 | let activeSheetName = 'sheet1';
156 |
157 | // step1
158 | let depGraph = genDepGraph(activeSheetName);
159 | let builder = new CellDependencyBuilder(depGraph);
160 | let B1 = { column: 2, row: 1 };
161 | // step2
162 | builder.clear(activeSheetName, B1);
163 |
164 | let sorted = depGraph.sort();
165 | expect(sorted).to.has.lengthOf(0);
166 |
167 | // step3 B1 = A1 + $A1 + A1 + C1
168 | depGraph = genDepGraph(activeSheetName, depGraph);
169 | sorted = depGraph.sort();
170 | expect(sorted).to.has.lengthOf(3);
171 |
172 | // step4 C1 = D1 + E2
173 | builder = new CellDependencyBuilder(depGraph);
174 | let C1 = SimpleCellAddress.build(activeSheetName, 3, 1);
175 | let D1 = buildRelativeAddressAST(null, 'D', 1);
176 | let E2 = buildRelativeAddressAST(null, 'E', 2);
177 | builder.addOrUpdateDependencies(C1, [D1, E2]);
178 |
179 | // 此时,预期的依赖关系图是:
180 | // ┌──────┐
181 | // ┌─────────▶│ A1 │
182 | // │ └──────┘
183 | // ┌──────┐ ┌──────┐
184 | // │ B1 │ ┌────────────▶│ D1 │
185 | // └──────┘ │ └──────┘
186 | // │ ┌──────┐
187 | // └─────────▶│ C1 │
188 | // └──────┘
189 | // │ ┌──────┐
190 | // └────────────▶│ E1 │
191 | // └──────┘
192 |
193 | sorted = depGraph.sort();
194 | expect(sorted).to.has.lengthOf(5);
195 |
196 | let D1Index = findIndex(SimpleCellAddress.build(activeSheetName, 4, 1), sorted);
197 | let E1Index = findIndex(SimpleCellAddress.build(activeSheetName, 5, 1), sorted);
198 | let C1Index = findIndex(SimpleCellAddress.build(activeSheetName, 3, 1), sorted);
199 | let A1Index = findIndex(SimpleCellAddress.build(activeSheetName, 1, 1), sorted);
200 | let B1Index = findIndex(SimpleCellAddress.build(activeSheetName, 2, 1), sorted);
201 |
202 | expect(B1Index > A1Index).to.be.true;
203 | expect(B1Index > C1Index).to.be.true;
204 | expect(C1Index > D1Index).to.be.true;
205 | expect(C1Index > E1Index).to.be.true;
206 | });
207 |
208 | it('删除工作表', function() {
209 | // 测试用例描述:
210 | //
211 | expect.fail();
212 | });
213 |
214 | it('单元格范围依赖', function () {
215 | // 当设置单元格范围内的某个单元格公式时,可以建立单元格范围到该单元格地址的依赖
216 | // 用例描述:
217 | // Step1, A1 = SUM(B2:C3),形成 2 个顶点 A1, B2:C3
218 | // Step2, B2 = D5,形成 2 个顶点 B2,D5;
219 | // 在 Step2 完成时,形成一条从 'B2:C3' 到 'B2' 的边
220 | let activeSheetName = 'sheet1';
221 | let A1 = SimpleCellAddress.build(activeSheetName, 'A', 1);
222 | let B2_C3 = buildRelativeRangeAST(activeSheetName, 'B', 2, 'C', 3);
223 |
224 | let depGraph = new DependencyGraph();
225 | const builder = new CellDependencyBuilder(depGraph);
226 | builder.addOrUpdateDependencies(A1, [B2_C3]);
227 | let sorted = depGraph.sort();
228 | expect(sorted).to.has.lengthOf(2);
229 |
230 | let B2 = SimpleCellAddress.build(activeSheetName, 'B', 2);
231 | let D5 = buildRelativeAddressAST(activeSheetName, 'D', 5);
232 | builder.addOrUpdateDependencies(B2, [D5]);
233 |
234 | // 预期的依赖效果:
235 | // ┌────┐ ┌───────┐
236 | // │ A1 │──▶│ B2:C3 │
237 | // └────┘ └───────┘
238 | // │
239 | // ▼
240 | // ┌───────┐ ┌────┐
241 | // │ B2 │──▶│ D5 │
242 | // └───────┘ └────┘
243 |
244 | // 越是优先计算的,索引越小。
245 | sorted = depGraph.sort();
246 | expect(sorted).to.has.lengthOf(4);
247 |
248 | let B2_C3Index = findIndex(SimpleCellRange.build(activeSheetName, 'B', 2, 'C', 3), sorted);
249 | let B2Index = findIndex(SimpleCellAddress.build(activeSheetName, 'B', 2), sorted);
250 |
251 | expect(B2_C3Index > B2Index).to.be.true;
252 | });
253 | })
--------------------------------------------------------------------------------
/src/platform/formula/test/DependencyTransformer.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 变更单元格结构测试。
3 | */
4 | const expect = require('chai').expect;
5 | const { SimpleCellAddress, SimpleCellRange } = require('platform/formula/cellAddressParts/common/CellAddressParts');
6 | const { DependencyTransformer } = require('platform/formula/cellDependency/DependencyTransformer');
7 | const { DependencyBuilder } = require('platform/formula/cellDependency/DependencyBuilder');
8 | const { DependencyGraph, CellData } = require('platform/formula/cellDependency/DependencyGraph');
9 |
10 | const { buildRelativeRangeAST } = require('platform/formula/test/TestUtilsASTNodeBuilder');
11 |
12 | describe('[DependencyTransformer] 变更单元格结构', function () {
13 | describe('单元格地址', function () {
14 | beforeEach(function () {
15 |
16 | });
17 |
18 | afterEach(function () {
19 |
20 | });
21 |
22 |
23 | it('插入行', function () {
24 | expect.fail();
25 | })
26 |
27 | it('删除行', function () {
28 | expect.fail();
29 | })
30 |
31 | it('插入列', function () {
32 | expect.fail();
33 | })
34 |
35 | it('删除列', function () {
36 | expect.fail();
37 | })
38 | })
39 |
40 | describe('单元格范围', function () {
41 | let depGraph = undefined;
42 | let builder = undefined;
43 | let transformer = undefined;
44 | beforeEach(function () {
45 | depGraph = new DependencyGraph();
46 | builder = new DependencyBuilder(depGraph);
47 | transformer = new DependencyTransformer(depGraph);
48 | });
49 |
50 | afterEach(function () {
51 |
52 | });
53 |
54 | it('插入行', function () {
55 | expect.fail();
56 |
57 | // 测试用例描述:
58 | // 1) C2 = SUM(A1:B2)
59 | // 2) 选择第 1 行,向上增加 1 行
60 | // 3) 期待 C3 = SUM(A2:B3)
61 |
62 |
63 | });
64 |
65 | it('删除行', function () {
66 |
67 | // 测试用例描述:
68 | // 1) C2 = SUM(A1:B3);
69 | // 2) 选择第 3 行,删除 1 行
70 | // 3) 预期:C2 = SUM(A1:B2),即依赖图中包括两个节点,C2 节点、A1:B2 节点。
71 | const activeSheetName = 'sheet1';
72 |
73 | // 准备数据
74 | let C2 = SimpleCellAddress.build(activeSheetName, 3, 2);
75 | let astNode_A1_B3 = buildRelativeRangeAST(activeSheetName, 'A', 1, 'B', 3);
76 | builder.addOrUpdateDependencies(C2, [astNode_A1_B3]);
77 | transformer.removeRows(activeSheetName, 3, 1);
78 |
79 | let range_A1_B2 = SimpleCellRange.build(activeSheetName, 'A', 1, 'B', 2);
80 | // 验证依赖图的结构, depGraph
81 | let node = depGraph.lookupCell(new CellData(range_A1_B2));
82 | expect(node).to.not.be.undefined;
83 | });
84 |
85 | it('插入列', function () {
86 | expect.fail();
87 | });
88 |
89 | it('删除列', function () {
90 | expect.fail();
91 | });
92 | })
93 | })
--------------------------------------------------------------------------------
/src/platform/formula/test/Evaluator.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 测试公式求值过程。
3 | * 利用语法树 AST 求值。
4 | *
5 | * 本测试用例只验证语法上合法的公式,可以验证在求值过程中的错误。
6 | *
7 | * 计算错误时返回的计算结果有:
8 | * https://support.microsoft.com/zh-cn/office/检测公式中的错误-3a8acca5-1d61-4702-80e0-99a36a2822c1
9 | *
10 | */
11 | const expect = require('chai').expect;
12 | const Core = require('platform/formula/core/SingleFormulaCore').INSTANCE;
13 | const SingleFormulaAST = require('platform/formula/core/SingleFormulaAST').SingleFormulaAST;
14 | const Evaluator = require('platform/formula/cellEvaluation/Evaluator').Evaluator;
15 |
16 | describe('特殊常量求值', function() {
17 | it('@a.b 语法', function() {
18 | let cellValueProvider = {
19 | fetchDataObject: function(object, property) {
20 | if(object !== 'Entity') {
21 | return 0;
22 | }
23 | if(property === 'prop'){
24 | return 5.0;
25 | }
26 |
27 | if(property === 'percent') {
28 | return 0.03;
29 | }
30 | }
31 | };
32 |
33 | let input = '= @Entity.prop * @Entity.percent';
34 | let ast = new SingleFormulaAST(Core.parse(input));
35 | let evaluator = new Evaluator(null, cellValueProvider);
36 |
37 | let ret = evaluator.evaluateAST(ast);
38 | let resultExpected = 5.0 * 0.03;
39 | expect(ret).to.equal(resultExpected);
40 |
41 | })
42 | })
43 |
44 | describe('公式求值', function () {
45 | let cellValueProvider = {
46 | getCellValue: function (cellAddress) {
47 |
48 | },
49 | setCellValue: function (cellAddress) {
50 |
51 | }
52 | }
53 |
54 | it('公式全部重算', function () {
55 | // 测试用例描述:
56 | // Step1. 设置公式 A2 = A1
57 | // Step2, set A1 = 1;
58 | // 全部重新计算
59 |
60 |
61 | expect.fail();
62 | })
63 |
64 | it('带括号的公式', function () {
65 | let cellValueProvider = {
66 | data:{
67 | 'c-5': 1,
68 | 'c-6': 2,
69 | 'c-7': -1,
70 | 'c-8': 0.5
71 | },
72 | getCellValue: function (cellAddress) {
73 | // E6 = 1, F6 = 2, G6 = -1, H6 = 0.5
74 | // c:5,r=6; c:6,r=6; c:7,r=6; c:8,r=6
75 | return this.data[`c-${cellAddress.column}`];
76 | },
77 | setCellValue: function (cellAddress) {
78 |
79 | }
80 | }
81 |
82 | let input = '=(E6*F6-G6)*H6';
83 | let ast = new SingleFormulaAST(Core.parse(input));
84 | let evaluator = new Evaluator(null, cellValueProvider);
85 |
86 | let ret = evaluator.evaluateAST(ast);
87 | let resultExpected = 1.5;
88 | expect(ret).to.equal(resultExpected);
89 | });
90 |
91 | describe('[常量+运算符]', function () {
92 | function assertEvaluation(input, expected) {
93 | let ast = new SingleFormulaAST(Core.parse(input));
94 | let evaluator = new Evaluator(null, cellValueProvider);
95 |
96 | let ret = evaluator.evaluateAST(ast);
97 | if (Number.isNaN(expected)) {
98 | expect(ret).to.be.NaN;
99 | } else {
100 | expect(ret).to.equal(expected);
101 | }
102 |
103 | }
104 |
105 | const datas = [
106 | ['-1', -1],
107 | ['+0', 0],
108 | ['1 + 1', 2],
109 | ['0 - 1', -1],
110 | ['0.0 * 1', 0],
111 | [' 1 / 2.5', 1 / 2.5],
112 | ['2 * 50%', 1],
113 | ['- - 1', 1],
114 | ['10 % 0', NaN],
115 | ['2 ** 0', 1],
116 | ['1 < 0', false],
117 | ['0 > 1', false],
118 | ['\'1\' + 1', '11'], // 边界输入
119 | ];
120 |
121 |
122 |
123 | datas.forEach(function (item) {
124 | it(item[0], function () {
125 | assertEvaluation(item[0], item[1]);
126 | })
127 | });
128 |
129 | // 非法输入,识别后,保留原始内容为文本存储。
130 | it('非法输入,保留原文本', function () {
131 | const datas = [
132 | //['1 +', '1 +'], // 公式非法
133 | //['1 /', '1 /'], // 公式非法
134 | ['1/0', '1/0'] // 公式合法,求值出错
135 | ];
136 |
137 | // 放入到依赖图中的,都符合语法,但是计算可能会出错。
138 | datas.forEach(function (item) {
139 |
140 | })
141 |
142 | });
143 | })
144 | })
--------------------------------------------------------------------------------
/src/platform/formula/test/FormulaParser.test.js:
--------------------------------------------------------------------------------
1 | const antlr4 = require('antlr4');
2 | const FormulaLexer = require('../runtime/ReportFormulaLexer').ReportFormulaLexer;
3 | const FormulaParser = require('../runtime/ReportFormulaParser').ReportFormulaParser;
4 | const EditorTokensVisitor = require('../core/EditorTokensVisitor').EditorTokensVisitor;
5 |
6 | describe('公式语法解析', function() {
7 | it('收集语法 token', function() {
8 | let input = 'SUM(A1)';
9 |
10 | const errorStartingColumns = [];
11 | const EOF = -1;
12 |
13 | class ErrorTokenListener extends antlr4.error.ErrorListener {
14 | syntaxError(recognizer, offendingSymbol, line, column, msg, e) {
15 | errorStartingColumns.push(column)
16 | }
17 | }
18 |
19 | const chars = new antlr4.InputStream(input);
20 | const lexer = new FormulaLexer(chars);
21 |
22 | lexer.removeErrorListeners();
23 | lexer.addErrorListener(new ErrorTokenListener());
24 |
25 | const tokens = new antlr4.CommonTokenStream(lexer);
26 | const parser = new FormulaParser(tokens);
27 |
28 | parser.removeErrorListeners(); // 移除默认的 ConsoleErrorListener
29 | parser.addErrorListener(new ErrorTokenListener());
30 |
31 | var tree = parser.formulaExpr(); // 启动公式解析,遇到错误会触发 ErrorListener。
32 |
33 | var tokenContainer = [];
34 | tree.accept(new EditorTokensVisitor(tokenContainer));
35 |
36 | let tokenList = [];
37 | do {
38 | let token = lexer.nextToken();
39 | if (!token || token.type == EOF) {
40 | break;
41 | }
42 |
43 | let tokenTypeName = lexer.symbolicNames[token.type];
44 | tokenList.push({ tokenTypeName, column: token.column });
45 | } while (true);
46 |
47 | errorStartingColumns.forEach(function (errTokenColumn) {
48 | tokenList.push({ tokenTypeName: 'error', column: errTokenColumn });
49 | });
50 | });
51 | });
--------------------------------------------------------------------------------
/src/platform/formula/test/SingleFormulaCore.test.js:
--------------------------------------------------------------------------------
1 | const expect = require('chai').expect;
2 | const sinon = require('sinon');
3 | const FormulaCore = require('platform/formula/core/SingleFormulaCore').SingleFormulaCore;
4 |
5 | // 验证语法的识别是否准确
6 | function assertRecognitionList(core, list, grammarRule) {
7 | if (!list || list.length === 0) {
8 | return;
9 | }
10 | list.forEach(function (item) {
11 | const parser = core.buildParser(item.rawValue);
12 | const parseTree = parser[grammarRule]();
13 |
14 | var result = parseTree.getText();
15 | expect(result).to.equal(item.expected, '语法识别错误');
16 | })
17 | }
18 |
19 | describe('一般常量', function () {
20 | let core;
21 | beforeEach(function () {
22 | core = new FormulaCore();
23 | })
24 |
25 |
26 | /**
27 | * 处理识别错误
28 | */
29 | function decorateCoreWithErrHandler(core, rawInput, ruleName) {
30 | var handleEvaluateErrorStub = sinon.spy(function (e) { });
31 |
32 | var handleParseErrorStub = sinon.spy(function (input, line, column, message) {
33 | expect(input).to.equal(rawInput);
34 | expect(message, '无法识别的符号')
35 | });
36 |
37 | // 测试错误的符号是否可以正确识别,并提供恰当充分的出错信息
38 | core.setErrorHandler({
39 | handleEvaluateError: handleEvaluateErrorStub,
40 | handleParseError: handleParseErrorStub
41 | });
42 | const parser = core.buildParser(rawInput);
43 |
44 | if (ruleName) {
45 | parser[ruleName]();
46 | } else {
47 |
48 | parser.formulaExpr();
49 | }
50 |
51 | //验证语法错误必须识别
52 | expect(handleParseErrorStub.called).to.be.true;
53 | }
54 |
55 | describe('字符串', function () {
56 | it('字符串:识别', function () {
57 | assertRecognitionList(core, [
58 | {
59 | rawValue: "\"OK\"",
60 | expected: "\"OK\""
61 | },
62 | {
63 | rawValue: "\"\"",
64 | expected: "\"\""
65 | },
66 | {
67 | rawValue: "\"\\\"\"",
68 | expected: "\"\\\"\""
69 | }
70 | ], 'literal');
71 | });
72 |
73 | describe('字符串:识别:错误处理', function () {
74 | it('"', function () {
75 | decorateCoreWithErrHandler(core, '"');
76 | });
77 |
78 | it('\'', function () {
79 | decorateCoreWithErrHandler(core, '\'');
80 | });
81 |
82 | it('#', function () {
83 | decorateCoreWithErrHandler(core, '#');
84 | });
85 | });
86 | })
87 |
88 | describe('布尔', function () {
89 | it('识别:布尔', function () {
90 | assertRecognitionList(core, [
91 | {
92 | rawValue: "true",
93 | expected: "true"
94 | },
95 | {
96 | rawValue: "false",
97 | expected: "false"
98 | }
99 | ], 'literal');
100 | });
101 |
102 | it('TRUE/True', function () {
103 | decorateCoreWithErrHandler(core, 'TRUE', 'literal');
104 | decorateCoreWithErrHandler(core, 'True', 'literal');
105 | // assertRecognitionList(core, [
106 | // {
107 | // rawValue: 'TRUE',
108 | // expected: 'TRUE'
109 | // },
110 | // {
111 | // rawValue: 'True',
112 | // expected: 'True'
113 | // }
114 | // ], 'literal');
115 | });
116 |
117 |
118 | it('FALSE/False', function () {
119 | assertRecognitionList(core, [
120 | {
121 | rawValue: 'FALSE',
122 | expected: 'FALSE'
123 | },
124 | {
125 | rawValue: 'False',
126 | expected: 'False'
127 | }
128 | ], 'literal');
129 | });
130 | })
131 |
132 | describe('数字', function () {
133 | it('识别:数字', function () {
134 | assertRecognitionList(core, [
135 | {
136 | rawValue: '1',
137 | expected: '1'
138 | },
139 | {
140 | rawValue: '-1',
141 | expected: '-1'
142 | },
143 | {
144 | rawValue: '0',
145 | expected: '0'
146 | },
147 | {
148 | rawValue: '1.2',
149 | expected: '1.2'
150 | },
151 | {
152 | rawValue: '0.1',
153 | expected: '0.1'
154 | },
155 | {
156 | rawValue: '-0.1',
157 | expected: '-0.1'
158 | },
159 | {
160 | rawValue: '13%',
161 | expected: '13%'
162 | },
163 | {
164 | rawValue: '+1',
165 | expected: '+1'
166 | },
167 | {
168 | rawValue: '+0',
169 | expected: '+0'
170 | },
171 | {
172 | rawValue: '-0',
173 | expected: '-0'
174 | },
175 | {
176 | rawValue: '0.0',
177 | expected: '0.0'
178 | },
179 | {
180 | rawValue: '0.00',
181 | expected: '0.00'
182 | },
183 | {
184 | rawValue: '100%',
185 | expected: '100%'
186 | }
187 | ], 'singleExpression');
188 | });
189 |
190 | describe('数字:错误处理', function () {
191 | it('1-', function () {
192 | decorateCoreWithErrHandler(core, '1-');
193 | });
194 |
195 | it('1a', function () {
196 | decorateCoreWithErrHandler(core, '1a');
197 | });
198 |
199 | it('a.0', function () {
200 | decorateCoreWithErrHandler(core, 'a.0');
201 | });
202 |
203 | // 跳过该测试。"." 运算符用于“成员表达式”
204 | xit('0.a', function () {
205 | decorateCoreWithErrHandler(core, '0.a');
206 | });
207 | })
208 | })
209 |
210 | it('识别:空', function () {
211 | assertRecognitionList(core, [
212 | {
213 | rawValue: 'null',
214 | expected: 'null'
215 | }
216 | ], 'literal');
217 | });
218 |
219 | });
220 |
221 | describe('识别变量(SingleFormulaCore.test.js)', function () {
222 | let core;
223 | beforeEach(function () {
224 | core = new FormulaCore();
225 | })
226 | it('相对单元格地址', function () {
227 | assertRecognitionList(core, [
228 | {
229 | rawValue: 'A1',
230 | expected: 'A1'
231 | },
232 | {
233 | rawValue: 'B1',
234 | expected: 'B1'
235 | },
236 | {
237 | rawValue: 'A1000',
238 | expected: 'A1000'
239 | },
240 | {
241 | rawValue: 'Z1000',
242 | expected: 'Z1000'
243 | },
244 | {
245 | rawValue: 'Sheet1!A1',
246 | expected: 'Sheet1!A1'
247 | }
248 | ], 'identifier');
249 | });
250 |
251 | it('绝对单元格地址', function () {
252 | assertRecognitionList(core, [
253 | {
254 | rawValue: '$A$1',
255 | expected: '$A$1'
256 | }
257 | ], 'identifier');
258 | });
259 |
260 | it('混合单元格地址', function () {
261 | assertRecognitionList(core, [
262 | {
263 | rawValue: 'A$1',
264 | expected: 'A$1'
265 | },
266 | {
267 | rawValue: '$A1',
268 | expected: '$A1'
269 | },
270 |
271 | {
272 | rawValue: 'Sheet1!A$1',
273 | expected: 'Sheet1!A$1'
274 | }
275 | ], 'identifier');
276 | });
277 |
278 | it('单元格范围', function () {
279 | assertRecognitionList(core, [
280 | {
281 | rawValue: 'A1:B1',
282 | expected: 'A1:B1'
283 | },
284 | {
285 | rawValue: 'Sheet1!A1:B1',
286 | expected: 'Sheet1!A1:B1'
287 | },
288 | {
289 | rawValue: 'Sheet1!A$1:$B1',
290 | expected: 'Sheet1!A$1:$B1'
291 | },
292 | {
293 | rawValue: 'Sheet1!$A$1:$B$1',
294 | expected: 'Sheet1!$A$1:$B$1'
295 | },
296 | ], 'identifier');
297 | });
298 |
299 | it('浮动单元格范围', function(){
300 | assertRecognitionList(core, [
301 | {
302 | rawValue: 'A1->B1',
303 | expected: 'A1->B1'
304 | },
305 | {
306 | rawValue: 'Sheet1!A1->B1',
307 | expected: 'Sheet1!A1->B1'
308 | },
309 | {
310 | rawValue: 'Sheet1!A$1->$B1',
311 | expected: 'Sheet1!A$1->$B1'
312 | },
313 | {
314 | rawValue: 'Sheet1!$A$1->$B$1',
315 | expected: 'Sheet1!$A$1->$B$1'
316 | },
317 | ], 'identifier');
318 | })
319 |
320 | describe('报表项', function () {
321 |
322 | });
323 |
324 | describe('普通变量', function () {
325 |
326 | });
327 | });
328 |
--------------------------------------------------------------------------------
/src/platform/formula/test/TestUtilsASTNodeBuilder.js:
--------------------------------------------------------------------------------
1 | const { SheetNameIdentifier,
2 | CellRangeIdentifier, CellAddressIdentifier,
3 | A1ReferenceIdentifier,
4 | AbsoluteColumnIdentifier, RelativeColumnIdentifier,
5 | AbsoluteRowIdentifier, RelativeRowIdentifier
6 | } = require('platform/formula/core/SingleFormulaAST');
7 |
8 | // 构建语法树节点
9 | exports.buildRelativeRangeAST = function buildRelativeRangeAST(sheetName, columnText1, rowLine1, columnText2, rowLine2) {
10 | let startRef = new A1ReferenceIdentifier(
11 | new RelativeColumnIdentifier(columnText1), new RelativeRowIdentifier(rowLine1)
12 | );
13 |
14 | let endRef = new A1ReferenceIdentifier(
15 | new RelativeColumnIdentifier(columnText2), new RelativeRowIdentifier(rowLine2)
16 | );
17 |
18 | return new CellRangeIdentifier(new SheetNameIdentifier(sheetName), startRef, endRef);
19 | }
20 |
--------------------------------------------------------------------------------
/src/platform/registry/common/platform.js:
--------------------------------------------------------------------------------
1 | const Types = require('../../../base/common/types');
2 | const Assert = require('../../../base/common/assert');
3 |
4 | class IRegistry {
5 | /**
6 | * Adds the extension functions and properties defined by data to the
7 | * platform. The provided id must be unique.
8 | * @param id a unique identifier
9 | * @param data a contribution
10 | */
11 | add(id, data){}
12 |
13 | /**
14 | * Returns true iff there is an extension with the provided id.
15 | * @param id an extension identifier
16 | */
17 | knows(id){}
18 |
19 | /**
20 | * Returns the extension functions and properties defined by the specified key or null.
21 | * @param id an extension identifier
22 | */
23 | as(id){}
24 | }
25 |
26 | class RegistryImpl extends IRegistry{
27 | constructor() {
28 | this.data = {};
29 | }
30 |
31 | add(id, data){
32 | Assert.ok(Types.isString(id));
33 | Assert.ok(Types.isObject(data));
34 | Assert.ok(!this.data.hasOwnProperty(id), '该 id 已经被其他扩展占用');
35 |
36 | this.data[id] = data;
37 | }
38 |
39 | knows(id){
40 | return this.data.hasOwnProperty(id);
41 | }
42 |
43 | as(id){
44 | return this.data[id] || null;
45 | }
46 | }
47 |
48 | exports.Registry = new RegistryImpl();
--------------------------------------------------------------------------------
/src/platform/theme/colorRegistry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 管理全部的颜色。
3 | * 用于支持公式单元格地址标识符颜色、表格中的单元格颜色。
4 | */
5 |
6 | /**
7 | * 颜色查找表
8 | */
9 | const ColorTable = [
10 |
11 | ];
12 |
13 | exports.ColorTable = ColorTable;
--------------------------------------------------------------------------------
/src/workbench/ CellValueProvider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 可以访问表格中的全部数据,包括所有 Sheet 下的全部单元格数据。
3 | */
4 | class CellValueProvider {
5 | constructor() {
6 | this.workingSheet = null;
7 | }
8 |
9 | /**
10 | * 公式引擎识别后的单元格地址字面量
11 | */
12 | getCellValue(cellAddressString) {
13 |
14 | }
15 | }
--------------------------------------------------------------------------------
/src/workbench/monaco-editor/colorsProvider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 生成不同的颜色
3 | */
4 | const chroma = require('chroma-js');
5 | const colorTest = require('base/color/colorTest').ColorTest;
6 | const ColorTable = require('platform/theme/colorRegistry').ColorTable;
7 |
8 | class ColorsProvider {
9 | constructor() {
10 | this.cellAddress2color = {};
11 |
12 | // CellAddress Style
13 | var styleDomNode = document.createElement('style');
14 | styleDomNode.type = 'text/css';
15 | styleDomNode.media = 'screen';
16 | styleDomNode.className = 'formula-text-colors';
17 | // styleSheet.addRule('.', 'color: ' + formulaSDK.ColorsProvider.generate().toString());
18 |
19 | document.head.appendChild(styleDomNode);
20 |
21 | this._styleSheet = styleDomNode.sheet;
22 | }
23 | }
24 |
25 | ColorsProvider.prototype.appendToCSS = function (index, hexColor) {
26 | this._styleSheet.insertRule('.ftc' + index + '{ color: ' + hexColor + ' !important; }', this._styleSheet.cssRules.length);
27 | }
28 |
29 | ColorsProvider.prototype.deleteCSS = function (index) {
30 | this._styleSheet.deleteRule(index);
31 | }
32 |
33 | // 已经使用该颜色,返回 true
34 | ColorsProvider.prototype.hasAppliedColor = function (index) {
35 | return this.cellAddress2color.hasOwnProperty(index);
36 | }
37 |
38 | ColorsProvider.prototype.getColor = function (index) {
39 | return this.cellAddress2color[index];
40 | }
41 |
42 | ColorsProvider.prototype.setColor = function (index, color) {
43 | this.cellAddress2color[index] = color;
44 |
45 | // 设置 CSS 样式
46 | this.appendToCSS(index, color.hex());
47 |
48 | return color;
49 | }
50 |
51 | ColorsProvider.prototype.generate = function () {
52 | let color = chroma.random();
53 | let channels = color.rgb();
54 | let contrast = colorTest(1, 1, 1, channels[0], channels[1], channels[2]);
55 | contrast = Math.abs(contrast);
56 |
57 | if (contrast > 110) {
58 | color = color.darken();
59 | }
60 |
61 | if (contrast < 50) {
62 | color = color.darken();
63 | }
64 |
65 | return color;
66 | }
67 |
68 | ColorsProvider.prototype.pickOrCreateColor = function (index) {
69 | if (!this.hasAppliedColor(index)) {
70 | this.setColor(index, this.generate());
71 | }
72 | return this.getColor(index);
73 | }
74 |
75 | ColorsProvider.INSTANCE = new ColorsProvider();
76 |
77 | exports.ColorsProvider = ColorsProvider;
--------------------------------------------------------------------------------
/src/workbench/monaco-editor/monaco.contribution.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 编辑器插件的入口文件
3 | */
4 | const languageFeatures = require('./languageFeatures');
5 |
6 |
7 | function setupEditor(monaco, editor) {
8 | languageFeatures.CellAddressTokensDecorator.registerDecorator(editor, monaco);
9 | }
10 | function setupLanguage(monaco, langId) {
11 | // 配置基本的编辑功能
12 | monaco.languages.setLanguageConfiguration(langId, {
13 | brackets: [
14 | ['{', '}'],
15 | ['[', ']'],
16 | ['(', ')']
17 | ],
18 | autoClosingPairs: [{
19 | open: '{',
20 | close: '}'
21 | },
22 | {
23 | open: '[',
24 | close: ']'
25 | },
26 | {
27 | open: '(',
28 | close: ')'
29 | },
30 | {
31 | open: '"',
32 | close: '"',
33 | notIn: ['string']
34 | },
35 | {
36 | open: '\'',
37 | close: '\'',
38 | notIn: ['string', 'comment']
39 | },
40 | {
41 | open: "/**",
42 | close: " */",
43 | notIn: ["string"]
44 | }
45 | ]
46 | });
47 |
48 | monaco.languages.setTokensProvider(langId, new languageFeatures.FormulaTokensProvider());
49 |
50 | monaco.languages.registerCompletionItemProvider(langId, new languageFeatures.FormulaSuggestionsProvider());
51 | // monaco.languages.registerSignatureHelpProvider(langId, new languageFeatures.SignatureHelpAdapter(worker));
52 | monaco.languages.registerSignatureHelpProvider(langId, new languageFeatures.SignatureHelpProvider());
53 | monaco.languages.registerHoverProvider(langId, new languageFeatures.QuickInfoAdapter());
54 |
55 | new languageFeatures.DiagnosticsAdapter(langId);
56 | }
57 |
58 | function init(monaco) {
59 | monaco.languages.onLanguage('lang-formula', () => {
60 | return setupLanguage(monaco, 'lang-formula');
61 | });
62 |
63 | // 需要在 setupLanguage 函数外。
64 | monaco.languages.register({
65 | id: 'lang-formula'
66 | });
67 | }
68 |
69 | function initEditor(monaco, editor) {
70 | setupEditor(monaco, editor);
71 | }
72 |
73 | exports.init = init;
74 | exports.initEditor = initEditor;
--------------------------------------------------------------------------------
/test/.mocharc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | spec: [
3 | 'test/**/*.test.js',
4 | 'src/**/*.test.js',
5 | ]
6 | }
--------------------------------------------------------------------------------
/test/event-loop-example.js:
--------------------------------------------------------------------------------
1 |
2 | // 浏览器渲染管线伪代码
3 | //
4 | while(true) {
5 | queue = getNextQueue();
6 | task = queue.pop();
7 | execute(task);
8 |
9 | while(microtaskQueue.hasTasks()) {
10 | doMicrotask();
11 | }
12 |
13 | if(isRepaintTime()) {
14 | animationTasks = animationQueue.copyTasks();
15 | for(task in animationTasks) {
16 | doAnimationTask(task);
17 | }
18 |
19 | repaint();
20 | }
21 | }
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 公式编辑器测试页面
8 |
9 |
10 |
11 | 公式编辑器(IntelliSense)
12 | 样例:
13 | =IF(C7<E7,MIN(ABS(E7-C7),D7),0)
14 | =MIN(ROUND($G$5*E14,2), D14)
15 | =MAX(C33-D33,0)
16 | =ROUNDUP(MIN(C19*0.6,A101010一般企业收入明细表!C4*0.005),2)
17 | =IF(A105060广告费和业务宣传费跨年度纳税调整明细表!C15>0, 0, ABS(A105060广告费和业务宣传费跨年度纳税调整明细表!C15))
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/test/input/formula1.txt:
--------------------------------------------------------------------------------
1 | Sheet!A1
--------------------------------------------------------------------------------
/test/require-alias/loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 添加别名功能
3 | */
4 |
5 | const Module = require('module');
6 | const path = require('path');
7 | const mochaMain = require('mocha/lib/cli').main;
8 |
9 | const nodeRequire = Module.prototype.require;
10 |
11 | const resolve = {
12 | alias: {
13 | base: path.resolve(__dirname, '../../src/base/'),
14 | platform: path.resolve(__dirname, '../../src/platform/'),
15 | workbench: path.resolve(__dirname, '../../src/workbench/')
16 | }
17 | }
18 |
19 | function findAlias(id, alias) {
20 | let keys = Object.keys(alias);
21 | return keys.find(function(key) {
22 | return id.startsWith(key);
23 | });
24 | }
25 |
26 | function resolveId(id, aliasMap) {
27 | let aliasKey = findAlias(id, aliasMap);
28 | if(!aliasKey) {
29 | return id;
30 | }
31 |
32 | return id.replace(aliasKey, aliasMap[aliasKey]);
33 | }
34 |
35 | function requireHooks(id) {
36 | // console.log('hooks ' + id);
37 | let aliasId = resolveId(id, resolve.alias);
38 | return nodeRequire.apply(this, [aliasId]);
39 | }
40 |
41 |
42 | Module.prototype.require = requireHooks;
43 |
44 | mochaMain();
--------------------------------------------------------------------------------