@trebco/treb 37.0.0 → 37.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +119 -0
- package/build/treb-base-types/src/api_types.d.ts +11 -0
- package/build/treb-base-types/src/api_types.js +22 -0
- package/build/treb-base-types/src/api_types.js.map +1 -0
- package/build/treb-base-types/src/area-utils.d.ts +9 -0
- package/build/treb-base-types/src/area-utils.js +50 -0
- package/build/treb-base-types/src/area-utils.js.map +1 -0
- package/build/treb-base-types/src/area.d.ts +182 -0
- package/build/treb-base-types/src/area.js +715 -0
- package/build/treb-base-types/src/area.js.map +1 -0
- package/build/treb-base-types/src/basic_types.d.ts +20 -0
- package/build/treb-base-types/src/basic_types.js +22 -0
- package/build/treb-base-types/src/basic_types.js.map +1 -0
- package/build/treb-base-types/src/cell.d.ts +167 -0
- package/build/treb-base-types/src/cell.js +432 -0
- package/build/treb-base-types/src/cell.js.map +1 -0
- package/build/treb-base-types/src/cells.d.ts +251 -0
- package/build/treb-base-types/src/cells.js +1136 -0
- package/build/treb-base-types/src/cells.js.map +1 -0
- package/build/treb-base-types/src/color.d.ts +35 -0
- package/build/treb-base-types/src/color.js +162 -0
- package/build/treb-base-types/src/color.js.map +1 -0
- package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
- package/build/treb-base-types/src/dom-utilities.js +144 -0
- package/build/treb-base-types/src/dom-utilities.js.map +1 -0
- package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
- package/build/treb-base-types/src/evaluate-options.js +22 -0
- package/build/treb-base-types/src/evaluate-options.js.map +1 -0
- package/build/treb-base-types/src/font-stack.d.ts +37 -0
- package/build/treb-base-types/src/font-stack.js +93 -0
- package/build/treb-base-types/src/font-stack.js.map +1 -0
- package/build/treb-base-types/src/gradient.d.ts +18 -0
- package/build/treb-base-types/src/gradient.js +86 -0
- package/build/treb-base-types/src/gradient.js.map +1 -0
- package/build/treb-base-types/src/import.d.ts +48 -0
- package/build/treb-base-types/src/import.js +22 -0
- package/build/treb-base-types/src/import.js.map +1 -0
- package/build/treb-base-types/src/index-standalone.d.ts +6 -0
- package/build/treb-base-types/src/index-standalone.js +27 -0
- package/build/treb-base-types/src/index-standalone.js.map +1 -0
- package/build/treb-base-types/src/index.d.ts +22 -0
- package/build/treb-base-types/src/index.js +45 -0
- package/build/treb-base-types/src/index.js.map +1 -0
- package/build/treb-base-types/src/layout.d.ts +22 -0
- package/build/treb-base-types/src/layout.js +22 -0
- package/build/treb-base-types/src/layout.js.map +1 -0
- package/build/treb-base-types/src/localization.d.ts +37 -0
- package/build/treb-base-types/src/localization.js +157 -0
- package/build/treb-base-types/src/localization.js.map +1 -0
- package/build/treb-base-types/src/rectangle.d.ts +51 -0
- package/build/treb-base-types/src/rectangle.js +123 -0
- package/build/treb-base-types/src/rectangle.js.map +1 -0
- package/build/treb-base-types/src/render_text.d.ts +34 -0
- package/build/treb-base-types/src/render_text.js +22 -0
- package/build/treb-base-types/src/render_text.js.map +1 -0
- package/build/treb-base-types/src/style.d.ts +214 -0
- package/build/treb-base-types/src/style.js +373 -0
- package/build/treb-base-types/src/style.js.map +1 -0
- package/build/treb-base-types/src/table.d.ts +58 -0
- package/build/treb-base-types/src/table.js +27 -0
- package/build/treb-base-types/src/table.js.map +1 -0
- package/build/treb-base-types/src/text_part.d.ts +26 -0
- package/build/treb-base-types/src/text_part.js +47 -0
- package/build/treb-base-types/src/text_part.js.map +1 -0
- package/build/treb-base-types/src/theme.d.ts +120 -0
- package/build/treb-base-types/src/theme.js +460 -0
- package/build/treb-base-types/src/theme.js.map +1 -0
- package/build/treb-base-types/src/union.d.ts +73 -0
- package/build/treb-base-types/src/union.js +61 -0
- package/build/treb-base-types/src/union.js.map +1 -0
- package/build/treb-base-types/src/value-type.d.ts +86 -0
- package/build/treb-base-types/src/value-type.js +168 -0
- package/build/treb-base-types/src/value-type.js.map +1 -0
- package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
- package/build/treb-base-types/src/worker-proxy.js +221 -0
- package/build/treb-base-types/src/worker-proxy.js.map +1 -0
- package/build/treb-calculator/src/calculator.d.ts +249 -0
- package/build/treb-calculator/src/calculator.js +2755 -0
- package/build/treb-calculator/src/calculator.js.map +1 -0
- package/build/treb-calculator/src/complex-math.d.ts +75 -0
- package/build/treb-calculator/src/complex-math.js +559 -0
- package/build/treb-calculator/src/complex-math.js.map +1 -0
- package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/array-vertex.js +156 -0
- package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/graph.d.ts +134 -0
- package/build/treb-calculator/src/dag/graph.js +842 -0
- package/build/treb-calculator/src/dag/graph.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/vertex.js +274 -0
- package/build/treb-calculator/src/dag/vertex.js.map +1 -0
- package/build/treb-calculator/src/descriptors.d.ts +189 -0
- package/build/treb-calculator/src/descriptors.js +22 -0
- package/build/treb-calculator/src/descriptors.js.map +1 -0
- package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
- package/build/treb-calculator/src/expression-calculator.js +1033 -0
- package/build/treb-calculator/src/expression-calculator.js.map +1 -0
- package/build/treb-calculator/src/function-error.d.ts +35 -0
- package/build/treb-calculator/src/function-error.js +85 -0
- package/build/treb-calculator/src/function-error.js.map +1 -0
- package/build/treb-calculator/src/function-library.d.ts +22 -0
- package/build/treb-calculator/src/function-library.js +96 -0
- package/build/treb-calculator/src/function-library.js.map +1 -0
- package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
- package/build/treb-calculator/src/functions/base-functions.js +2611 -0
- package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/beta.d.ts +17 -0
- package/build/treb-calculator/src/functions/beta.js +201 -0
- package/build/treb-calculator/src/functions/beta.js.map +1 -0
- package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
- package/build/treb-calculator/src/functions/checkbox.js +128 -0
- package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
- package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/complex-functions.js +217 -0
- package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
- package/build/treb-calculator/src/functions/date-utils.js +59 -0
- package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
- package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/finance-functions.js +547 -0
- package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/fp.d.ts +2 -0
- package/build/treb-calculator/src/functions/fp.js +463 -0
- package/build/treb-calculator/src/functions/fp.js.map +1 -0
- package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
- package/build/treb-calculator/src/functions/function-utilities.js +36 -0
- package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
- package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
- package/build/treb-calculator/src/functions/gamma.js +142 -0
- package/build/treb-calculator/src/functions/gamma.js.map +1 -0
- package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/information-functions.js +71 -0
- package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
- package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
- package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/normal.d.ts +2 -0
- package/build/treb-calculator/src/functions/normal.js +32 -0
- package/build/treb-calculator/src/functions/normal.js.map +1 -0
- package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/regex-functions.js +188 -0
- package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
- package/build/treb-calculator/src/functions/sparkline.js +264 -0
- package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
- package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
- package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
- package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
- package/build/treb-calculator/src/functions/students-t.js +64 -0
- package/build/treb-calculator/src/functions/students-t.js.map +1 -0
- package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
- package/build/treb-calculator/src/functions/text-functions.js +320 -0
- package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
- package/build/treb-calculator/src/index.d.ts +2 -0
- package/build/treb-calculator/src/index.js +22 -0
- package/build/treb-calculator/src/index.js.map +1 -0
- package/build/treb-calculator/src/notifier-types.d.ts +26 -0
- package/build/treb-calculator/src/notifier-types.js +22 -0
- package/build/treb-calculator/src/notifier-types.js.map +1 -0
- package/build/treb-calculator/src/primitives.d.ts +15 -0
- package/build/treb-calculator/src/primitives.js +398 -0
- package/build/treb-calculator/src/primitives.js.map +1 -0
- package/build/treb-calculator/src/utilities.d.ts +68 -0
- package/build/treb-calculator/src/utilities.js +324 -0
- package/build/treb-calculator/src/utilities.js.map +1 -0
- package/build/treb-charts/src/chart-functions.d.ts +8 -0
- package/build/treb-charts/src/chart-functions.js +209 -0
- package/build/treb-charts/src/chart-functions.js.map +1 -0
- package/build/treb-charts/src/chart-types.d.ts +233 -0
- package/build/treb-charts/src/chart-types.js +57 -0
- package/build/treb-charts/src/chart-types.js.map +1 -0
- package/build/treb-charts/src/chart-utils.d.ts +106 -0
- package/build/treb-charts/src/chart-utils.js +1060 -0
- package/build/treb-charts/src/chart-utils.js.map +1 -0
- package/build/treb-charts/src/chart.d.ts +23 -0
- package/build/treb-charts/src/chart.js +94 -0
- package/build/treb-charts/src/chart.js.map +1 -0
- package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
- package/build/treb-charts/src/default-chart-renderer.js +533 -0
- package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
- package/build/treb-charts/src/index.d.ts +5 -0
- package/build/treb-charts/src/index.js +24 -0
- package/build/treb-charts/src/index.js.map +1 -0
- package/build/treb-charts/src/main.d.ts +1 -0
- package/build/treb-charts/src/main.js +34 -0
- package/build/treb-charts/src/main.js.map +1 -0
- package/build/treb-charts/src/quicksort.d.ts +1 -0
- package/build/treb-charts/src/quicksort.js +49 -0
- package/build/treb-charts/src/quicksort.js.map +1 -0
- package/build/treb-charts/src/rectangle.d.ts +18 -0
- package/build/treb-charts/src/rectangle.js +41 -0
- package/build/treb-charts/src/rectangle.js.map +1 -0
- package/build/treb-charts/src/renderer-type.d.ts +24 -0
- package/build/treb-charts/src/renderer-type.js +22 -0
- package/build/treb-charts/src/renderer-type.js.map +1 -0
- package/build/treb-charts/src/renderer.d.ts +127 -0
- package/build/treb-charts/src/renderer.js +1518 -0
- package/build/treb-charts/src/renderer.js.map +1 -0
- package/build/treb-charts/src/util.d.ts +18 -0
- package/build/treb-charts/src/util.js +71 -0
- package/build/treb-charts/src/util.js.map +1 -0
- package/build/treb-data-model/src/annotation.d.ts +167 -0
- package/build/treb-data-model/src/annotation.js +120 -0
- package/build/treb-data-model/src/annotation.js.map +1 -0
- package/build/treb-data-model/src/conditional_format.d.ts +155 -0
- package/build/treb-data-model/src/conditional_format.js +62 -0
- package/build/treb-data-model/src/conditional_format.js.map +1 -0
- package/build/treb-data-model/src/data-validation.d.ts +28 -0
- package/build/treb-data-model/src/data-validation.js +22 -0
- package/build/treb-data-model/src/data-validation.js.map +1 -0
- package/build/treb-data-model/src/data_model.d.ts +173 -0
- package/build/treb-data-model/src/data_model.js +637 -0
- package/build/treb-data-model/src/data_model.js.map +1 -0
- package/build/treb-data-model/src/index.d.ts +13 -0
- package/build/treb-data-model/src/index.js +28 -0
- package/build/treb-data-model/src/index.js.map +1 -0
- package/build/treb-data-model/src/language-model.d.ts +22 -0
- package/build/treb-data-model/src/language-model.js +22 -0
- package/build/treb-data-model/src/language-model.js.map +1 -0
- package/build/treb-data-model/src/named.d.ts +124 -0
- package/build/treb-data-model/src/named.js +372 -0
- package/build/treb-data-model/src/named.js.map +1 -0
- package/build/treb-data-model/src/serialize_options.d.ts +49 -0
- package/build/treb-data-model/src/serialize_options.js +22 -0
- package/build/treb-data-model/src/serialize_options.js.map +1 -0
- package/build/treb-data-model/src/sheet.d.ts +499 -0
- package/build/treb-data-model/src/sheet.js +2904 -0
- package/build/treb-data-model/src/sheet.js.map +1 -0
- package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
- package/build/treb-data-model/src/sheet_collection.js +112 -0
- package/build/treb-data-model/src/sheet_collection.js.map +1 -0
- package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
- package/build/treb-data-model/src/sheet_selection.js +39 -0
- package/build/treb-data-model/src/sheet_selection.js.map +1 -0
- package/build/treb-data-model/src/sheet_types.d.ts +104 -0
- package/build/treb-data-model/src/sheet_types.js +22 -0
- package/build/treb-data-model/src/sheet_types.js.map +1 -0
- package/build/treb-data-model/src/types.d.ts +59 -0
- package/build/treb-data-model/src/types.js +22 -0
- package/build/treb-data-model/src/types.js.map +1 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
- package/build/treb-embed/src/custom-element/treb-global.js +64 -0
- package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
- package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
- package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
- package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
- package/build/treb-embed/src/index.d.ts +12 -0
- package/build/treb-embed/src/index.js +34 -0
- package/build/treb-embed/src/index.js.map +1 -0
- package/build/treb-embed/src/options.d.ts +266 -0
- package/build/treb-embed/src/options.js +56 -0
- package/build/treb-embed/src/options.js.map +1 -0
- package/build/treb-embed/src/plugin.d.ts +9 -0
- package/build/treb-embed/src/plugin.js +22 -0
- package/build/treb-embed/src/plugin.js.map +1 -0
- package/build/treb-embed/src/progress-dialog.d.ts +49 -0
- package/build/treb-embed/src/progress-dialog.js +178 -0
- package/build/treb-embed/src/progress-dialog.js.map +1 -0
- package/build/treb-embed/src/selection-state.d.ts +15 -0
- package/build/treb-embed/src/selection-state.js +22 -0
- package/build/treb-embed/src/selection-state.js.map +1 -0
- package/build/treb-embed/src/spinner.d.ts +8 -0
- package/build/treb-embed/src/spinner.js +40 -0
- package/build/treb-embed/src/spinner.js.map +1 -0
- package/build/treb-embed/src/toolbar-message.d.ts +72 -0
- package/build/treb-embed/src/toolbar-message.js +22 -0
- package/build/treb-embed/src/toolbar-message.js.map +1 -0
- package/build/treb-embed/src/types.d.ts +185 -0
- package/build/treb-embed/src/types.js +45 -0
- package/build/treb-embed/src/types.js.map +1 -0
- package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
- package/build/treb-export/src/address-type.d.ts +34 -0
- package/build/treb-export/src/address-type.js +53 -0
- package/build/treb-export/src/address-type.js.map +1 -0
- package/build/treb-export/src/base-template.d.ts +1 -0
- package/build/treb-export/src/base-template.js +22 -0
- package/build/treb-export/src/base-template.js.map +1 -0
- package/build/treb-export/src/column-width.d.ts +2 -0
- package/build/treb-export/src/column-width.js +80 -0
- package/build/treb-export/src/column-width.js.map +1 -0
- package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
- package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
- package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
- package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
- package/build/treb-export/src/drawing/chart.d.ts +26 -0
- package/build/treb-export/src/drawing/chart.js +247 -0
- package/build/treb-export/src/drawing/chart.js.map +1 -0
- package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
- package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
- package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/drawing.d.ts +49 -0
- package/build/treb-export/src/drawing/drawing.js +193 -0
- package/build/treb-export/src/drawing/drawing.js.map +1 -0
- package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
- package/build/treb-export/src/drawing/embedded-image.js +54 -0
- package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
- package/build/treb-export/src/export.d.ts +72 -0
- package/build/treb-export/src/export.js +2039 -0
- package/build/treb-export/src/export.js.map +1 -0
- package/build/treb-export/src/import-export-messages.d.ts +31 -0
- package/build/treb-export/src/import-export-messages.js +22 -0
- package/build/treb-export/src/import-export-messages.js.map +1 -0
- package/build/treb-export/src/import.d.ts +33 -0
- package/build/treb-export/src/import.js +1258 -0
- package/build/treb-export/src/import.js.map +1 -0
- package/build/treb-export/src/index.worker.d.ts +1 -0
- package/build/treb-export/src/index.worker.js +93 -0
- package/build/treb-export/src/index.worker.js.map +1 -0
- package/build/treb-export/src/metadata.d.ts +51 -0
- package/build/treb-export/src/metadata.js +153 -0
- package/build/treb-export/src/metadata.js.map +1 -0
- package/build/treb-export/src/ooxml.d.ts +7 -0
- package/build/treb-export/src/ooxml.js +41 -0
- package/build/treb-export/src/ooxml.js.map +1 -0
- package/build/treb-export/src/relationship.d.ts +8 -0
- package/build/treb-export/src/relationship.js +27 -0
- package/build/treb-export/src/relationship.js.map +1 -0
- package/build/treb-export/src/shared-strings.d.ts +11 -0
- package/build/treb-export/src/shared-strings.js +105 -0
- package/build/treb-export/src/shared-strings.js.map +1 -0
- package/build/treb-export/src/template-2.d.ts +1 -0
- package/build/treb-export/src/template-2.js +22 -0
- package/build/treb-export/src/template-2.js.map +1 -0
- package/build/treb-export/src/unescape_xml.d.ts +1 -0
- package/build/treb-export/src/unescape_xml.js +61 -0
- package/build/treb-export/src/unescape_xml.js.map +1 -0
- package/build/treb-export/src/workbook-sheet.d.ts +75 -0
- package/build/treb-export/src/workbook-sheet.js +128 -0
- package/build/treb-export/src/workbook-sheet.js.map +1 -0
- package/build/treb-export/src/workbook-style.d.ts +110 -0
- package/build/treb-export/src/workbook-style.js +1134 -0
- package/build/treb-export/src/workbook-style.js.map +1 -0
- package/build/treb-export/src/workbook-theme.d.ts +13 -0
- package/build/treb-export/src/workbook-theme.js +85 -0
- package/build/treb-export/src/workbook-theme.js.map +1 -0
- package/build/treb-export/src/workbook.d.ts +123 -0
- package/build/treb-export/src/workbook.js +644 -0
- package/build/treb-export/src/workbook.js.map +1 -0
- package/build/treb-export/src/xml-test.d.ts +9 -0
- package/build/treb-export/src/xml-test.js +52 -0
- package/build/treb-export/src/xml-test.js.map +1 -0
- package/build/treb-export/src/xml-utils.d.ts +76 -0
- package/build/treb-export/src/xml-utils.js +223 -0
- package/build/treb-export/src/xml-utils.js.map +1 -0
- package/build/treb-export/src/zip-wrapper.d.ts +22 -0
- package/build/treb-export/src/zip-wrapper.js +93 -0
- package/build/treb-export/src/zip-wrapper.js.map +1 -0
- package/build/treb-format/src/format.d.ts +130 -0
- package/build/treb-format/src/format.js +805 -0
- package/build/treb-format/src/format.js.map +1 -0
- package/build/treb-format/src/format_cache.d.ts +55 -0
- package/build/treb-format/src/format_cache.js +166 -0
- package/build/treb-format/src/format_cache.js.map +1 -0
- package/build/treb-format/src/format_parser.d.ts +70 -0
- package/build/treb-format/src/format_parser.js +618 -0
- package/build/treb-format/src/format_parser.js.map +1 -0
- package/build/treb-format/src/index.d.ts +4 -0
- package/build/treb-format/src/index.js +25 -0
- package/build/treb-format/src/index.js.map +1 -0
- package/build/treb-format/src/number_format_section.d.ts +58 -0
- package/build/treb-format/src/number_format_section.js +78 -0
- package/build/treb-format/src/number_format_section.js.map +1 -0
- package/build/treb-format/src/value_parser.d.ts +48 -0
- package/build/treb-format/src/value_parser.js +244 -0
- package/build/treb-format/src/value_parser.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
- package/build/treb-grid/src/editors/autocomplete.js +316 -0
- package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
- package/build/treb-grid/src/editors/editor.d.ts +214 -0
- package/build/treb-grid/src/editors/editor.js +879 -0
- package/build/treb-grid/src/editors/editor.js.map +1 -0
- package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
- package/build/treb-grid/src/editors/external_editor.js +118 -0
- package/build/treb-grid/src/editors/external_editor.js.map +1 -0
- package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
- package/build/treb-grid/src/editors/formula_bar.js +444 -0
- package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
- package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
- package/build/treb-grid/src/editors/overlay_editor.js +353 -0
- package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
- package/build/treb-grid/src/index.d.ts +12 -0
- package/build/treb-grid/src/index.js +28 -0
- package/build/treb-grid/src/index.js.map +1 -0
- package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
- package/build/treb-grid/src/layout/base_layout.js +2050 -0
- package/build/treb-grid/src/layout/base_layout.js.map +1 -0
- package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
- package/build/treb-grid/src/layout/grid_layout.js +235 -0
- package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
- package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
- package/build/treb-grid/src/layout/mock-layout.js +37 -0
- package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
- package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
- package/build/treb-grid/src/render/selection-renderer.js +315 -0
- package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
- package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
- package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
- package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
- package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
- package/build/treb-grid/src/render/svg_selection_block.js +106 -0
- package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
- package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
- package/build/treb-grid/src/render/tile_renderer.js +1609 -0
- package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
- package/build/treb-grid/src/types/border_constants.d.ts +9 -0
- package/build/treb-grid/src/types/border_constants.js +34 -0
- package/build/treb-grid/src/types/border_constants.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
- package/build/treb-grid/src/types/clipboard_data.js +22 -0
- package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
- package/build/treb-grid/src/types/clipboard_data2.js +22 -0
- package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
- package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
- package/build/treb-grid/src/types/drag_mask.js +78 -0
- package/build/treb-grid/src/types/drag_mask.js.map +1 -0
- package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
- package/build/treb-grid/src/types/external_editor_config.js +22 -0
- package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
- package/build/treb-grid/src/types/grid.d.ts +806 -0
- package/build/treb-grid/src/types/grid.js +6410 -0
- package/build/treb-grid/src/types/grid.js.map +1 -0
- package/build/treb-grid/src/types/grid_base.d.ts +442 -0
- package/build/treb-grid/src/types/grid_base.js +3523 -0
- package/build/treb-grid/src/types/grid_base.js.map +1 -0
- package/build/treb-grid/src/types/grid_command.d.ts +408 -0
- package/build/treb-grid/src/types/grid_command.js +75 -0
- package/build/treb-grid/src/types/grid_command.js.map +1 -0
- package/build/treb-grid/src/types/grid_events.d.ts +93 -0
- package/build/treb-grid/src/types/grid_events.js +36 -0
- package/build/treb-grid/src/types/grid_events.js.map +1 -0
- package/build/treb-grid/src/types/grid_options.d.ts +50 -0
- package/build/treb-grid/src/types/grid_options.js +34 -0
- package/build/treb-grid/src/types/grid_options.js.map +1 -0
- package/build/treb-grid/src/types/scale-control.d.ts +21 -0
- package/build/treb-grid/src/types/scale-control.js +148 -0
- package/build/treb-grid/src/types/scale-control.js.map +1 -0
- package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
- package/build/treb-grid/src/types/set_range_options.js +22 -0
- package/build/treb-grid/src/types/set_range_options.js.map +1 -0
- package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
- package/build/treb-grid/src/types/tab_bar.js +426 -0
- package/build/treb-grid/src/types/tab_bar.js.map +1 -0
- package/build/treb-grid/src/types/tile.d.ts +29 -0
- package/build/treb-grid/src/types/tile.js +22 -0
- package/build/treb-grid/src/types/tile.js.map +1 -0
- package/build/treb-grid/src/types/update_flags.d.ts +48 -0
- package/build/treb-grid/src/types/update_flags.js +22 -0
- package/build/treb-grid/src/types/update_flags.js.map +1 -0
- package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
- package/build/treb-grid/src/util/fontmetrics.js +82 -0
- package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
- package/build/treb-grid/src/util/ua.d.ts +33 -0
- package/build/treb-grid/src/util/ua.js +86 -0
- package/build/treb-grid/src/util/ua.js.map +1 -0
- package/build/treb-parser/src/csv-parser.d.ts +13 -0
- package/build/treb-parser/src/csv-parser.js +107 -0
- package/build/treb-parser/src/csv-parser.js.map +1 -0
- package/build/treb-parser/src/index.d.ts +4 -0
- package/build/treb-parser/src/index.js +25 -0
- package/build/treb-parser/src/index.js.map +1 -0
- package/build/treb-parser/src/md-parser.d.ts +97 -0
- package/build/treb-parser/src/md-parser.js +403 -0
- package/build/treb-parser/src/md-parser.js.map +1 -0
- package/build/treb-parser/src/parser-types.d.ts +345 -0
- package/build/treb-parser/src/parser-types.js +53 -0
- package/build/treb-parser/src/parser-types.js.map +1 -0
- package/build/treb-parser/src/parser.d.ts +422 -0
- package/build/treb-parser/src/parser.js +2418 -0
- package/build/treb-parser/src/parser.js.map +1 -0
- package/build/treb-utils/src/event_source.d.ts +34 -0
- package/build/treb-utils/src/event_source.js +110 -0
- package/build/treb-utils/src/event_source.js.map +1 -0
- package/build/treb-utils/src/ievent_source.d.ts +9 -0
- package/build/treb-utils/src/ievent_source.js +22 -0
- package/build/treb-utils/src/ievent_source.js.map +1 -0
- package/build/treb-utils/src/index.d.ts +6 -0
- package/build/treb-utils/src/index.js +30 -0
- package/build/treb-utils/src/index.js.map +1 -0
- package/build/treb-utils/src/measurement.d.ts +42 -0
- package/build/treb-utils/src/measurement.js +145 -0
- package/build/treb-utils/src/measurement.js.map +1 -0
- package/build/treb-utils/src/scale.d.ts +16 -0
- package/build/treb-utils/src/scale.js +106 -0
- package/build/treb-utils/src/scale.js.map +1 -0
- package/build/treb-utils/src/serialize_html.d.ts +5 -0
- package/build/treb-utils/src/serialize_html.js +128 -0
- package/build/treb-utils/src/serialize_html.js.map +1 -0
- package/build/treb-utils/src/validate_uri.d.ts +20 -0
- package/build/treb-utils/src/validate_uri.js +55 -0
- package/build/treb-utils/src/validate_uri.js.map +1 -0
- package/dist/{chunk-XD5PEZBZ.mjs → chunk-E35ONJUS.mjs} +1 -1
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +4 -4
- package/package.json +1 -1
|
@@ -0,0 +1,2418 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2026 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
import { ArgumentSeparatorType, DecimalMarkType, DefaultParserConfig } from './parser-types';
|
|
22
|
+
/**
|
|
23
|
+
* regex determines if a sheet name requires quotes. centralizing
|
|
24
|
+
* this to simplify maintenance and reduce overlap/errors
|
|
25
|
+
*/
|
|
26
|
+
export const QuotedSheetNameRegex = /[\s-+=<>!()]/;
|
|
27
|
+
/**
|
|
28
|
+
* similarly, illegal sheet name. we don't actually handle this in
|
|
29
|
+
* the parser, but it seems like a reasonable place to keep this
|
|
30
|
+
* definition.
|
|
31
|
+
*/
|
|
32
|
+
export const IllegalSheetNameRegex = /['*\\]/;
|
|
33
|
+
const DOUBLE_QUOTE = 0x22; // '"'.charCodeAt(0);
|
|
34
|
+
const SINGLE_QUOTE = 0x27; // `'`.charCodeAt(0);
|
|
35
|
+
const NON_BREAKING_SPACE = 0xa0;
|
|
36
|
+
const SPACE = 0x20;
|
|
37
|
+
const TAB = 0x09;
|
|
38
|
+
const CR = 0x0a;
|
|
39
|
+
const LF = 0x0d;
|
|
40
|
+
const ZERO = 0x30;
|
|
41
|
+
const NINE = 0x39;
|
|
42
|
+
const PERIOD = 0x2e;
|
|
43
|
+
const PLUS = 0x2b;
|
|
44
|
+
const MINUS = 0x2d;
|
|
45
|
+
const OPEN_PAREN = 0x28;
|
|
46
|
+
const CLOSE_PAREN = 0x29;
|
|
47
|
+
const COMMA = 0x2c;
|
|
48
|
+
const PERCENT = 0x25;
|
|
49
|
+
const UNDERSCORE = 0x5f;
|
|
50
|
+
const DOLLAR_SIGN = 0x24;
|
|
51
|
+
const OPEN_BRACE = 0x7b;
|
|
52
|
+
const CLOSE_BRACE = 0x7d;
|
|
53
|
+
const OPEN_SQUARE_BRACKET = 0x5b;
|
|
54
|
+
const CLOSE_SQUARE_BRACKET = 0x5d;
|
|
55
|
+
const QUESTION_MARK = 0x3f;
|
|
56
|
+
const EXCLAMATION_MARK = 0x21;
|
|
57
|
+
// const COLON = 0x3a; // became an operator
|
|
58
|
+
const SEMICOLON = 0x3b;
|
|
59
|
+
const HASH = 0x23; // #
|
|
60
|
+
const AT = 0x40; // @
|
|
61
|
+
const UC_A = 0x41;
|
|
62
|
+
const LC_A = 0x61;
|
|
63
|
+
const UC_E = 0x45;
|
|
64
|
+
const LC_E = 0x65;
|
|
65
|
+
const UC_Z = 0x5a;
|
|
66
|
+
const LC_Z = 0x7a;
|
|
67
|
+
const LC_I = 0x69;
|
|
68
|
+
// const LC_J = 0x6a;
|
|
69
|
+
// there are a couple of characters we don't want in this
|
|
70
|
+
// range; we should split into separate ranges. also we
|
|
71
|
+
// probably have characters we don't need (atm)
|
|
72
|
+
const ACCENTED_RANGE_START = 192;
|
|
73
|
+
const ACCENTED_RANGE_END = 382; // bumping up for polish // 312;
|
|
74
|
+
/**
|
|
75
|
+
* precedence map
|
|
76
|
+
*/
|
|
77
|
+
const binary_operators_precendence = {
|
|
78
|
+
'==': 6,
|
|
79
|
+
'!=': 6, // FIXME: we should not support these (legacy)
|
|
80
|
+
'<>': 6,
|
|
81
|
+
'=': 6, // these are the appropriate equality operators for SL
|
|
82
|
+
'<': 7,
|
|
83
|
+
'>': 7,
|
|
84
|
+
'<=': 7,
|
|
85
|
+
'>=': 7,
|
|
86
|
+
'+': 9,
|
|
87
|
+
'-': 9,
|
|
88
|
+
'&': 9,
|
|
89
|
+
'*': 10,
|
|
90
|
+
'/': 10,
|
|
91
|
+
'^': 11, // highest math op
|
|
92
|
+
':': 13, // range operator
|
|
93
|
+
};
|
|
94
|
+
/* *
|
|
95
|
+
* binary ops are sorted by length so we can compare long ops first
|
|
96
|
+
switching to a composite w/ unary operators
|
|
97
|
+
* /
|
|
98
|
+
const binary_operators = Object.keys(binary_operators_precendence).sort(
|
|
99
|
+
(a, b) => b.length - a.length,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* unary operators. atm we have no precedence issues, unary operators
|
|
104
|
+
* always have absolute precedence. (for numbers, these are properly part
|
|
105
|
+
* of the number, but consider `=-SUM(1,2)` -- this is an operator).
|
|
106
|
+
*
|
|
107
|
+
* implicit intersection operator should now have precedence over +/-.
|
|
108
|
+
*/
|
|
109
|
+
const unary_operators = { '@': 50, '-': 100, '+': 100 };
|
|
110
|
+
/**
|
|
111
|
+
* to avoid the double - and +, we're just adding our one extra unary
|
|
112
|
+
* operator. doing this dynamically would be silly, although this does
|
|
113
|
+
* make this code more fragile.
|
|
114
|
+
*/
|
|
115
|
+
const composite_operators = [...Object.keys(binary_operators_precendence), '@'].sort((a, b) => b.length - a.length);
|
|
116
|
+
/**
|
|
117
|
+
* parser for spreadsheet language.
|
|
118
|
+
*
|
|
119
|
+
* FIXME: this is stateless, think about exporting a singleton.
|
|
120
|
+
*
|
|
121
|
+
* (there is internal state, but it's only used during a Parse() call,
|
|
122
|
+
* which runs synchronously). one benefit of using a singleton would be
|
|
123
|
+
* consistency in decimal mark, we'd only have to set once.
|
|
124
|
+
*
|
|
125
|
+
* FIXME: the internal state is starting to grate. there's no reason for
|
|
126
|
+
* it and it just confuses things because parsing is stateless (except for
|
|
127
|
+
* configuration). internal state just keeps results from the last parse
|
|
128
|
+
* operation. we should refactor so parsing is clean and returns all
|
|
129
|
+
* results directly, caller can store if necessary.
|
|
130
|
+
*
|
|
131
|
+
* FIXME: split rendering into a separate class? would be a little cleaner.
|
|
132
|
+
*
|
|
133
|
+
* FIXME: we don't currently handle full-width punctuation. it would be
|
|
134
|
+
* simple to parse, a little more complicated to keep track of if we wanted
|
|
135
|
+
* to be able to rewrite. TODO/FIXME.
|
|
136
|
+
*
|
|
137
|
+
*/
|
|
138
|
+
export class Parser {
|
|
139
|
+
/**
|
|
140
|
+
* accessor replacing old field. the actual value is moved to flags,
|
|
141
|
+
* and should be set via the SetLocaleSettings method.
|
|
142
|
+
*/
|
|
143
|
+
get argument_separator() {
|
|
144
|
+
return this.flags.argument_separator;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* accessor replacing old field. the actual value is moved to flags,
|
|
148
|
+
* and should be set via the SetLocaleSettings method.
|
|
149
|
+
*/
|
|
150
|
+
get decimal_mark() {
|
|
151
|
+
return this.flags.decimal_mark;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* unifying flags
|
|
155
|
+
*/
|
|
156
|
+
flags = {
|
|
157
|
+
...DefaultParserConfig,
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* FIXME: why is this a class member? at a minimum it could be static
|
|
161
|
+
* FIXME: why are we doing this with a regex?
|
|
162
|
+
*/
|
|
163
|
+
r1c1_regex = /[rR]((?:\[[-+]{0,1}\d+\]|\d*))[cC]((?:\[[-+]{0,1}\d+\]|\d*))$/;
|
|
164
|
+
/**
|
|
165
|
+
* internal argument separator, as a number. this is set internally on
|
|
166
|
+
* parse call, following the argument_separator value.
|
|
167
|
+
*/
|
|
168
|
+
argument_separator_char = COMMA;
|
|
169
|
+
/**
|
|
170
|
+
* internal decimal mark, as a number.
|
|
171
|
+
*/
|
|
172
|
+
decimal_mark_char = PERIOD;
|
|
173
|
+
/**
|
|
174
|
+
* imaginary number value. this is "i", except for those EE weirdos who
|
|
175
|
+
* use "j". although I guess those guys put it in front, so it won't really
|
|
176
|
+
* work anyway... let's stick with "i" for now.
|
|
177
|
+
*/
|
|
178
|
+
imaginary_char = LC_I;
|
|
179
|
+
/**
|
|
180
|
+
* imaginary number as text for matching
|
|
181
|
+
*/
|
|
182
|
+
imaginary_number = 'i';
|
|
183
|
+
/**
|
|
184
|
+
* internal counter for incrementing IDs
|
|
185
|
+
*/
|
|
186
|
+
id_counter = 0;
|
|
187
|
+
expression = '';
|
|
188
|
+
data = [];
|
|
189
|
+
index = 0;
|
|
190
|
+
length = 0;
|
|
191
|
+
/** success flag */
|
|
192
|
+
valid = true;
|
|
193
|
+
/** rolling error state */
|
|
194
|
+
error_position;
|
|
195
|
+
/** rolling error state */
|
|
196
|
+
error;
|
|
197
|
+
dependencies = {
|
|
198
|
+
addresses: {},
|
|
199
|
+
ranges: {},
|
|
200
|
+
};
|
|
201
|
+
// referenced addresses -- used to merge ranges/addresses, although I'm
|
|
202
|
+
// not sure that's actually all that useful
|
|
203
|
+
address_refcount = {};
|
|
204
|
+
/**
|
|
205
|
+
* full list of referenced addresses and ranges. we're adding this
|
|
206
|
+
* to support highlighting, for which we need multiple instances
|
|
207
|
+
* of a single address. the original dep list was used for graph dependencies,
|
|
208
|
+
* so we compressed the list.
|
|
209
|
+
*
|
|
210
|
+
* FIXME: use a single list, i.e. something like
|
|
211
|
+
*
|
|
212
|
+
* address -> [instance, instance]
|
|
213
|
+
*
|
|
214
|
+
* because that's a big API change it's going to have to wait. for now,
|
|
215
|
+
* use a second list.
|
|
216
|
+
*
|
|
217
|
+
* UPDATE: adding (otherwise unused) tokens, which could be named ranges.
|
|
218
|
+
* in the future we may pass in a list of names at parse time, and resolve
|
|
219
|
+
* them; for now we are just listing names.
|
|
220
|
+
*/
|
|
221
|
+
full_reference_list = [];
|
|
222
|
+
/**
|
|
223
|
+
* cache for storing/restoring parser state, if we toggle it
|
|
224
|
+
*/
|
|
225
|
+
parser_state_cache = [];
|
|
226
|
+
/**
|
|
227
|
+
* step towards protecting these values and setting them in one
|
|
228
|
+
* operation.
|
|
229
|
+
*
|
|
230
|
+
* UPDATE: switch order. argument separator is optional and implied.
|
|
231
|
+
*/
|
|
232
|
+
SetLocaleSettings(decimal_mark, argument_separator) {
|
|
233
|
+
if (typeof argument_separator === 'undefined') {
|
|
234
|
+
argument_separator = (decimal_mark === DecimalMarkType.Comma) ?
|
|
235
|
+
ArgumentSeparatorType.Semicolon :
|
|
236
|
+
ArgumentSeparatorType.Comma;
|
|
237
|
+
}
|
|
238
|
+
// I suppose semicolon and period is allowable, although no one
|
|
239
|
+
// uses it. this test only works because we know the internal type
|
|
240
|
+
// representation, but that's fragile and not a good idea. FIXME
|
|
241
|
+
if (argument_separator === decimal_mark) {
|
|
242
|
+
throw new Error('invalid locale setting');
|
|
243
|
+
}
|
|
244
|
+
this.flags.argument_separator = argument_separator;
|
|
245
|
+
this.flags.decimal_mark = decimal_mark;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* save local configuration to a buffer, so it can be restored. we're doing
|
|
249
|
+
* this because in a lot of places we're caching parser flagss, changing
|
|
250
|
+
* them, and then restoring them. that's become repetitive, fragile to
|
|
251
|
+
* changes or new flags, and annoying.
|
|
252
|
+
*
|
|
253
|
+
* config is managed in a list with push/pop semantics. we store it as
|
|
254
|
+
* JSON so there's no possibility we'll accidentally mutate.
|
|
255
|
+
*
|
|
256
|
+
* FIXME: while we're at it why not migrate the separators -> flags, so
|
|
257
|
+
* there's a single location for this kind of state? (...TODO)
|
|
258
|
+
*
|
|
259
|
+
*/
|
|
260
|
+
Save() {
|
|
261
|
+
this.parser_state_cache.push(JSON.stringify(this.flags));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* restore persisted config
|
|
265
|
+
* @see Save
|
|
266
|
+
*/
|
|
267
|
+
Restore() {
|
|
268
|
+
const json = this.parser_state_cache.shift();
|
|
269
|
+
if (json) {
|
|
270
|
+
try {
|
|
271
|
+
this.flags = JSON.parse(json);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
console.error(err);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
console.warn("No parser state to restore");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* recursive tree walk that allows substitution. this should be
|
|
283
|
+
* a drop-in replacement for the original Walk function but I'm
|
|
284
|
+
* keeping it separate temporarily just in case it breaks something.
|
|
285
|
+
*
|
|
286
|
+
* @param func - in this version function can return `true` (continue
|
|
287
|
+
* walking subtree), `false` (don't walk subtree), or an ExpressionUnit.
|
|
288
|
+
* in the last case, we'll replace the original unit with the substitution.
|
|
289
|
+
* obviously in that case we don't recurse.
|
|
290
|
+
*/
|
|
291
|
+
Walk2(unit, func) {
|
|
292
|
+
const result = func(unit);
|
|
293
|
+
if (typeof result === 'object') {
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
switch (unit.type) {
|
|
297
|
+
case 'address':
|
|
298
|
+
case 'missing':
|
|
299
|
+
case 'literal':
|
|
300
|
+
case 'complex':
|
|
301
|
+
case 'identifier':
|
|
302
|
+
case 'operator':
|
|
303
|
+
case 'structured-reference':
|
|
304
|
+
break;
|
|
305
|
+
case 'dimensioned':
|
|
306
|
+
if (result) {
|
|
307
|
+
unit.expression = this.Walk2(unit.expression, func); // could be an issue
|
|
308
|
+
unit.unit = this.Walk2(unit.unit, func); // could be an issue
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
case 'range':
|
|
312
|
+
if (func(unit)) {
|
|
313
|
+
unit.start = this.Walk2(unit.start, func); // could be an issue
|
|
314
|
+
unit.end = this.Walk2(unit.end, func); // could be an issue
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
case 'binary':
|
|
318
|
+
if (func(unit)) {
|
|
319
|
+
unit.left = this.Walk2(unit.left, func);
|
|
320
|
+
unit.right = this.Walk2(unit.right, func);
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
case 'unary':
|
|
324
|
+
if (func(unit)) {
|
|
325
|
+
unit.operand = this.Walk2(unit.operand, func);
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
case 'group':
|
|
329
|
+
if (func(unit)) {
|
|
330
|
+
unit.elements = unit.elements.map(source => this.Walk2(source, func));
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
case 'implicit-call':
|
|
334
|
+
if (func(unit)) {
|
|
335
|
+
unit.call = this.Walk2(unit.call, func);
|
|
336
|
+
unit.args = unit.args.map(source => this.Walk2(source, func));
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case 'call':
|
|
340
|
+
if (func(unit)) {
|
|
341
|
+
unit.args = unit.args.map(source => this.Walk2(source, func));
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
return unit;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* recursive tree walk.
|
|
349
|
+
*
|
|
350
|
+
* @param func function called on each node. for nodes that have children
|
|
351
|
+
* (operations, calls, groups) return false to skip the subtree, or true to
|
|
352
|
+
* traverse.
|
|
353
|
+
*/
|
|
354
|
+
Walk(unit, func) {
|
|
355
|
+
switch (unit.type) {
|
|
356
|
+
case 'address':
|
|
357
|
+
case 'missing':
|
|
358
|
+
case 'literal':
|
|
359
|
+
case 'complex':
|
|
360
|
+
case 'identifier':
|
|
361
|
+
case 'operator':
|
|
362
|
+
case 'structured-reference':
|
|
363
|
+
func(unit);
|
|
364
|
+
return;
|
|
365
|
+
case 'dimensioned':
|
|
366
|
+
if (func(unit)) {
|
|
367
|
+
this.Walk(unit.expression, func);
|
|
368
|
+
this.Walk(unit.unit, func);
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
case 'range':
|
|
372
|
+
if (func(unit)) {
|
|
373
|
+
this.Walk(unit.start, func);
|
|
374
|
+
this.Walk(unit.end, func);
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
case 'binary':
|
|
378
|
+
if (func(unit)) {
|
|
379
|
+
this.Walk(unit.left, func);
|
|
380
|
+
this.Walk(unit.right, func);
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
case 'unary':
|
|
384
|
+
if (func(unit)) {
|
|
385
|
+
this.Walk(unit.operand, func);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
case 'group':
|
|
389
|
+
if (func(unit)) {
|
|
390
|
+
// unit.elements.forEach((element) => this.Walk(element, func));
|
|
391
|
+
for (const element of unit.elements) {
|
|
392
|
+
this.Walk(element, func);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
case 'implicit-call':
|
|
397
|
+
if (func(unit)) {
|
|
398
|
+
this.Walk(unit.call, func);
|
|
399
|
+
for (const arg of unit.args) {
|
|
400
|
+
this.Walk(arg, func);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
case 'call':
|
|
405
|
+
if (func(unit)) {
|
|
406
|
+
for (const arg of unit.args) {
|
|
407
|
+
this.Walk(arg, func);
|
|
408
|
+
}
|
|
409
|
+
// unit.args.forEach((arg) => this.Walk(arg, func));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/** utility: transpose array */
|
|
414
|
+
Transpose(arr) {
|
|
415
|
+
const m = arr.length;
|
|
416
|
+
const transposed = [];
|
|
417
|
+
let n = 0;
|
|
418
|
+
for (let i = 0; i < m; i++) {
|
|
419
|
+
if (Array.isArray(arr[i])) {
|
|
420
|
+
n = Math.max(n, arr[i].length);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (let i = 0; i < n; i++) {
|
|
424
|
+
transposed[i] = [];
|
|
425
|
+
for (let j = 0; j < m; j++) {
|
|
426
|
+
transposed[i][j] = arr[j] ? arr[j][i] : undefined;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return transposed;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* renders the passed expression as a string.
|
|
433
|
+
* @param unit base expression
|
|
434
|
+
* @param offset offset for addresses, used to offset relative addresses
|
|
435
|
+
* (and ranges). this is for copy-and-paste or move operations.
|
|
436
|
+
* @param missing string to represent missing values (can be '', for functions)
|
|
437
|
+
*
|
|
438
|
+
* FIXME: we're accumulating too many arguments. need to switch to an
|
|
439
|
+
* options object. do that after the structured reference stuff merges.
|
|
440
|
+
*
|
|
441
|
+
*/
|
|
442
|
+
Render(unit, options = {}) {
|
|
443
|
+
// defaults
|
|
444
|
+
const offset = options.offset || { rows: 0, columns: 0 };
|
|
445
|
+
const missing = options.missing ?? '(missing)';
|
|
446
|
+
// the rest are optional
|
|
447
|
+
/*
|
|
448
|
+
offset: { rows: number; columns: number } = { rows: 0, columns: 0 },
|
|
449
|
+
missing = '(missing)',
|
|
450
|
+
convert_decimal?: DecimalMarkType,
|
|
451
|
+
convert_argument_separator?: ArgumentSeparatorType,
|
|
452
|
+
convert_imaginary_number?: 'i'|'j',
|
|
453
|
+
long_structured_references?: boolean,
|
|
454
|
+
table_name?: string,
|
|
455
|
+
|
|
456
|
+
): string {
|
|
457
|
+
*/
|
|
458
|
+
const { convert_decimal, convert_argument_separator,
|
|
459
|
+
// convert_imaginary_number,
|
|
460
|
+
long_structured_references, table_name, } = options;
|
|
461
|
+
// use default separator, unless we're explicitly converting.
|
|
462
|
+
let separator = this.flags.argument_separator + ' ';
|
|
463
|
+
if (convert_argument_separator === ArgumentSeparatorType.Comma) {
|
|
464
|
+
separator = ', ';
|
|
465
|
+
}
|
|
466
|
+
else if (convert_argument_separator === ArgumentSeparatorType.Semicolon) {
|
|
467
|
+
separator = '; ';
|
|
468
|
+
}
|
|
469
|
+
/*
|
|
470
|
+
let imaginary_character = this.imaginary_number;
|
|
471
|
+
if (convert_imaginary_number) {
|
|
472
|
+
imaginary_character = convert_imaginary_number;
|
|
473
|
+
}
|
|
474
|
+
*/
|
|
475
|
+
// this is only used if we're converting.
|
|
476
|
+
const decimal = convert_decimal === DecimalMarkType.Comma ? ',' : '.';
|
|
477
|
+
const decimal_rex = this.flags.decimal_mark === DecimalMarkType.Comma ? /,/ : /\./;
|
|
478
|
+
// we need this for complex numbers, but I don't want to change the
|
|
479
|
+
// original at the moment, just in case. we can run through that later.
|
|
480
|
+
const decimal_rex_g = this.flags.decimal_mark === DecimalMarkType.Comma ? /,/g : /\./g;
|
|
481
|
+
switch (unit.type) {
|
|
482
|
+
case 'address':
|
|
483
|
+
if (options.pass_through_addresses) {
|
|
484
|
+
return unit.label;
|
|
485
|
+
}
|
|
486
|
+
return options.r1c1 ? this.R1C1Label(unit, options) : this.AddressLabel(unit, offset);
|
|
487
|
+
case 'range':
|
|
488
|
+
if (options.pass_through_addresses) {
|
|
489
|
+
return unit.label;
|
|
490
|
+
}
|
|
491
|
+
return options.r1c1 ?
|
|
492
|
+
this.R1C1Label(unit.start, options) + ':' +
|
|
493
|
+
this.R1C1Label(unit.end, options) :
|
|
494
|
+
this.AddressLabel(unit.start, offset) + ':' + this.AddressLabel(unit.end, offset);
|
|
495
|
+
case 'missing':
|
|
496
|
+
return missing;
|
|
497
|
+
case 'array':
|
|
498
|
+
// we have to transpose because we're column-major but the
|
|
499
|
+
// format is row-major
|
|
500
|
+
return '{' +
|
|
501
|
+
this.Transpose(unit.values).map((row) => row.map((value) => {
|
|
502
|
+
if (typeof value === 'string') {
|
|
503
|
+
return '"' + value + '"';
|
|
504
|
+
}
|
|
505
|
+
return value;
|
|
506
|
+
}).join(', ')).join('; ') + '}';
|
|
507
|
+
case 'binary':
|
|
508
|
+
// in some cases we might see range constructs as binary units
|
|
509
|
+
// because one side (or maybe both sides) of the range is a
|
|
510
|
+
// function. in that case we don't want a space in front of the
|
|
511
|
+
// operator.
|
|
512
|
+
// UPDATE: for aesthetic reasons, also remove spaces around a
|
|
513
|
+
// power operator (caret, "^")
|
|
514
|
+
// FIXME: parameterize?
|
|
515
|
+
{
|
|
516
|
+
const separator = ((unit.operator === ':' || unit.operator === '^') ? '' : ' ');
|
|
517
|
+
return (this.Render(unit.left, options) +
|
|
518
|
+
separator +
|
|
519
|
+
unit.operator +
|
|
520
|
+
separator +
|
|
521
|
+
this.Render(unit.right, options));
|
|
522
|
+
}
|
|
523
|
+
case 'unary':
|
|
524
|
+
return (unit.operator +
|
|
525
|
+
this.Render(unit.operand, options));
|
|
526
|
+
case 'complex':
|
|
527
|
+
// formatting complex value (note for searching)
|
|
528
|
+
// this uses small regular "i"
|
|
529
|
+
// as with literals, we want to preserve the original text,
|
|
530
|
+
// which might have slight precision differences from what
|
|
531
|
+
// we would render.
|
|
532
|
+
if (unit.text) {
|
|
533
|
+
if (convert_decimal) {
|
|
534
|
+
// we don't support grouping numbers for complex, so there's
|
|
535
|
+
// no need to handle grouping
|
|
536
|
+
const text = unit.text;
|
|
537
|
+
return text.replace(decimal_rex_g, decimal);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
return unit.text;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
// if we don't have the original text for whatever reason, format
|
|
545
|
+
// and convert if necessary.
|
|
546
|
+
let imaginary_text = Math.abs(unit.imaginary).toString();
|
|
547
|
+
if (convert_decimal === DecimalMarkType.Comma || this.flags.decimal_mark === DecimalMarkType.Comma) {
|
|
548
|
+
imaginary_text = imaginary_text.replace(/\./, ',');
|
|
549
|
+
}
|
|
550
|
+
if (unit.real) {
|
|
551
|
+
let real_text = unit.real.toString();
|
|
552
|
+
if (convert_decimal === DecimalMarkType.Comma || this.flags.decimal_mark === DecimalMarkType.Comma) {
|
|
553
|
+
real_text = real_text.replace(/\./, ',');
|
|
554
|
+
}
|
|
555
|
+
const i = Math.abs(unit.imaginary);
|
|
556
|
+
return `${real_text}${unit.imaginary < 0 ? ' - ' : ' + '}${i === 1 ? '' : imaginary_text}i`;
|
|
557
|
+
}
|
|
558
|
+
else if (unit.imaginary === -1) {
|
|
559
|
+
return `-i`;
|
|
560
|
+
}
|
|
561
|
+
else if (unit.imaginary === 1) {
|
|
562
|
+
return `i`;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
return `${unit.imaginary < 0 ? '-' : ''}${imaginary_text}i`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
break;
|
|
569
|
+
case 'literal':
|
|
570
|
+
if (typeof unit.value === 'string') {
|
|
571
|
+
// escape any quotation marks in string
|
|
572
|
+
return '"' + unit.value.replace(/"/g, '""') + '"';
|
|
573
|
+
}
|
|
574
|
+
else if (typeof unit.value === 'boolean') {
|
|
575
|
+
// use render option (replacement) value; then flags value; then a default
|
|
576
|
+
if (unit.value) {
|
|
577
|
+
return options.boolean_true || this.flags.boolean_true || 'true'; // default
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
return options.boolean_false || this.flags.boolean_false || 'false'; // default
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
else if (convert_decimal && typeof unit.value === 'number') {
|
|
584
|
+
if (unit.text) {
|
|
585
|
+
// here we want to translate the literal typed-in value.
|
|
586
|
+
// users can type in a decimal point and possibly grouping.
|
|
587
|
+
// if we are converting from dot to comma, we need to make
|
|
588
|
+
// sure to remove any existing commas. for the time being
|
|
589
|
+
// we will just remove them.
|
|
590
|
+
// what about the alternate case? in that case, we're not allowing
|
|
591
|
+
// users to type in groupings (I think), so we can skip that part.
|
|
592
|
+
// ACTUALLY, we don't allow grouping at all. we normalize it
|
|
593
|
+
// if you type in a number. why? consider functions, grouping
|
|
594
|
+
// looks like parameter separation. so no.
|
|
595
|
+
let text = unit.text;
|
|
596
|
+
if (convert_decimal === DecimalMarkType.Comma &&
|
|
597
|
+
this.flags.decimal_mark === DecimalMarkType.Period) {
|
|
598
|
+
text = text.replace(/,/g, ''); // remove grouping
|
|
599
|
+
}
|
|
600
|
+
return text.replace(decimal_rex, decimal);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
// this always works because this function is guaranteed
|
|
604
|
+
// to return value in dot-decimal format without separators.
|
|
605
|
+
return unit.value.toString().replace(/\./, decimal);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
else if (unit.text)
|
|
609
|
+
return unit.text;
|
|
610
|
+
return unit.value.toString();
|
|
611
|
+
case 'identifier':
|
|
612
|
+
return unit.name;
|
|
613
|
+
case 'operator':
|
|
614
|
+
return '[' + unit.operator + ']'; // this should be invalid output
|
|
615
|
+
case 'group':
|
|
616
|
+
if (unit.explicit) {
|
|
617
|
+
return ('(' +
|
|
618
|
+
unit.elements
|
|
619
|
+
.map((x) => this.Render(x, options)).join(separator) +
|
|
620
|
+
')');
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
return unit.elements
|
|
624
|
+
.map((x) => this.Render(x, options)).join(separator);
|
|
625
|
+
}
|
|
626
|
+
case 'implicit-call':
|
|
627
|
+
return this.Render(unit.call, options) +
|
|
628
|
+
'(' + unit.args.map(element => this.Render(element, options)).join(separator) + ')';
|
|
629
|
+
case 'call':
|
|
630
|
+
return (unit.name +
|
|
631
|
+
'(' +
|
|
632
|
+
unit.args
|
|
633
|
+
.map((x) => this.Render(x, options)).join(separator) +
|
|
634
|
+
')');
|
|
635
|
+
case 'dimensioned':
|
|
636
|
+
return this.Render(unit.expression) + ' ' + this.Render(unit.unit);
|
|
637
|
+
case 'structured-reference':
|
|
638
|
+
// not sure of the rules around one or two braces for the
|
|
639
|
+
// column name... certainly spaces means you need at least one
|
|
640
|
+
{
|
|
641
|
+
let column = unit.column;
|
|
642
|
+
if (/[^A-Za-z]/.test(column)) {
|
|
643
|
+
column = '[' + column + ']';
|
|
644
|
+
}
|
|
645
|
+
let table = unit.table;
|
|
646
|
+
// console.info("RENDER SR", unit, table_name, long_structured_references);
|
|
647
|
+
if (!table && long_structured_references && table_name) {
|
|
648
|
+
table = table_name;
|
|
649
|
+
}
|
|
650
|
+
switch (unit.scope) {
|
|
651
|
+
case 'all':
|
|
652
|
+
return `${table}[[#all],${column}]`;
|
|
653
|
+
case 'row':
|
|
654
|
+
if (long_structured_references) {
|
|
655
|
+
return `${table}[[#this row],${column}]`;
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
return `${table}[@${column}]`;
|
|
659
|
+
}
|
|
660
|
+
case 'column':
|
|
661
|
+
return `${table}[${column}]`;
|
|
662
|
+
}
|
|
663
|
+
// this is here in case we add a new scope in the future,
|
|
664
|
+
// so we remember to handle this case
|
|
665
|
+
throw new Error('unhandled scope in structured reference');
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return '??';
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* parses expression and returns the root of the parse tree, plus a
|
|
672
|
+
* list of dependencies (addresses and ranges) found in the expression.
|
|
673
|
+
*
|
|
674
|
+
* NOTE that in the new address parsing structure, we will overlap ranges
|
|
675
|
+
* and addresses (range corners). this is OK because ranges are mapped
|
|
676
|
+
* to individual address dependencies. it's just sloppy (FIXME: refcount?)
|
|
677
|
+
*/
|
|
678
|
+
Parse(expression) {
|
|
679
|
+
// normalize
|
|
680
|
+
expression = expression.trim();
|
|
681
|
+
// remove leading =
|
|
682
|
+
if (expression[0] === '=') {
|
|
683
|
+
expression = expression.substr(1).trim();
|
|
684
|
+
}
|
|
685
|
+
this.expression = expression;
|
|
686
|
+
this.data = [];
|
|
687
|
+
this.length = expression.length;
|
|
688
|
+
this.index = 0;
|
|
689
|
+
this.valid = true;
|
|
690
|
+
this.error_position = undefined;
|
|
691
|
+
this.error = undefined;
|
|
692
|
+
this.dependencies.addresses = {};
|
|
693
|
+
this.dependencies.ranges = {};
|
|
694
|
+
this.address_refcount = {};
|
|
695
|
+
this.full_reference_list = [];
|
|
696
|
+
// reset ID
|
|
697
|
+
this.id_counter = 0;
|
|
698
|
+
// set separator
|
|
699
|
+
switch (this.flags.argument_separator) {
|
|
700
|
+
case ArgumentSeparatorType.Semicolon:
|
|
701
|
+
this.argument_separator_char = SEMICOLON;
|
|
702
|
+
break;
|
|
703
|
+
default:
|
|
704
|
+
this.argument_separator_char = COMMA;
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
// and decimal mark
|
|
708
|
+
switch (this.flags.decimal_mark) {
|
|
709
|
+
case DecimalMarkType.Comma:
|
|
710
|
+
this.decimal_mark_char = COMMA;
|
|
711
|
+
break;
|
|
712
|
+
default:
|
|
713
|
+
this.decimal_mark_char = PERIOD;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
// NOTE on this function: charCodeAt returns UTF-16. codePointAt returns
|
|
717
|
+
// unicode. length returns UTF-16 length. any characters that are not
|
|
718
|
+
// representable as a single character in UTF-16 will be 'the first unit
|
|
719
|
+
// of a surrogate pair...' and so on.
|
|
720
|
+
//
|
|
721
|
+
// we want UTF-16, not unicode. for the parser itself, we are only really
|
|
722
|
+
// looking for ASCII, so it's not material. for anything else, if we
|
|
723
|
+
// construct strings from the original data we want to map the UTF-16,
|
|
724
|
+
// otherwise we will construct the string incorrectly. this applies to
|
|
725
|
+
// strings, function names, and anything else.
|
|
726
|
+
//
|
|
727
|
+
// which is all a long way of saying, don't be tempted to replace this
|
|
728
|
+
// with codePointAt.
|
|
729
|
+
for (let i = 0; i < this.length; i++) {
|
|
730
|
+
this.data[i] = expression.charCodeAt(i);
|
|
731
|
+
}
|
|
732
|
+
const expr = this.ParseGeneric();
|
|
733
|
+
// last pass: convert any remaining imaginary values to complex values.
|
|
734
|
+
// FIXME: could do this elsewhere? not sure we should be adding yet
|
|
735
|
+
// another loop...
|
|
736
|
+
// (moving)
|
|
737
|
+
// remove extraneous addresses
|
|
738
|
+
// NOTE: we still may have duplicates that have different absolute/relative
|
|
739
|
+
// modifiers, e.g. C3 and $C$3 (and $C3 and C$3). not sure what we should
|
|
740
|
+
// do about that, since some consumers may consider these different -- we
|
|
741
|
+
// need to establish a contract about this
|
|
742
|
+
const addresses = {};
|
|
743
|
+
for (const key of Object.keys(this.dependencies.addresses)) {
|
|
744
|
+
if (this.address_refcount[key]) {
|
|
745
|
+
addresses[key] = this.dependencies.addresses[key];
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
this.dependencies.addresses = addresses;
|
|
749
|
+
return {
|
|
750
|
+
expression: expr || undefined,
|
|
751
|
+
valid: this.valid,
|
|
752
|
+
error: this.error,
|
|
753
|
+
error_position: this.error_position,
|
|
754
|
+
dependencies: this.dependencies,
|
|
755
|
+
separator: this.flags.argument_separator,
|
|
756
|
+
decimal_mark: this.flags.decimal_mark,
|
|
757
|
+
full_reference_list: this.full_reference_list.slice(0),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
/** generates column label ("A") from column index (0-based) */
|
|
761
|
+
ColumnLabel(column) {
|
|
762
|
+
if (column === Infinity) {
|
|
763
|
+
return '';
|
|
764
|
+
}
|
|
765
|
+
let s = String.fromCharCode(65 + (column % 26));
|
|
766
|
+
while (column > 25) {
|
|
767
|
+
column = Math.floor(column / 26) - 1;
|
|
768
|
+
s = String.fromCharCode(65 + (column % 26)) + s;
|
|
769
|
+
}
|
|
770
|
+
return s;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* generates absolute or relative R1C1 address
|
|
774
|
+
*
|
|
775
|
+
* FIXME: not supporting relative (offset) addresses atm? I'd like to
|
|
776
|
+
* change this but I don't want to break anything...
|
|
777
|
+
*/
|
|
778
|
+
R1C1Label(address, options) {
|
|
779
|
+
const force_relative = !!options.r1c1_force_relative;
|
|
780
|
+
const base = options.r1c1_base;
|
|
781
|
+
let label = '';
|
|
782
|
+
if (address.sheet) { // && (!base?.sheet || base?.sheet !== address.sheet)) {
|
|
783
|
+
label = (QuotedSheetNameRegex.test(address.sheet) ?
|
|
784
|
+
'\'' + address.sheet + '\'' : address.sheet) + '!';
|
|
785
|
+
}
|
|
786
|
+
let row = '';
|
|
787
|
+
let column = '';
|
|
788
|
+
if (force_relative && options.r1c1_proper_semantics && base) {
|
|
789
|
+
if (address.absolute_row) {
|
|
790
|
+
row = (address.row + 1).toString();
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
const delta_row = address.row - base.row;
|
|
794
|
+
if (delta_row) {
|
|
795
|
+
row = `[${delta_row}]`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (address.absolute_column) {
|
|
799
|
+
column = (address.column + 1).toString();
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
const delta_column = address.column - base.column;
|
|
803
|
+
if (delta_column) {
|
|
804
|
+
column = `[${delta_column}]`;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
else if (force_relative && base) {
|
|
809
|
+
const delta_row = address.row - base.row;
|
|
810
|
+
const delta_column = address.column - base.column;
|
|
811
|
+
if (delta_row) {
|
|
812
|
+
row = `[${delta_row}]`;
|
|
813
|
+
}
|
|
814
|
+
if (delta_column) {
|
|
815
|
+
column = `[${delta_column}]`;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
row = address.offset_row ? `[${address.row}]` : (address.row + 1).toString();
|
|
820
|
+
column = address.offset_column ? `[${address.column}]` : (address.column + 1).toString();
|
|
821
|
+
}
|
|
822
|
+
/*
|
|
823
|
+
const row = (address.absolute_row || !base) ? (address.row + 1).toString() : `[${address.row - base.row}]`;
|
|
824
|
+
const column = (address.absolute_column || !base) ? (address.column + 1).toString() : `[${address.column - base.column}]`;
|
|
825
|
+
*/
|
|
826
|
+
label += `R${row}C${column}`;
|
|
827
|
+
return label;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* generates address label ("C3") from address (0-based).
|
|
831
|
+
*
|
|
832
|
+
* @param offset - offset by some number of rows or columns
|
|
833
|
+
* @param r1c1 - if set, return data in R1C1 format.
|
|
834
|
+
*/
|
|
835
|
+
AddressLabel(address, offset) {
|
|
836
|
+
let column = address.column;
|
|
837
|
+
if (!address.absolute_column && address.column !== Infinity)
|
|
838
|
+
column += offset.columns;
|
|
839
|
+
let row = address.row;
|
|
840
|
+
if (!address.absolute_row && address.row !== Infinity)
|
|
841
|
+
row += offset.rows;
|
|
842
|
+
if (row < 0 || column < 0 || (row === Infinity && column === Infinity))
|
|
843
|
+
return '#REF';
|
|
844
|
+
let label = '';
|
|
845
|
+
if (address.sheet) {
|
|
846
|
+
label = (QuotedSheetNameRegex.test(address.sheet) ?
|
|
847
|
+
'\'' + address.sheet + '\'' : address.sheet) + '!';
|
|
848
|
+
}
|
|
849
|
+
if (row === Infinity) {
|
|
850
|
+
return label +
|
|
851
|
+
(address.absolute_column ? '$' : '') +
|
|
852
|
+
this.ColumnLabel(column);
|
|
853
|
+
}
|
|
854
|
+
if (column === Infinity) {
|
|
855
|
+
return label +
|
|
856
|
+
(address.absolute_row ? '$' : '') +
|
|
857
|
+
(row + 1);
|
|
858
|
+
}
|
|
859
|
+
return (label +
|
|
860
|
+
(address.absolute_column ? '$' : '') +
|
|
861
|
+
this.ColumnLabel(column) +
|
|
862
|
+
(address.absolute_row ? '$' : '') +
|
|
863
|
+
(row + 1) +
|
|
864
|
+
(address.spill ? '#' : ''));
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* base parse routine; may recurse inside parens (either as grouped
|
|
868
|
+
* operations or in function arguments).
|
|
869
|
+
*
|
|
870
|
+
* @param exit exit on specific characters
|
|
871
|
+
*/
|
|
872
|
+
ParseGeneric(exit = [0], explicit_group = false) {
|
|
873
|
+
let stream = [];
|
|
874
|
+
for (; this.index < this.length;) {
|
|
875
|
+
const unit = this.ParseNext(stream.length === 0);
|
|
876
|
+
if (typeof unit === 'number') {
|
|
877
|
+
if (exit.some((test) => unit === test)) {
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
else if (unit === OPEN_PAREN) {
|
|
881
|
+
// note that function calls are handled elsewhere,
|
|
882
|
+
// so we only have to worry about grouping. parse
|
|
883
|
+
// up to the closing paren...
|
|
884
|
+
// actually now we have implicit calls, so we need
|
|
885
|
+
// to manage that here.
|
|
886
|
+
this.index++; // open paren
|
|
887
|
+
const group = this.ParseGeneric([CLOSE_PAREN], true);
|
|
888
|
+
this.index++; // close paren
|
|
889
|
+
// and wrap up in a group element to prevent reordering.
|
|
890
|
+
// flag indicates that this is a user grouping, not ours
|
|
891
|
+
// skip nulls
|
|
892
|
+
// ...don't skip nulls? don't know what the rationale was
|
|
893
|
+
// but for implicit calls we will need to support empty arguments
|
|
894
|
+
// if (group) {
|
|
895
|
+
stream.push({
|
|
896
|
+
type: 'group',
|
|
897
|
+
id: this.id_counter++,
|
|
898
|
+
elements: group ? [group] : [],
|
|
899
|
+
explicit: true,
|
|
900
|
+
});
|
|
901
|
+
//}
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
// this can probably move to PNext? except for the test
|
|
905
|
+
// on looking for a binary operator? (...)
|
|
906
|
+
const operator = this.ConsumeOperator();
|
|
907
|
+
if (operator) {
|
|
908
|
+
stream.push(operator);
|
|
909
|
+
}
|
|
910
|
+
else if (explicit_group && unit === this.argument_separator_char) {
|
|
911
|
+
// adding a new unit type here to explicitly show we're in
|
|
912
|
+
// a group; prevents later passes from treating arguments as
|
|
913
|
+
// fractions or something else. we just need to remove these
|
|
914
|
+
// later
|
|
915
|
+
stream.push({
|
|
916
|
+
type: 'group-separator',
|
|
917
|
+
position: this.index,
|
|
918
|
+
id: this.id_counter++,
|
|
919
|
+
});
|
|
920
|
+
this.index++;
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
this.error = `unexpected character [1]: ${String.fromCharCode(unit)}, 0x${unit.toString(16)}`;
|
|
924
|
+
this.valid = false;
|
|
925
|
+
this.index++;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
stream.push(unit);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
// why do we build ranges after doing reordering? since ranges
|
|
934
|
+
// have the highest precedence (after complex numbers), why not
|
|
935
|
+
// just run through them now? also we could merge the complex
|
|
936
|
+
// composition (or not, since that's optional)
|
|
937
|
+
// ...
|
|
938
|
+
// OK, doing that now (testing). a side benefit is that this solves
|
|
939
|
+
// one of the problems we had with complex numbers, mismatching naked
|
|
940
|
+
// column identifiers like I:J. if we do ranges first we will not run
|
|
941
|
+
// into that problem.
|
|
942
|
+
if (stream.length) {
|
|
943
|
+
stream = this.BinaryToRange2(stream);
|
|
944
|
+
// FIXME: fractions should perhaps move, not sure about the proper
|
|
945
|
+
// ordering...
|
|
946
|
+
if (this.flags.fractions) {
|
|
947
|
+
// the specific pattern we are looking for for a fraction is
|
|
948
|
+
//
|
|
949
|
+
// literal (integer)
|
|
950
|
+
// literal (integer)
|
|
951
|
+
// operator (/)
|
|
952
|
+
// literal (integer)
|
|
953
|
+
//
|
|
954
|
+
// NOTE: excel actually translates these functions after you
|
|
955
|
+
// enter them to remove the fractions. not sure why, but it's
|
|
956
|
+
// possible that exporting them to something else (lotus?) wouldn't
|
|
957
|
+
// work. we can export them to excel, however, so maybe we can just
|
|
958
|
+
// leave as-is.
|
|
959
|
+
const rebuilt = [];
|
|
960
|
+
const IsInteger = (test) => {
|
|
961
|
+
return (test.type === 'literal')
|
|
962
|
+
&& ((typeof test.value) === 'number')
|
|
963
|
+
&& (test.value % 1 === 0); // bad typescript
|
|
964
|
+
};
|
|
965
|
+
let i = 0;
|
|
966
|
+
for (; i < stream.length - 3; i++) {
|
|
967
|
+
if (IsInteger(stream[i])
|
|
968
|
+
&& IsInteger(stream[i + 1])
|
|
969
|
+
&& (stream[i + 2].type === 'operator' && stream[i + 2].operator === '/')
|
|
970
|
+
&& IsInteger(stream[i + 3])) {
|
|
971
|
+
const a = stream[i];
|
|
972
|
+
const b = stream[i + 1];
|
|
973
|
+
const c = stream[i + 3];
|
|
974
|
+
const f = ((a.value < 0) ? -1 : 1) * (b.value / c.value);
|
|
975
|
+
i += 3;
|
|
976
|
+
rebuilt.push({
|
|
977
|
+
id: stream[i].id,
|
|
978
|
+
type: 'literal',
|
|
979
|
+
text: this.expression.substring(a.position, c.position + 1),
|
|
980
|
+
value: a.value + f,
|
|
981
|
+
position: a.position,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
rebuilt.push(stream[i]);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
for (; i < stream.length; i++) {
|
|
989
|
+
rebuilt.push(stream[i]);
|
|
990
|
+
}
|
|
991
|
+
stream = rebuilt;
|
|
992
|
+
}
|
|
993
|
+
// so we're moving complex handling to post-reordering, to support
|
|
994
|
+
// precedence properly. there's still one thing we have to do here,
|
|
995
|
+
// though: handle those cases of naked imaginary values "i". these
|
|
996
|
+
// will be text identifiers, because they don't look like anything
|
|
997
|
+
// else. the previous routine will have pulled out column ranges like
|
|
998
|
+
// I:I so we don't have to worry about that anymore.
|
|
999
|
+
stream = stream.map(test => {
|
|
1000
|
+
if (test.type === 'identifier' && test.name === this.imaginary_number) {
|
|
1001
|
+
return {
|
|
1002
|
+
type: 'complex',
|
|
1003
|
+
real: 0,
|
|
1004
|
+
imaginary: 1,
|
|
1005
|
+
position: test.position,
|
|
1006
|
+
text: test.name,
|
|
1007
|
+
id: this.id_counter++,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
return test;
|
|
1011
|
+
});
|
|
1012
|
+
if (this.flags.dimensioned_quantities) {
|
|
1013
|
+
// support dimensioned quantities. we need to think a little about what
|
|
1014
|
+
// should and should not be supported here -- definitely a literal
|
|
1015
|
+
// followed by an identifier; definitely not two identifiers in a row;
|
|
1016
|
+
// (really?) definitely not expressions followed by identifiers...
|
|
1017
|
+
//
|
|
1018
|
+
// what about
|
|
1019
|
+
// group: (3+2)mm [yes]
|
|
1020
|
+
// call: sin(3)mm [yes]
|
|
1021
|
+
// name?: Xmm [...]
|
|
1022
|
+
//
|
|
1023
|
+
// what about space?
|
|
1024
|
+
// 10 fluid ounces
|
|
1025
|
+
// 10 fl oz
|
|
1026
|
+
//
|
|
1027
|
+
const rebuilt = [];
|
|
1028
|
+
let unit;
|
|
1029
|
+
for (let i = 0; i < stream.length; i++) {
|
|
1030
|
+
//for (const entry of stream) {
|
|
1031
|
+
const entry = stream[i];
|
|
1032
|
+
if (!unit) {
|
|
1033
|
+
unit = entry;
|
|
1034
|
+
}
|
|
1035
|
+
else if (entry.type === 'identifier' && (unit.type === 'literal' || unit.type === 'group' || unit.type === 'call')) {
|
|
1036
|
+
// check for multi-word unit (unit has spaces)
|
|
1037
|
+
const identifier = entry;
|
|
1038
|
+
while (stream[i + 1]?.type === 'identifier') {
|
|
1039
|
+
identifier.name += (' ' + stream[++i].name);
|
|
1040
|
+
}
|
|
1041
|
+
rebuilt.push({
|
|
1042
|
+
type: 'dimensioned',
|
|
1043
|
+
expression: unit,
|
|
1044
|
+
unit: entry,
|
|
1045
|
+
id: this.id_counter++,
|
|
1046
|
+
});
|
|
1047
|
+
unit = undefined; // consume
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
rebuilt.push(unit);
|
|
1051
|
+
unit = entry;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
// trailer
|
|
1055
|
+
if (unit) {
|
|
1056
|
+
rebuilt.push(unit);
|
|
1057
|
+
}
|
|
1058
|
+
stream = rebuilt;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
// console.info("STREAM\n", stream, "\n\n");
|
|
1062
|
+
if (stream.length === 0)
|
|
1063
|
+
return null;
|
|
1064
|
+
if (stream.length === 1)
|
|
1065
|
+
return stream[0];
|
|
1066
|
+
// fix ordering of binary operations based on precedence; also
|
|
1067
|
+
// convert and validate ranges
|
|
1068
|
+
return this.BinaryToComplex(this.ArrangeUnits(stream));
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* helper function, @see BinaryToRange
|
|
1072
|
+
* @param unit
|
|
1073
|
+
* @returns
|
|
1074
|
+
*/
|
|
1075
|
+
UnitToAddress(unit) {
|
|
1076
|
+
// console.info("U2", unit);
|
|
1077
|
+
// for literals, only numbers are valid
|
|
1078
|
+
if (unit.type === 'literal') {
|
|
1079
|
+
if (typeof unit.value === 'number' && unit.value > 0 && !/\./.test(unit.text || '')) {
|
|
1080
|
+
return {
|
|
1081
|
+
type: 'address',
|
|
1082
|
+
position: unit.position,
|
|
1083
|
+
label: unit.value.toString(),
|
|
1084
|
+
row: unit.value - 1,
|
|
1085
|
+
id: this.id_counter++,
|
|
1086
|
+
column: Infinity,
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
// UPDATE: sheet names... we may actually need a subparser for this?
|
|
1092
|
+
// or can we do it with a regex? (...)
|
|
1093
|
+
let sheet;
|
|
1094
|
+
let name = unit.name;
|
|
1095
|
+
const tokens = name.split('!');
|
|
1096
|
+
if (tokens.length > 1) {
|
|
1097
|
+
sheet = tokens.slice(0, tokens.length - 1).join('!');
|
|
1098
|
+
name = name.substr(sheet.length + 1);
|
|
1099
|
+
if (sheet[0] === '\'') {
|
|
1100
|
+
if (sheet.length > 1 && sheet[sheet.length - 1] === '\'') {
|
|
1101
|
+
sheet = sheet.substr(1, sheet.length - 2);
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
// console.info('mismatched single quote');
|
|
1105
|
+
return undefined;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
const absolute = name[0] === '$';
|
|
1110
|
+
name = (absolute ? name.substr(1) : name).toUpperCase();
|
|
1111
|
+
const as_number = Number(name);
|
|
1112
|
+
// if it looks like a number, consider it a number and then be strict
|
|
1113
|
+
if (!isNaN(as_number)) {
|
|
1114
|
+
if (as_number > 0 && as_number !== Infinity && !/\./.test(name)) {
|
|
1115
|
+
return {
|
|
1116
|
+
type: 'address',
|
|
1117
|
+
position: unit.position,
|
|
1118
|
+
absolute_row: absolute,
|
|
1119
|
+
label: unit.name,
|
|
1120
|
+
row: as_number - 1,
|
|
1121
|
+
id: this.id_counter++,
|
|
1122
|
+
column: Infinity,
|
|
1123
|
+
sheet,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
else if (/[A-Z]{1,3}/.test(name)) {
|
|
1128
|
+
let column = -1; // clever
|
|
1129
|
+
for (let i = 0; i < name.length; i++) {
|
|
1130
|
+
const char = name[i].charCodeAt(0);
|
|
1131
|
+
column = 26 * (1 + column) + (char - UC_A);
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
type: 'address',
|
|
1135
|
+
position: unit.position,
|
|
1136
|
+
absolute_column: absolute,
|
|
1137
|
+
label: unit.name,
|
|
1138
|
+
column,
|
|
1139
|
+
id: this.id_counter++,
|
|
1140
|
+
row: Infinity,
|
|
1141
|
+
sheet,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return undefined;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* rewrite of binary to range. this version operates on the initial stream,
|
|
1149
|
+
* which should be OK because range has the highest precedence so we would
|
|
1150
|
+
* never reorder a range.
|
|
1151
|
+
*
|
|
1152
|
+
* ACTUALLY this will break in the case of
|
|
1153
|
+
*
|
|
1154
|
+
* -15:16
|
|
1155
|
+
*
|
|
1156
|
+
* (I think that's the only case). we can fix that though. this should
|
|
1157
|
+
* not impact the case of `2-15:16`, because in that case the - will look
|
|
1158
|
+
* like an operator and not part of the number. the same goes for a leading
|
|
1159
|
+
* `+` which will get dropped implicitly but has no effect (we might want
|
|
1160
|
+
* to preserve it for consistency though).
|
|
1161
|
+
*
|
|
1162
|
+
* NOTE: that error existed in the old version, too, and this way is perhaps
|
|
1163
|
+
* better for fixing it. we should merge this into main.
|
|
1164
|
+
*
|
|
1165
|
+
*
|
|
1166
|
+
* old version comments:
|
|
1167
|
+
* ---
|
|
1168
|
+
*
|
|
1169
|
+
* converts binary operations with a colon operator to ranges. this also
|
|
1170
|
+
* validates that there are no colon operations with non-address operands
|
|
1171
|
+
* (which is why it's called after precendence reordering; colon has the
|
|
1172
|
+
* highest preference). recursive only over binary ops AND unary ops.
|
|
1173
|
+
*
|
|
1174
|
+
* NOTE: there are other legal arguments to a colon operator. specifically:
|
|
1175
|
+
*
|
|
1176
|
+
* (1) two numbers, in either order
|
|
1177
|
+
*
|
|
1178
|
+
* 15:16
|
|
1179
|
+
* 16:16
|
|
1180
|
+
* 16:15
|
|
1181
|
+
*
|
|
1182
|
+
* (2) with one or both optionally having a $
|
|
1183
|
+
*
|
|
1184
|
+
* 15:$16
|
|
1185
|
+
* $16:$16
|
|
1186
|
+
*
|
|
1187
|
+
* (3) two column identifiers, in either order
|
|
1188
|
+
*
|
|
1189
|
+
* A:F
|
|
1190
|
+
* B:A
|
|
1191
|
+
*
|
|
1192
|
+
* (4) and the same with $
|
|
1193
|
+
*
|
|
1194
|
+
* $A:F
|
|
1195
|
+
* $A:$F
|
|
1196
|
+
*
|
|
1197
|
+
* because none of these are legal in any other context, we leave the
|
|
1198
|
+
* default treatment of them UNLESS they are arguments to the colon
|
|
1199
|
+
* operator, in which case we will grab them. that does mean we parse
|
|
1200
|
+
* them twice, but (...)
|
|
1201
|
+
*
|
|
1202
|
+
* FIXME: will need some updated to rendering these, we don't have any
|
|
1203
|
+
* handler for rendering infinity
|
|
1204
|
+
*/
|
|
1205
|
+
BinaryToRange2(stream) {
|
|
1206
|
+
const result = [];
|
|
1207
|
+
for (let i = 0; i < stream.length; i++) {
|
|
1208
|
+
const a = stream[i];
|
|
1209
|
+
const b = stream[i + 1];
|
|
1210
|
+
const c = stream[i + 2];
|
|
1211
|
+
let range;
|
|
1212
|
+
let label = '';
|
|
1213
|
+
let negative; // this is a fix for the error case `-14:15`, see below
|
|
1214
|
+
if (a && b && c && b.type === 'operator' && b.operator === ':') {
|
|
1215
|
+
if (a.type === 'address' && c.type === 'address') {
|
|
1216
|
+
// construct a label using the full text. there's a possibility,
|
|
1217
|
+
// I suppose, that there are spaces (this should probably not be
|
|
1218
|
+
// legal). this is a canonical label, though (generated)
|
|
1219
|
+
// it might be better to let this slip, or treat it as an error
|
|
1220
|
+
// and force a correction... not sure (TODO/FIXME)
|
|
1221
|
+
const start_index = a.position + a.label.length;
|
|
1222
|
+
const end_index = c.position;
|
|
1223
|
+
range = {
|
|
1224
|
+
type: 'range',
|
|
1225
|
+
id: this.id_counter++,
|
|
1226
|
+
position: a.position,
|
|
1227
|
+
start: a,
|
|
1228
|
+
end: c,
|
|
1229
|
+
label: a.label +
|
|
1230
|
+
this.expression.substring(start_index, end_index) +
|
|
1231
|
+
c.label,
|
|
1232
|
+
};
|
|
1233
|
+
label = range.start.label + ':' + range.end.label;
|
|
1234
|
+
this.address_refcount[range.start.label]--;
|
|
1235
|
+
this.address_refcount[range.end.label]--;
|
|
1236
|
+
// remove entries from the list for start, stop
|
|
1237
|
+
const positions = [a.position, c.position];
|
|
1238
|
+
this.full_reference_list = this.full_reference_list.filter((test) => {
|
|
1239
|
+
return (test.position !== positions[0] && test.position !== positions[1]);
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
else if ((a.type === 'literal' || a.type === 'identifier')
|
|
1243
|
+
&& (c.type === 'literal' || c.type === 'identifier')) {
|
|
1244
|
+
// see if we can plausibly interpret both of these as rows or columns
|
|
1245
|
+
// this is a fix for the case of `-14:15`, which is kind of a rare
|
|
1246
|
+
// case but could happen. in that case we need to invert the first number,
|
|
1247
|
+
// so it parses as an address properly, and also insert a "-" which
|
|
1248
|
+
// should be treated as a unary operator.
|
|
1249
|
+
// if this happens, the first part must look like a negative number,
|
|
1250
|
+
// e.g. -10, so there are no leading spaces or intervening spaces
|
|
1251
|
+
// between the - and the value. therefore...
|
|
1252
|
+
let left = this.UnitToAddress(a);
|
|
1253
|
+
if (!left && a.type === 'literal' && typeof a.value === 'number' && a.value < 0) {
|
|
1254
|
+
const test = {
|
|
1255
|
+
...a,
|
|
1256
|
+
text: (a.text || '').replace(/^-/, ''), // <- ...sign always in position 0
|
|
1257
|
+
position: a.position + 1, // <- ...advance 1
|
|
1258
|
+
value: -a.value, // <- ...invert value
|
|
1259
|
+
};
|
|
1260
|
+
left = this.UnitToAddress(test);
|
|
1261
|
+
if (left) {
|
|
1262
|
+
// if that worked, we need to insert an operator into the
|
|
1263
|
+
// stream to reflect the - sign. we use the original position.
|
|
1264
|
+
negative = {
|
|
1265
|
+
type: 'operator',
|
|
1266
|
+
operator: '-',
|
|
1267
|
+
position: a.position,
|
|
1268
|
+
id: this.id_counter++,
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const right = this.UnitToAddress(c);
|
|
1273
|
+
// and they need to match
|
|
1274
|
+
if (left && right
|
|
1275
|
+
&& ((left.column === Infinity && right.column === Infinity)
|
|
1276
|
+
|| (left.row === Infinity && right.row === Infinity))) {
|
|
1277
|
+
label = left.label + ':' + right.label;
|
|
1278
|
+
// we don't support out-of-order ranges, so we should correct.
|
|
1279
|
+
// they just won't work otherwise. (TODO/FIXME)
|
|
1280
|
+
range = {
|
|
1281
|
+
type: 'range',
|
|
1282
|
+
id: this.id_counter++,
|
|
1283
|
+
position: left.position,
|
|
1284
|
+
start: left,
|
|
1285
|
+
end: right,
|
|
1286
|
+
label,
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (range) {
|
|
1292
|
+
if (negative) {
|
|
1293
|
+
result.push(negative);
|
|
1294
|
+
}
|
|
1295
|
+
result.push(range);
|
|
1296
|
+
this.dependencies.ranges[label] = range;
|
|
1297
|
+
this.full_reference_list.push(range);
|
|
1298
|
+
// skip
|
|
1299
|
+
i += 2;
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
result.push(a);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return result;
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* we've now come full circle. we started with handling ranges as
|
|
1309
|
+
* binary operators; then we added complex composition as a first-pass
|
|
1310
|
+
* function; then we moved ranges to a first-pass function; and now we're
|
|
1311
|
+
* moving complex composition to a lower-level restructuring of binary
|
|
1312
|
+
* operations.
|
|
1313
|
+
*
|
|
1314
|
+
* that allows better precedence handling for (potentially) ambiguous
|
|
1315
|
+
* constructions like =B3 * 2 + 3i. we do have parens, so.
|
|
1316
|
+
*
|
|
1317
|
+
* @param unit
|
|
1318
|
+
* @returns
|
|
1319
|
+
*/
|
|
1320
|
+
BinaryToComplex(unit) {
|
|
1321
|
+
if (unit.type === 'binary') {
|
|
1322
|
+
if ((unit.operator === '+' || unit.operator === '-')
|
|
1323
|
+
&& unit.left.type === 'literal'
|
|
1324
|
+
&& typeof unit.left.value === 'number'
|
|
1325
|
+
&& unit.right.type === 'complex' // 'imaginary') {
|
|
1326
|
+
&& !unit.right.composited) {
|
|
1327
|
+
// ok, compose
|
|
1328
|
+
// console.info("WANT TO COMPOSE", unit);
|
|
1329
|
+
let text = '';
|
|
1330
|
+
text = this.expression.substring(unit.left.position, unit.right.position + (unit.right.text?.length || 0));
|
|
1331
|
+
let imaginary_value = unit.right.imaginary;
|
|
1332
|
+
if (unit.operator === '-') {
|
|
1333
|
+
imaginary_value = -imaginary_value;
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
type: 'complex',
|
|
1337
|
+
position: unit.left.position,
|
|
1338
|
+
text: text,
|
|
1339
|
+
id: this.id_counter++,
|
|
1340
|
+
imaginary: imaginary_value,
|
|
1341
|
+
real: unit.left.value,
|
|
1342
|
+
composited: true,
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
unit.left = this.BinaryToComplex(unit.left);
|
|
1347
|
+
unit.right = this.BinaryToComplex(unit.right);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
else if (unit.type === 'unary' &&
|
|
1351
|
+
(unit.operator === '-' || unit.operator === '+') &&
|
|
1352
|
+
unit.operand.type === 'complex' &&
|
|
1353
|
+
unit.operand.text === this.imaginary_number) {
|
|
1354
|
+
// sigh... patch fix for very special case of "-i"
|
|
1355
|
+
// actually: why do I care about this? we could let whomever is using
|
|
1356
|
+
// the result deal with this particular case... although it's more
|
|
1357
|
+
// properly our responsibility if we are parsing complex numbers.
|
|
1358
|
+
// we only have to worry about mischaracterizing the range label,
|
|
1359
|
+
// e.g. "-i:j", but we should have already handled that in a prior pass.
|
|
1360
|
+
return {
|
|
1361
|
+
...unit.operand,
|
|
1362
|
+
position: unit.position,
|
|
1363
|
+
text: this.expression.substring(unit.position, unit.operand.position + (unit.operand.text || '').length),
|
|
1364
|
+
imaginary: unit.operand.imaginary * (unit.operator === '-' ? -1 : 1),
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
return unit;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* reorders operations for precendence
|
|
1371
|
+
*
|
|
1372
|
+
* this method was written with the assumption that groups were
|
|
1373
|
+
* always an error. that's no longer true, with implicit calls.
|
|
1374
|
+
* we should still error if it's not an _explicit_ group, i.e. there's
|
|
1375
|
+
* just a bunch of naked tokens.
|
|
1376
|
+
*
|
|
1377
|
+
*/
|
|
1378
|
+
ArrangeUnits(stream) {
|
|
1379
|
+
// probably should not happen
|
|
1380
|
+
if (stream.length === 0)
|
|
1381
|
+
return { type: 'missing', id: this.id_counter++ };
|
|
1382
|
+
// this is probably already covered
|
|
1383
|
+
if (stream.length === 1)
|
|
1384
|
+
return stream[0];
|
|
1385
|
+
const stack = [];
|
|
1386
|
+
// work left-to-right (implied precendence), unless there
|
|
1387
|
+
// is actual precendence. spreadsheet language only supports
|
|
1388
|
+
// binary operators, so we always expect unit - operator - unit
|
|
1389
|
+
//
|
|
1390
|
+
// UPDATE: that's incorrect. SL supports unary + and - operators.
|
|
1391
|
+
// which makes this more complicated.
|
|
1392
|
+
//
|
|
1393
|
+
// we explicitly support unfinished expressions for the first pass
|
|
1394
|
+
// to build dependencies, but if they're invalid the resulting
|
|
1395
|
+
// parse tree isn't expected to be correct. in that case we
|
|
1396
|
+
// generally will pass back a bag of parts, with a flag set.
|
|
1397
|
+
for (let index = 0; index < stream.length; index++) {
|
|
1398
|
+
let element = stream[index];
|
|
1399
|
+
if (element.type === 'group-separator') {
|
|
1400
|
+
continue; // drop
|
|
1401
|
+
}
|
|
1402
|
+
// given that we need to support unary operators, the logic needs
|
|
1403
|
+
// to be a little different. operators are OK at any position, provided
|
|
1404
|
+
// we can construct either a unary or binary operation.
|
|
1405
|
+
if (element.type === 'operator') {
|
|
1406
|
+
if (stack.length === 0 || stack[stack.length - 1].type === 'operator') {
|
|
1407
|
+
// valid if unary operator and we can construct a unary operation.
|
|
1408
|
+
// in this case we do it with recursion.
|
|
1409
|
+
if (unary_operators[element.operator]) {
|
|
1410
|
+
const right = this.BinaryToComplex(this.ArrangeUnits(stream.slice(index + 1)));
|
|
1411
|
+
// this ensures we return the highest-level group, even if we recurse
|
|
1412
|
+
if (!this.valid) {
|
|
1413
|
+
return {
|
|
1414
|
+
type: 'group',
|
|
1415
|
+
id: this.id_counter++,
|
|
1416
|
+
elements: stream,
|
|
1417
|
+
explicit: false,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
// if it succeeded, then we need to apply the unary operator to
|
|
1421
|
+
// the result, or if it's a binary operation, to the left-hand side
|
|
1422
|
+
// (because we have precedence) -- unless it's a range [this is now
|
|
1423
|
+
// handled above]
|
|
1424
|
+
if (right.type === 'binary') {
|
|
1425
|
+
right.left = {
|
|
1426
|
+
type: 'unary',
|
|
1427
|
+
id: this.id_counter++,
|
|
1428
|
+
operator: element.operator,
|
|
1429
|
+
operand: right.left,
|
|
1430
|
+
position: element.position,
|
|
1431
|
+
};
|
|
1432
|
+
element = right;
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
// create a unary operation which will replace the element
|
|
1436
|
+
element = {
|
|
1437
|
+
type: 'unary',
|
|
1438
|
+
id: this.id_counter++,
|
|
1439
|
+
operator: element.operator,
|
|
1440
|
+
operand: right,
|
|
1441
|
+
position: element.position,
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
// end loop after this pass, because the recurse consumes everything else
|
|
1445
|
+
index = stream.length;
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
this.error = `unexpected character [2]: ${element.operator}`;
|
|
1449
|
+
this.error_position = element.position;
|
|
1450
|
+
this.valid = false;
|
|
1451
|
+
return {
|
|
1452
|
+
type: 'group',
|
|
1453
|
+
id: this.id_counter++,
|
|
1454
|
+
elements: stream,
|
|
1455
|
+
explicit: false,
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
else {
|
|
1460
|
+
stack.push(element);
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
//
|
|
1465
|
+
// why is this 2? are we thinking about combining complex numbers?
|
|
1466
|
+
// or ranges? (those would be binary). or was this for dimensioned
|
|
1467
|
+
// quantities? [actually that makes sense] [A: no, it wasn't that]
|
|
1468
|
+
//
|
|
1469
|
+
// actually what's the case where this is triggered and it's _not_
|
|
1470
|
+
// an error? can we find that?
|
|
1471
|
+
//
|
|
1472
|
+
if (stack.length < 2) {
|
|
1473
|
+
// we know that `element` is not an operator, because we
|
|
1474
|
+
// would have consumed it
|
|
1475
|
+
if (stack.length === 1) {
|
|
1476
|
+
const a = stack[0].type;
|
|
1477
|
+
// support for lambdas
|
|
1478
|
+
if (element.type === 'group' && element.explicit) {
|
|
1479
|
+
if (a === 'address' || a === 'call' || a === 'identifier' || a === 'implicit-call') {
|
|
1480
|
+
// our parser seems to create implicit groups from these
|
|
1481
|
+
// values in parens. we should fix that, but we can unpack it.
|
|
1482
|
+
let args = element.elements;
|
|
1483
|
+
if (args.length === 1 && args[0].type === 'group' && !args[0].explicit) {
|
|
1484
|
+
args = args[0].elements;
|
|
1485
|
+
}
|
|
1486
|
+
// create an implicit call. replace on the stack.
|
|
1487
|
+
stack[0] = {
|
|
1488
|
+
type: 'implicit-call',
|
|
1489
|
+
call: stack[0],
|
|
1490
|
+
args,
|
|
1491
|
+
id: this.id_counter++,
|
|
1492
|
+
position: stack[0].position,
|
|
1493
|
+
};
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
/*
|
|
1498
|
+
else if (a !== 'operator') {
|
|
1499
|
+
|
|
1500
|
+
// console.warn("unexpected element", stack[0], element);
|
|
1501
|
+
|
|
1502
|
+
this.error = `unexpected element [3]: ${element.type}`;
|
|
1503
|
+
this.error_position = (element.type === 'missing' || element.type === 'group' || element.type === 'dimensioned') ? -1 : element.position;
|
|
1504
|
+
this.valid = false;
|
|
1505
|
+
return {
|
|
1506
|
+
type: 'group',
|
|
1507
|
+
id: this.id_counter++,
|
|
1508
|
+
elements: stream,
|
|
1509
|
+
explicit: false,
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
}
|
|
1513
|
+
*/
|
|
1514
|
+
}
|
|
1515
|
+
stack.push(element);
|
|
1516
|
+
}
|
|
1517
|
+
else if (stack[stack.length - 1].type === 'operator') {
|
|
1518
|
+
const left = stack[stack.length - 2];
|
|
1519
|
+
const operator_unit = stack[stack.length - 1];
|
|
1520
|
+
const operator = operator_unit.operator;
|
|
1521
|
+
// assume we can construct it as follows: [A op B]
|
|
1522
|
+
const operation = {
|
|
1523
|
+
type: 'binary',
|
|
1524
|
+
id: this.id_counter++,
|
|
1525
|
+
left,
|
|
1526
|
+
operator,
|
|
1527
|
+
position: operator_unit.position,
|
|
1528
|
+
right: element,
|
|
1529
|
+
};
|
|
1530
|
+
// we have to reorder if left (A) is a binary operation, and the
|
|
1531
|
+
// precedence of the new operator is higher. note that we will
|
|
1532
|
+
// deal with range operations later, for now just worry about
|
|
1533
|
+
// operator precedence
|
|
1534
|
+
if (left.type === 'binary' &&
|
|
1535
|
+
binary_operators_precendence[operator] >
|
|
1536
|
+
binary_operators_precendence[left.operator]) {
|
|
1537
|
+
// so we have [[A op1 B] op2 C], and we need to re-order this into [A op1 [B op2 C]].
|
|
1538
|
+
operation.left = left.left; // <- A
|
|
1539
|
+
operation.operator = left.operator; // <- op1
|
|
1540
|
+
operation.position = left.position;
|
|
1541
|
+
operation.right = {
|
|
1542
|
+
type: 'binary',
|
|
1543
|
+
id: this.id_counter++,
|
|
1544
|
+
left: left.right, // <- B
|
|
1545
|
+
right: element, // <- C
|
|
1546
|
+
operator, // <- op2
|
|
1547
|
+
position: operator_unit.position,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
stack.splice(-2, 2, operation);
|
|
1551
|
+
}
|
|
1552
|
+
else {
|
|
1553
|
+
/*
|
|
1554
|
+
this.error = `multiple expressions`;
|
|
1555
|
+
this.error_position = (element as {position?: number}).position;
|
|
1556
|
+
this.valid = false;
|
|
1557
|
+
return {
|
|
1558
|
+
type: 'group',
|
|
1559
|
+
id: this.id_counter++,
|
|
1560
|
+
elements: stream,
|
|
1561
|
+
explicit: false,
|
|
1562
|
+
};
|
|
1563
|
+
*/
|
|
1564
|
+
stack.push(element);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (stack.length > 1) {
|
|
1568
|
+
return {
|
|
1569
|
+
type: 'group',
|
|
1570
|
+
id: this.id_counter++,
|
|
1571
|
+
elements: stack,
|
|
1572
|
+
explicit: false,
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
return stack[0];
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* parses literals and tokens from the stream, ignoring whitespace,
|
|
1579
|
+
* and stopping on unexpected tokens (generally operators or parens).
|
|
1580
|
+
*
|
|
1581
|
+
* @param naked treat -/+ as signs (part of numbers) rather than operators.
|
|
1582
|
+
*/
|
|
1583
|
+
ParseNext(naked = true) {
|
|
1584
|
+
this.ConsumeWhiteSpace();
|
|
1585
|
+
const char = this.data[this.index];
|
|
1586
|
+
if (char === DOUBLE_QUOTE) {
|
|
1587
|
+
return {
|
|
1588
|
+
type: 'literal',
|
|
1589
|
+
id: this.id_counter++,
|
|
1590
|
+
position: this.index,
|
|
1591
|
+
value: this.ConsumeString(),
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
else if ((char >= ZERO && char <= NINE) || char === this.decimal_mark_char) {
|
|
1595
|
+
return this.ConsumeNumber();
|
|
1596
|
+
}
|
|
1597
|
+
else if (char === OPEN_BRACE) {
|
|
1598
|
+
return this.ConsumeArray();
|
|
1599
|
+
}
|
|
1600
|
+
else if (naked && (char === MINUS || char === PLUS)) {
|
|
1601
|
+
// there's a case where you type '=-func()', which should support
|
|
1602
|
+
// '=+func()' as well, both of which are naked operators and not numbers.
|
|
1603
|
+
// the only way to figure this out is to check for a second number char.
|
|
1604
|
+
// this is turning into lookahead, which we did not want to do...
|
|
1605
|
+
const check = this.data[this.index + 1];
|
|
1606
|
+
if ((check >= ZERO && check <= NINE) ||
|
|
1607
|
+
check === this.decimal_mark_char) {
|
|
1608
|
+
return this.ConsumeNumber();
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
else if ((char >= UC_A && char <= UC_Z) ||
|
|
1612
|
+
(char >= LC_A && char <= LC_Z) ||
|
|
1613
|
+
char === UNDERSCORE ||
|
|
1614
|
+
char === HASH || // new: only allowed in position 1, always an error
|
|
1615
|
+
char === SINGLE_QUOTE ||
|
|
1616
|
+
char === DOLLAR_SIGN ||
|
|
1617
|
+
// we used to not allow square brackets to start tokens, because
|
|
1618
|
+
// we only supported them for relative R1C1 references -- hence you'd
|
|
1619
|
+
// need the R first. but we now allow them for "structured references".
|
|
1620
|
+
char === OPEN_SQUARE_BRACKET ||
|
|
1621
|
+
(char >= ACCENTED_RANGE_START && char <= ACCENTED_RANGE_END) // adding accented characters, needs some testing
|
|
1622
|
+
) {
|
|
1623
|
+
return this.ConsumeToken(char);
|
|
1624
|
+
}
|
|
1625
|
+
// else throw(new Error('Unexpected character: ' + char));
|
|
1626
|
+
return char;
|
|
1627
|
+
}
|
|
1628
|
+
ConsumeArray() {
|
|
1629
|
+
const expression = {
|
|
1630
|
+
type: 'array',
|
|
1631
|
+
id: this.id_counter++,
|
|
1632
|
+
values: [],
|
|
1633
|
+
position: this.index,
|
|
1634
|
+
};
|
|
1635
|
+
this.index++;
|
|
1636
|
+
let row = 0;
|
|
1637
|
+
let column = 0;
|
|
1638
|
+
while (this.index < this.length) {
|
|
1639
|
+
const item = this.ParseNext();
|
|
1640
|
+
const start_position = this.index;
|
|
1641
|
+
if (typeof item === 'number') {
|
|
1642
|
+
this.index++;
|
|
1643
|
+
switch (item) {
|
|
1644
|
+
case SEMICOLON:
|
|
1645
|
+
//column = 0;
|
|
1646
|
+
//row++;
|
|
1647
|
+
column++;
|
|
1648
|
+
row = 0;
|
|
1649
|
+
break;
|
|
1650
|
+
case COMMA:
|
|
1651
|
+
//column++;
|
|
1652
|
+
row++;
|
|
1653
|
+
break;
|
|
1654
|
+
case CLOSE_BRACE:
|
|
1655
|
+
return expression;
|
|
1656
|
+
default:
|
|
1657
|
+
if (this.valid) {
|
|
1658
|
+
this.error = `invalid character in array literal`;
|
|
1659
|
+
this.error_position = start_position;
|
|
1660
|
+
this.valid = false;
|
|
1661
|
+
}
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
switch (item.type) {
|
|
1667
|
+
case 'literal':
|
|
1668
|
+
if (!expression.values[row]) {
|
|
1669
|
+
expression.values[row] = [];
|
|
1670
|
+
}
|
|
1671
|
+
expression.values[row][column] = item.value;
|
|
1672
|
+
break;
|
|
1673
|
+
default:
|
|
1674
|
+
if (this.valid) {
|
|
1675
|
+
this.error = `invalid value in array literal`;
|
|
1676
|
+
this.error_position = start_position;
|
|
1677
|
+
this.valid = false;
|
|
1678
|
+
}
|
|
1679
|
+
break;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
return expression;
|
|
1684
|
+
}
|
|
1685
|
+
ConsumeOperator() {
|
|
1686
|
+
for (const operator of composite_operators) {
|
|
1687
|
+
if (this.expression.substr(this.index, operator.length) === operator) {
|
|
1688
|
+
const position = this.index;
|
|
1689
|
+
this.index += operator.length;
|
|
1690
|
+
return {
|
|
1691
|
+
type: 'operator',
|
|
1692
|
+
id: this.id_counter++,
|
|
1693
|
+
operator,
|
|
1694
|
+
position,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
/** consume function arguments, which can be of any type */
|
|
1701
|
+
ConsumeArguments() {
|
|
1702
|
+
this.index++; // open paren
|
|
1703
|
+
let argument_index = 0;
|
|
1704
|
+
const args = [];
|
|
1705
|
+
for (; this.index < this.length;) {
|
|
1706
|
+
const unit = this.ParseGeneric([
|
|
1707
|
+
this.argument_separator_char,
|
|
1708
|
+
CLOSE_PAREN,
|
|
1709
|
+
]);
|
|
1710
|
+
if (null !== unit)
|
|
1711
|
+
args.push(unit);
|
|
1712
|
+
// why did parsing stop?
|
|
1713
|
+
const char = this.data[this.index];
|
|
1714
|
+
if (char === this.argument_separator_char) {
|
|
1715
|
+
this.index++;
|
|
1716
|
+
argument_index++;
|
|
1717
|
+
for (let i = args.length; i < argument_index; i++) {
|
|
1718
|
+
args.push({ type: 'missing', id: this.id_counter++ });
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
else if (char === CLOSE_PAREN) {
|
|
1722
|
+
this.index++;
|
|
1723
|
+
return args;
|
|
1724
|
+
}
|
|
1725
|
+
// else console.info('UNEXPECTED (CA)', char);
|
|
1726
|
+
}
|
|
1727
|
+
return args;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* consume token. also checks for function call, because parens
|
|
1731
|
+
* have a different meaning (grouping/precedence) when they appear
|
|
1732
|
+
* not immediately after a token.
|
|
1733
|
+
*
|
|
1734
|
+
* regarding periods: as long as there's no intervening whitespace
|
|
1735
|
+
* or operator, period should be a valid token character. tokens
|
|
1736
|
+
* cannot start with a period.
|
|
1737
|
+
*
|
|
1738
|
+
* NOTE: that's true irrespective of decimal mark type.
|
|
1739
|
+
*
|
|
1740
|
+
* you can have tokens (addresses) with single quotes; these are used
|
|
1741
|
+
* to escape sheet names with spaces (which is a bad idea, but hey). this
|
|
1742
|
+
* should only be legal if the token starts with a single quote, and only
|
|
1743
|
+
* for one (closing) quote.
|
|
1744
|
+
*
|
|
1745
|
+
* R1C1 relative notation uses square brackets, like =R2C[-1] or =R[-1]C[-2].
|
|
1746
|
+
* that's pretty easy to see. there's also regular R1C1, like =R1C1.
|
|
1747
|
+
*
|
|
1748
|
+
* "structured references" use square brackets. they can start with
|
|
1749
|
+
* square brackets -- in that case the table source is implicit (has to
|
|
1750
|
+
* be in the table). otherwise they look like =TableName[@ColumnName]. that
|
|
1751
|
+
* @ is optional and (I think) means don't spill.
|
|
1752
|
+
*
|
|
1753
|
+
*/
|
|
1754
|
+
ConsumeToken(initial_char) {
|
|
1755
|
+
const token = [initial_char];
|
|
1756
|
+
const position = this.index;
|
|
1757
|
+
let single_quote = (initial_char === SINGLE_QUOTE);
|
|
1758
|
+
let square_bracket = 0; // now balancing // false; // this one can't be initial
|
|
1759
|
+
// this is a set-once flag for square brackets; it can
|
|
1760
|
+
// short-circuit the check for structured references.
|
|
1761
|
+
let braces = false;
|
|
1762
|
+
// also watch first char
|
|
1763
|
+
if (initial_char === OPEN_SQUARE_BRACKET) {
|
|
1764
|
+
square_bracket = 1;
|
|
1765
|
+
braces = true;
|
|
1766
|
+
}
|
|
1767
|
+
for (++this.index; this.index < this.length; this.index++) {
|
|
1768
|
+
const char = this.data[this.index];
|
|
1769
|
+
if ((char >= UC_A && char <= UC_Z) ||
|
|
1770
|
+
(char >= LC_A && char <= LC_Z) ||
|
|
1771
|
+
(char >= ACCENTED_RANGE_START && char <= ACCENTED_RANGE_END) ||
|
|
1772
|
+
char === UNDERSCORE ||
|
|
1773
|
+
char === DOLLAR_SIGN ||
|
|
1774
|
+
char === PERIOD ||
|
|
1775
|
+
char === EXCLAMATION_MARK ||
|
|
1776
|
+
single_quote || // ((char === SINGLE_QUOTE || char === SPACE) && single_quote) ||
|
|
1777
|
+
(char >= ZERO && char <= NINE) // tokens can't start with a number, but this loop starts at index 1
|
|
1778
|
+
// we now allow square brackets for structured references;
|
|
1779
|
+
// minus is still only allowed in R1C1 references, so keep
|
|
1780
|
+
// that restriction
|
|
1781
|
+
|| char === OPEN_SQUARE_BRACKET
|
|
1782
|
+
|| (square_bracket > 0 && char === CLOSE_SQUARE_BRACKET)
|
|
1783
|
+
|| (char === MINUS && this.flags.r1c1 && (square_bracket === 1))
|
|
1784
|
+
// the @ sign can appear after the first square bracket...
|
|
1785
|
+
// but only immediately?
|
|
1786
|
+
|| (square_bracket > 0 && char === AT && this.data[this.index - 1] === OPEN_SQUARE_BRACKET)
|
|
1787
|
+
// comma can appear in the first level. this is maybe an older
|
|
1788
|
+
// syntax? it looks like `Table2[[#this row],[region]]
|
|
1789
|
+
|| (square_bracket === 1 && (char === COMMA || char === SPACE))
|
|
1790
|
+
// structured references allow basically any character, if
|
|
1791
|
+
// it's in the SECOND bracket. not sure what's up with that.
|
|
1792
|
+
|| (square_bracket > 1)
|
|
1793
|
+
// I think that's all the rules for structured references.
|
|
1794
|
+
// testing question marks, which are legal in defined names
|
|
1795
|
+
// (but I think not in table names or column names)
|
|
1796
|
+
|| (char === QUESTION_MARK && square_bracket === 0)
|
|
1797
|
+
// moving
|
|
1798
|
+
// || (char === HASH) // FIXME: this should only be allowed at the end...
|
|
1799
|
+
/*
|
|
1800
|
+
|
|
1801
|
+
|| (this.flags.r1c1 && (
|
|
1802
|
+
char === OPEN_SQUARE_BRACKET ||
|
|
1803
|
+
char === CLOSE_SQUARE_BRACKET ||
|
|
1804
|
+
(char === MINUS && square_bracket)
|
|
1805
|
+
))
|
|
1806
|
+
*/
|
|
1807
|
+
) {
|
|
1808
|
+
token.push(char);
|
|
1809
|
+
if (char === OPEN_SQUARE_BRACKET) {
|
|
1810
|
+
// square_bracket = true;
|
|
1811
|
+
square_bracket++;
|
|
1812
|
+
braces = true;
|
|
1813
|
+
}
|
|
1814
|
+
if (char === CLOSE_SQUARE_BRACKET) {
|
|
1815
|
+
// square_bracket = false;
|
|
1816
|
+
square_bracket--;
|
|
1817
|
+
}
|
|
1818
|
+
if (char === SINGLE_QUOTE) {
|
|
1819
|
+
single_quote = false; // one only
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
else
|
|
1823
|
+
break;
|
|
1824
|
+
}
|
|
1825
|
+
// hash at end only
|
|
1826
|
+
if (this.data[this.index] === HASH) {
|
|
1827
|
+
token.push(this.data[this.index++]);
|
|
1828
|
+
}
|
|
1829
|
+
const str = token.map((num) => String.fromCharCode(num)).join('');
|
|
1830
|
+
// special handling: unbalanced single quote (probably sheet name),
|
|
1831
|
+
// this is an error
|
|
1832
|
+
if (single_quote) { // unbalanced
|
|
1833
|
+
this.error = `unbalanced single quote`;
|
|
1834
|
+
this.error_position = position;
|
|
1835
|
+
this.valid = false;
|
|
1836
|
+
return {
|
|
1837
|
+
type: 'identifier',
|
|
1838
|
+
id: this.id_counter++,
|
|
1839
|
+
name: str,
|
|
1840
|
+
position,
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
// check unbalanced square bracket as well, could be a runaway structured
|
|
1844
|
+
// reference
|
|
1845
|
+
if (square_bracket) {
|
|
1846
|
+
this.error = `unbalanced square bracket`;
|
|
1847
|
+
this.error_position = position;
|
|
1848
|
+
this.valid = false;
|
|
1849
|
+
return {
|
|
1850
|
+
type: 'identifier',
|
|
1851
|
+
id: this.id_counter++,
|
|
1852
|
+
name: str,
|
|
1853
|
+
position,
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
/* remove special handling
|
|
1857
|
+
|
|
1858
|
+
// special handling
|
|
1859
|
+
|
|
1860
|
+
if (str.toLowerCase() === 'true') {
|
|
1861
|
+
return {
|
|
1862
|
+
type: 'literal',
|
|
1863
|
+
id: this.id_counter++,
|
|
1864
|
+
value: true,
|
|
1865
|
+
position,
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
if (str.toLowerCase() === 'false') {
|
|
1869
|
+
return {
|
|
1870
|
+
type: 'literal',
|
|
1871
|
+
id: this.id_counter++,
|
|
1872
|
+
value: false,
|
|
1873
|
+
position,
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
*/
|
|
1878
|
+
// function takes precendence over address? I guess so
|
|
1879
|
+
this.ConsumeWhiteSpace();
|
|
1880
|
+
// UPDATE: UNLESS the token is an address, because that's not
|
|
1881
|
+
// a legal function name. so change that precedence rule, address
|
|
1882
|
+
// comes first.
|
|
1883
|
+
// erm -- that's not 100% correct. LOG10 is a valid cell address
|
|
1884
|
+
// and a valid function name. there might be others as well.
|
|
1885
|
+
if (this.flags.spreadsheet_semantics) {
|
|
1886
|
+
const address = this.ConsumeAddress(str, position);
|
|
1887
|
+
if (address)
|
|
1888
|
+
return address;
|
|
1889
|
+
}
|
|
1890
|
+
// [FIXME: what about braces? (...)]
|
|
1891
|
+
const next_char = this.data[this.index];
|
|
1892
|
+
if (next_char === OPEN_PAREN) {
|
|
1893
|
+
const args = this.ConsumeArguments();
|
|
1894
|
+
return {
|
|
1895
|
+
type: 'call',
|
|
1896
|
+
id: this.id_counter++,
|
|
1897
|
+
name: str,
|
|
1898
|
+
args,
|
|
1899
|
+
position,
|
|
1900
|
+
end: this.index, // testing
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
if (this.flags.spreadsheet_semantics) {
|
|
1904
|
+
// check for address. in the case of a range, we'll see an address, the
|
|
1905
|
+
// range operator, and a second address. that will be turned into a range
|
|
1906
|
+
// later.
|
|
1907
|
+
// moved up
|
|
1908
|
+
// const address = this.ConsumeAddress(str, position);
|
|
1909
|
+
// if (address) return address;
|
|
1910
|
+
// check for structured reference, if we had square brackets
|
|
1911
|
+
if (braces) {
|
|
1912
|
+
const structured = this.ConsumeStructuredReference(str, position);
|
|
1913
|
+
if (structured) {
|
|
1914
|
+
return structured;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
// move true/false handling here
|
|
1919
|
+
// should we accept english even if it's not the active language? (...)
|
|
1920
|
+
const lc = str.toLowerCase();
|
|
1921
|
+
if (lc === 'true' || (this.flags.boolean_true && lc === this.flags.boolean_true.toLowerCase())) {
|
|
1922
|
+
return {
|
|
1923
|
+
type: 'literal',
|
|
1924
|
+
id: this.id_counter++,
|
|
1925
|
+
value: true,
|
|
1926
|
+
position,
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
if (lc === 'false' || (this.flags.boolean_false && lc === this.flags.boolean_false.toLowerCase())) {
|
|
1930
|
+
return {
|
|
1931
|
+
type: 'literal',
|
|
1932
|
+
id: this.id_counter++,
|
|
1933
|
+
value: false,
|
|
1934
|
+
position,
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
const identifier = {
|
|
1938
|
+
type: 'identifier',
|
|
1939
|
+
id: this.id_counter++,
|
|
1940
|
+
name: str,
|
|
1941
|
+
position,
|
|
1942
|
+
};
|
|
1943
|
+
this.full_reference_list.push(identifier);
|
|
1944
|
+
return identifier;
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* like ConsumeAddress, look for a structured reference.
|
|
1948
|
+
*/
|
|
1949
|
+
ConsumeStructuredReference(token, position) {
|
|
1950
|
+
// structured references look something like
|
|
1951
|
+
//
|
|
1952
|
+
// [@Column1]
|
|
1953
|
+
// [@[Column with spaces]]
|
|
1954
|
+
// [[#This Row],[Column2]]
|
|
1955
|
+
//
|
|
1956
|
+
// @ means the same as [#This Row]. there are probably other things
|
|
1957
|
+
// that use the # syntax, but I haven't seen them yet.
|
|
1958
|
+
//
|
|
1959
|
+
// some observations: case is not matched for the "this row" text.
|
|
1960
|
+
// I think that's true of column names as well, but that's not relevant
|
|
1961
|
+
// at this stage. whitespace around that comma is ignored. I _think_
|
|
1962
|
+
// whitespace around column names is also ignored, but spaces within
|
|
1963
|
+
// a column name are OK, at least within the second set of brackets.
|
|
1964
|
+
// const index = position;
|
|
1965
|
+
const token_length = token.length;
|
|
1966
|
+
const label = token;
|
|
1967
|
+
let table = '';
|
|
1968
|
+
let i = 0;
|
|
1969
|
+
for (; i < token_length; i++) {
|
|
1970
|
+
if (token[i] === '[') {
|
|
1971
|
+
token = token.substring(i);
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
table += token[i];
|
|
1975
|
+
}
|
|
1976
|
+
// after the table, must start and end with brackets
|
|
1977
|
+
if (token[0] !== '[' || token[token.length - 1] !== ']') {
|
|
1978
|
+
return undefined;
|
|
1979
|
+
}
|
|
1980
|
+
token = token.substring(1, token.length - 1);
|
|
1981
|
+
const parts = token.split(',').map(part => part.trim());
|
|
1982
|
+
let scope = 'column';
|
|
1983
|
+
// let this_row = false;
|
|
1984
|
+
let column = '';
|
|
1985
|
+
if (parts.length > 2) {
|
|
1986
|
+
return undefined; // ??
|
|
1987
|
+
}
|
|
1988
|
+
else if (parts.length === 2) {
|
|
1989
|
+
if (/\[#this row\]/i.test(parts[0])) {
|
|
1990
|
+
scope = 'row';
|
|
1991
|
+
}
|
|
1992
|
+
else if (/\[#all\]/i.test(parts[0])) {
|
|
1993
|
+
scope = 'all';
|
|
1994
|
+
}
|
|
1995
|
+
column = parts[1];
|
|
1996
|
+
}
|
|
1997
|
+
else {
|
|
1998
|
+
column = parts[0];
|
|
1999
|
+
if (column[0] === '@') {
|
|
2000
|
+
scope = 'row';
|
|
2001
|
+
column = column.substring(1, column.length);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (column[0] === '[' && column[column.length - 1] === ']') {
|
|
2005
|
+
column = column.substring(1, column.length - 1);
|
|
2006
|
+
}
|
|
2007
|
+
const reference = {
|
|
2008
|
+
type: 'structured-reference',
|
|
2009
|
+
id: this.id_counter++,
|
|
2010
|
+
label,
|
|
2011
|
+
position,
|
|
2012
|
+
scope,
|
|
2013
|
+
column,
|
|
2014
|
+
table,
|
|
2015
|
+
};
|
|
2016
|
+
// console.info(reference);
|
|
2017
|
+
this.full_reference_list.push(reference);
|
|
2018
|
+
return reference;
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* consumes address. this is outside of the normal parse flow;
|
|
2022
|
+
* we already have a token, here we're checking if it's an address.
|
|
2023
|
+
*
|
|
2024
|
+
* this used to check for ranges as well, but we now treat ranges as
|
|
2025
|
+
* an operation on two addresses; that supports whitespace between the
|
|
2026
|
+
* tokens.
|
|
2027
|
+
*
|
|
2028
|
+
* FIXME: that means we can now inline the column/row routines, since
|
|
2029
|
+
* they are not called more than once
|
|
2030
|
+
*/
|
|
2031
|
+
ConsumeAddress(token, position) {
|
|
2032
|
+
const index = position;
|
|
2033
|
+
const token_length = token.length;
|
|
2034
|
+
// FIXME: should mark this (!) when it hits, rather than search
|
|
2035
|
+
// UPDATE: ! is legal in sheet names, although it needs to be quoted.
|
|
2036
|
+
let sheet;
|
|
2037
|
+
const tokens = token.split('!');
|
|
2038
|
+
if (tokens.length > 1) {
|
|
2039
|
+
sheet = tokens.slice(0, tokens.length - 1).join('!');
|
|
2040
|
+
position += sheet.length + 1;
|
|
2041
|
+
}
|
|
2042
|
+
// handle first
|
|
2043
|
+
if (this.flags.r1c1) {
|
|
2044
|
+
const match = tokens[tokens.length - 1].match(this.r1c1_regex);
|
|
2045
|
+
if (match) {
|
|
2046
|
+
const r1c1 = {
|
|
2047
|
+
type: 'address',
|
|
2048
|
+
id: this.id_counter++,
|
|
2049
|
+
label: token, // TODO
|
|
2050
|
+
row: 0,
|
|
2051
|
+
column: 0,
|
|
2052
|
+
// absolute_row: false, // TODO: is this supported?
|
|
2053
|
+
// absolute_column: false, // TODO: is this supported?
|
|
2054
|
+
position: index,
|
|
2055
|
+
sheet,
|
|
2056
|
+
r1c1: true,
|
|
2057
|
+
};
|
|
2058
|
+
if (match[1][0] === '[') { // relative
|
|
2059
|
+
r1c1.offset_row = true;
|
|
2060
|
+
r1c1.row = Number(match[1].substring(1, match[1].length - 1));
|
|
2061
|
+
}
|
|
2062
|
+
else if (match[1]) { // absolute
|
|
2063
|
+
r1c1.row = Number(match[1]) - 1; // R1C1 is 1-based
|
|
2064
|
+
if (this.flags.r1c1_proper_semantics) {
|
|
2065
|
+
r1c1.absolute_row = true;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
else {
|
|
2069
|
+
r1c1.offset_row = true;
|
|
2070
|
+
r1c1.row = 0;
|
|
2071
|
+
}
|
|
2072
|
+
if (match[2][0] === '[') { // relative
|
|
2073
|
+
r1c1.offset_column = true;
|
|
2074
|
+
r1c1.column = Number(match[2].substring(1, match[2].length - 1));
|
|
2075
|
+
}
|
|
2076
|
+
else if (match[2]) { // absolute
|
|
2077
|
+
r1c1.column = Number(match[2]) - 1; // R1C1 is 1-based
|
|
2078
|
+
if (this.flags.r1c1_proper_semantics) {
|
|
2079
|
+
r1c1.absolute_column = true;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
else {
|
|
2083
|
+
r1c1.offset_column = true;
|
|
2084
|
+
r1c1.column = 0;
|
|
2085
|
+
}
|
|
2086
|
+
return r1c1;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
// FIXME: can inline
|
|
2090
|
+
const c = this.ConsumeAddressColumn(position);
|
|
2091
|
+
if (!c)
|
|
2092
|
+
return null;
|
|
2093
|
+
position = c.position;
|
|
2094
|
+
// things that look like an address but have row 0 are legal
|
|
2095
|
+
// as names. so this should be a token if r === 0.
|
|
2096
|
+
const r = this.ConsumeAddressRow(position);
|
|
2097
|
+
if (!r)
|
|
2098
|
+
return null;
|
|
2099
|
+
position = r.position;
|
|
2100
|
+
// special hack for LOG10. ugh. can't find any other functions with
|
|
2101
|
+
// this problem, in english at least. btw what's the translation for
|
|
2102
|
+
// log10?
|
|
2103
|
+
if (c.column === 8508 && r.row === 9) {
|
|
2104
|
+
return null;
|
|
2105
|
+
}
|
|
2106
|
+
const label = sheet ?
|
|
2107
|
+
sheet + token.substr(sheet.length, position - index).toUpperCase() :
|
|
2108
|
+
token.substr(0, position - index).toUpperCase();
|
|
2109
|
+
if (sheet && sheet[0] === '\'') {
|
|
2110
|
+
sheet = sheet.substr(1, sheet.length - 2);
|
|
2111
|
+
}
|
|
2112
|
+
const addr = {
|
|
2113
|
+
type: 'address',
|
|
2114
|
+
id: this.id_counter++,
|
|
2115
|
+
label, // : token.substr(0, position - index).toUpperCase(),
|
|
2116
|
+
row: r.row,
|
|
2117
|
+
column: c.column,
|
|
2118
|
+
absolute_row: r.absolute,
|
|
2119
|
+
absolute_column: c.absolute,
|
|
2120
|
+
position: index,
|
|
2121
|
+
sheet,
|
|
2122
|
+
spill: r.spill,
|
|
2123
|
+
};
|
|
2124
|
+
// if that's not the complete token, then it's invalid
|
|
2125
|
+
if (token_length !== position - index)
|
|
2126
|
+
return null;
|
|
2127
|
+
// store ref, increment count
|
|
2128
|
+
this.dependencies.addresses[addr.label] = addr;
|
|
2129
|
+
this.address_refcount[addr.label] =
|
|
2130
|
+
(this.address_refcount[addr.label] || 0) + 1;
|
|
2131
|
+
// add to new address list. use the actual object (not a clone or copy);
|
|
2132
|
+
// we update the list later, and we may want to remove it (if it turns
|
|
2133
|
+
// out it's part of a range)
|
|
2134
|
+
this.full_reference_list.push(addr);
|
|
2135
|
+
return addr;
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* consumes a row, possibly absolute ($). returns the numeric row
|
|
2139
|
+
* (0-based) and metadata.
|
|
2140
|
+
*
|
|
2141
|
+
* note that something like "X0" is a legal token, because 0 is not
|
|
2142
|
+
* a valid row. but at the same time it can't have a $ in it. although
|
|
2143
|
+
* maybe "X$0" is a token but not a valid name? dunno
|
|
2144
|
+
*/
|
|
2145
|
+
ConsumeAddressRow(position) {
|
|
2146
|
+
const absolute = this.data[position] === DOLLAR_SIGN;
|
|
2147
|
+
if (absolute)
|
|
2148
|
+
position++;
|
|
2149
|
+
const start = position;
|
|
2150
|
+
let value = 0;
|
|
2151
|
+
for (;; position++) {
|
|
2152
|
+
const char = this.data[position];
|
|
2153
|
+
if (char >= ZERO && char <= NINE) {
|
|
2154
|
+
value *= 10;
|
|
2155
|
+
value += char - ZERO;
|
|
2156
|
+
}
|
|
2157
|
+
else
|
|
2158
|
+
break;
|
|
2159
|
+
}
|
|
2160
|
+
if (start === position) {
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2163
|
+
// handle token X0. should ~maybe~ handle this only if !absolute
|
|
2164
|
+
// temp leaving this separate from the above test just so it's clear
|
|
2165
|
+
// what we are doing
|
|
2166
|
+
if (value === 0) {
|
|
2167
|
+
return false;
|
|
2168
|
+
}
|
|
2169
|
+
let spill = false;
|
|
2170
|
+
if (this.data[position] === HASH) {
|
|
2171
|
+
position++;
|
|
2172
|
+
spill = true;
|
|
2173
|
+
}
|
|
2174
|
+
return { absolute, row: value - 1, position, spill };
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* consumes a column, possibly absolute ($). returns the numeric
|
|
2178
|
+
* column (0-based) and metadata
|
|
2179
|
+
*/
|
|
2180
|
+
ConsumeAddressColumn(position) {
|
|
2181
|
+
let column = -1; // clever
|
|
2182
|
+
let length = 0; // max 3 chars for column
|
|
2183
|
+
const absolute = this.data[position] === DOLLAR_SIGN;
|
|
2184
|
+
if (absolute)
|
|
2185
|
+
position++;
|
|
2186
|
+
for (;; position++, length++) {
|
|
2187
|
+
if (length >= 4)
|
|
2188
|
+
return false; // max 3 chars for column
|
|
2189
|
+
const char = this.data[position];
|
|
2190
|
+
if (char >= UC_A && char <= UC_Z) {
|
|
2191
|
+
column = 26 * (1 + column) + (char - UC_A);
|
|
2192
|
+
}
|
|
2193
|
+
else if (char >= LC_A && char <= LC_Z) {
|
|
2194
|
+
column = 26 * (1 + column) + (char - LC_A);
|
|
2195
|
+
}
|
|
2196
|
+
else
|
|
2197
|
+
break;
|
|
2198
|
+
}
|
|
2199
|
+
if (column < 0)
|
|
2200
|
+
return false;
|
|
2201
|
+
return { absolute, column, position };
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* consumes number. supported formats (WIP):
|
|
2205
|
+
*
|
|
2206
|
+
* -3
|
|
2207
|
+
* +3
|
|
2208
|
+
* 100.9
|
|
2209
|
+
* 10.0%
|
|
2210
|
+
* 1e-2.2
|
|
2211
|
+
*
|
|
2212
|
+
* ~1,333,123.22~
|
|
2213
|
+
*
|
|
2214
|
+
* UPDATE: commas (separators) are not acceptable in numbers passed
|
|
2215
|
+
* in formulae, can't distinguish between them and function argument
|
|
2216
|
+
* separators.
|
|
2217
|
+
*
|
|
2218
|
+
* regarding the above, a couple of rules:
|
|
2219
|
+
*
|
|
2220
|
+
* 1. +/- is only legal in position 0 or immediately after e/E
|
|
2221
|
+
* 2. only one decimal point is allowed.
|
|
2222
|
+
* 3. any number of separators, in any position, are legal, but
|
|
2223
|
+
* only before the decimal point.
|
|
2224
|
+
* 4. only one % is allowed, and only in the last position
|
|
2225
|
+
*
|
|
2226
|
+
* NOTE: this is probably going to break on unfinished strings that
|
|
2227
|
+
* end in - or +... if they're not treated as operators...
|
|
2228
|
+
*
|
|
2229
|
+
* FIXME: find test cases for that so we can fix it
|
|
2230
|
+
*
|
|
2231
|
+
* UPDATE: exporting original text string for preservation/insertion.
|
|
2232
|
+
* this function now returns a tuple of [value, text].
|
|
2233
|
+
*
|
|
2234
|
+
* UPDATE: we now (at least in a branch) consume complex numbers. the last
|
|
2235
|
+
* element of the return array is a boolean which is set if the value is an
|
|
2236
|
+
* imaginary number. when parsing, we will only see the imaginary part;
|
|
2237
|
+
* we'll use a separate step to put complex numbers together.
|
|
2238
|
+
*
|
|
2239
|
+
*
|
|
2240
|
+
*/
|
|
2241
|
+
ConsumeNumber() {
|
|
2242
|
+
const starting_position = this.index;
|
|
2243
|
+
// for exponential notation
|
|
2244
|
+
let exponent = 0;
|
|
2245
|
+
let negative_exponent = false;
|
|
2246
|
+
// general
|
|
2247
|
+
let negative = false;
|
|
2248
|
+
let integer = 0;
|
|
2249
|
+
let decimal = 0;
|
|
2250
|
+
let fraction = 0;
|
|
2251
|
+
let state = 'integer';
|
|
2252
|
+
let position = 0;
|
|
2253
|
+
let imaginary = false;
|
|
2254
|
+
const start_index = this.index;
|
|
2255
|
+
for (; this.index < this.length; this.index++, position++) {
|
|
2256
|
+
const char = this.data[this.index];
|
|
2257
|
+
if (char === this.decimal_mark_char) {
|
|
2258
|
+
if (state === 'integer')
|
|
2259
|
+
state = 'fraction';
|
|
2260
|
+
else
|
|
2261
|
+
break; // end of token; not consuming
|
|
2262
|
+
}
|
|
2263
|
+
else if (char === PERCENT) {
|
|
2264
|
+
// FIXME: disallow combination of exponential and percent notation
|
|
2265
|
+
integer /= 100; // this is a dumb way to do this
|
|
2266
|
+
fraction /= 100;
|
|
2267
|
+
this.index++; // we are consuming
|
|
2268
|
+
break; // end of token
|
|
2269
|
+
}
|
|
2270
|
+
else if (char === PLUS || char === MINUS) {
|
|
2271
|
+
// NOTE: handling of positive/negative exponent in exponential
|
|
2272
|
+
// notation is handled separately, see below
|
|
2273
|
+
if (position === 0) {
|
|
2274
|
+
if (char === MINUS)
|
|
2275
|
+
negative = true;
|
|
2276
|
+
}
|
|
2277
|
+
else
|
|
2278
|
+
break; // end of token -- not consuming
|
|
2279
|
+
}
|
|
2280
|
+
else if (char === UC_E || char === LC_E) {
|
|
2281
|
+
if (state === 'integer' || state === 'fraction') {
|
|
2282
|
+
state = 'exponent';
|
|
2283
|
+
if (this.index < this.length - 1) {
|
|
2284
|
+
if (this.data[this.index + 1] === PLUS)
|
|
2285
|
+
this.index++;
|
|
2286
|
+
else if (this.data[this.index + 1] === MINUS) {
|
|
2287
|
+
this.index++;
|
|
2288
|
+
negative_exponent = true;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
else
|
|
2293
|
+
break; // not sure what this is, then
|
|
2294
|
+
}
|
|
2295
|
+
else if (char === this.imaginary_char) {
|
|
2296
|
+
// FIXME: this should only be set if it's exactly '8i' and not '8in',
|
|
2297
|
+
// since we want to use that for dimensioned quantities. what's legit
|
|
2298
|
+
// after the i and what is not? let's exclude anything in the "word"
|
|
2299
|
+
// range...
|
|
2300
|
+
// peek
|
|
2301
|
+
const peek = this.data[this.index + 1];
|
|
2302
|
+
if ((peek >= UC_A && peek <= UC_Z) ||
|
|
2303
|
+
(peek >= LC_A && peek <= LC_Z) ||
|
|
2304
|
+
(peek >= ACCENTED_RANGE_START && peek <= ACCENTED_RANGE_END) ||
|
|
2305
|
+
peek === UNDERSCORE) {
|
|
2306
|
+
break; // start of an identifier
|
|
2307
|
+
}
|
|
2308
|
+
// actually we could use our dimension logic instead of this... turn
|
|
2309
|
+
// this off when using dimensioned quantities and move it in there?
|
|
2310
|
+
if (state === 'integer' || state === 'fraction') {
|
|
2311
|
+
this.index++; // consume
|
|
2312
|
+
imaginary = true;
|
|
2313
|
+
break; // end of token
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
else if (char >= ZERO && char <= NINE) {
|
|
2317
|
+
switch (state) {
|
|
2318
|
+
case 'integer':
|
|
2319
|
+
integer = integer * 10 + (char - ZERO);
|
|
2320
|
+
break;
|
|
2321
|
+
case 'fraction':
|
|
2322
|
+
fraction = fraction * 10 + (char - ZERO);
|
|
2323
|
+
decimal++;
|
|
2324
|
+
break;
|
|
2325
|
+
case 'exponent':
|
|
2326
|
+
exponent = exponent * 10 + (char - ZERO);
|
|
2327
|
+
break;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
else
|
|
2331
|
+
break;
|
|
2332
|
+
}
|
|
2333
|
+
// NOTE: multiplying returns fp noise, but dividing does not? need
|
|
2334
|
+
// to check more browsers... maybe we should store the value in some
|
|
2335
|
+
// other form? (that's a larger TODO)
|
|
2336
|
+
// let value = integer + fraction * Math.pow(10, -decimal);
|
|
2337
|
+
let value = integer + fraction / (Math.pow(10, decimal)); // <- this is cleaner?
|
|
2338
|
+
if (state === 'exponent') {
|
|
2339
|
+
value = value * Math.pow(10, (negative_exponent ? -1 : 1) * exponent);
|
|
2340
|
+
}
|
|
2341
|
+
// const text = this.expression.substring(start_index, this.index) || '';
|
|
2342
|
+
// return [negative ? -value : value, text, imaginary];
|
|
2343
|
+
if (imaginary) {
|
|
2344
|
+
return {
|
|
2345
|
+
type: 'complex',
|
|
2346
|
+
id: this.id_counter++,
|
|
2347
|
+
position: starting_position,
|
|
2348
|
+
imaginary: negative ? -value : value,
|
|
2349
|
+
real: 0,
|
|
2350
|
+
text: this.expression.substring(start_index, this.index) || '',
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
else {
|
|
2354
|
+
return {
|
|
2355
|
+
type: 'literal',
|
|
2356
|
+
id: this.id_counter++,
|
|
2357
|
+
position: starting_position,
|
|
2358
|
+
value: negative ? -value : value,
|
|
2359
|
+
text: this.expression.substring(start_index, this.index) || '',
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
/*
|
|
2363
|
+
return {
|
|
2364
|
+
type: imaginary ? 'imaginary' : 'literal',
|
|
2365
|
+
id: this.id_counter++,
|
|
2366
|
+
position: starting_position,
|
|
2367
|
+
value: negative ? -value : value,
|
|
2368
|
+
text: this.expression.substring(start_index, this.index) || '',
|
|
2369
|
+
};
|
|
2370
|
+
*/
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* in spreadsheet language ONLY double-quoted strings are legal. there
|
|
2374
|
+
* are no escape characters, and a backslash is a legal character. to
|
|
2375
|
+
* embed a quotation mark, use "" (double-double quote); that's an escaped
|
|
2376
|
+
* double-quote.
|
|
2377
|
+
*/
|
|
2378
|
+
ConsumeString() {
|
|
2379
|
+
this.index++; // open quote
|
|
2380
|
+
const str = [];
|
|
2381
|
+
for (; this.index < this.length; this.index++) {
|
|
2382
|
+
const char = this.data[this.index];
|
|
2383
|
+
if (char === DOUBLE_QUOTE) {
|
|
2384
|
+
// always do this: either it's part of the string (and
|
|
2385
|
+
// we want to skip the next one), or it's the end of the
|
|
2386
|
+
// string and we want to close the literal.
|
|
2387
|
+
this.index++;
|
|
2388
|
+
// check for an escaped double-quote; otherwise close the string
|
|
2389
|
+
// note (1) we already incremented, so check the current value,
|
|
2390
|
+
// and (2) it will increment again on the loop pass so it will
|
|
2391
|
+
// drop the extra one. I note these because this was confusing to
|
|
2392
|
+
// write.
|
|
2393
|
+
if (this.index >= this.length ||
|
|
2394
|
+
this.data[this.index] !== DOUBLE_QUOTE) {
|
|
2395
|
+
break;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
str.push(char);
|
|
2399
|
+
}
|
|
2400
|
+
return str.map((char) => String.fromCharCode(char)).join('');
|
|
2401
|
+
}
|
|
2402
|
+
/** run through any intervening whitespace */
|
|
2403
|
+
ConsumeWhiteSpace() {
|
|
2404
|
+
for (; this.index < this.length;) {
|
|
2405
|
+
const char = this.data[this.index];
|
|
2406
|
+
if (char === SPACE ||
|
|
2407
|
+
char === TAB ||
|
|
2408
|
+
char === CR ||
|
|
2409
|
+
char === LF ||
|
|
2410
|
+
char === NON_BREAKING_SPACE) {
|
|
2411
|
+
this.index++;
|
|
2412
|
+
}
|
|
2413
|
+
else
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
//# sourceMappingURL=parser.js.map
|