From 9ef6c7eec4995656cacbb6bb48223b04919b7b6d Mon Sep 17 00:00:00 2001 From: iyxan23 Date: Thu, 12 Sep 2024 23:43:45 +0700 Subject: [PATCH] feat: impl expanding blocks --- package-lock.json | 2 +- package.json | 1 + src/sheet/expression/extractor.ts | 2 +- src/sheet/sheet-templater.ts | 118 ++++++++++++++++++++++++++++-- 4 files changed, 116 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6afbed1..c11c317 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@zip.js/zip.js": "^2.7.52", + "deepmerge": "^4.3.1", "fast-xml-parser": "^4.5.0" }, "devDependencies": { @@ -1380,7 +1381,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 792b570..86ceefe 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "sideEffects": false, "dependencies": { "@zip.js/zip.js": "^2.7.52", + "deepmerge": "^4.3.1", "fast-xml-parser": "^4.5.0" }, "peerDependencies": { diff --git a/src/sheet/expression/extractor.ts b/src/sheet/expression/extractor.ts index c61635b..323ba81 100644 --- a/src/sheet/expression/extractor.ts +++ b/src/sheet/expression/extractor.ts @@ -5,7 +5,7 @@ import type { ExpressionCell, Expression } from "./parser"; // startBlock expressions yet, it's a limitation of the current implementation // and i don't find myself needing that. so i'm going to leave it as is for now -type Block = { +export type Block = { identifier: string; arg: Expression; indexVariableIdentifier: string; diff --git a/src/sheet/sheet-templater.ts b/src/sheet/sheet-templater.ts index 7e205f1..a9aeb41 100644 --- a/src/sheet/sheet-templater.ts +++ b/src/sheet/sheet-templater.ts @@ -1,5 +1,5 @@ import { type ExpressionCell, parseExpressionCell } from "./expression/parser"; -import { extractHoistsAndBlocks } from "./expression/extractor"; +import { Block, extractHoistsAndBlocks } from "./expression/extractor"; import { Sheet } from "./sheet"; import { z } from "zod"; import { @@ -10,6 +10,7 @@ import { } from "./expression/evaluate"; import { resultSymbol, success } from "./expression/result"; import { Result } from "./expression/result"; +import * as deepmerge from "deepmerge"; interface TemplatableCell { getTextContent(): string; @@ -156,7 +157,6 @@ export class SheetTemplater { const parsedExpressions = this.parseExpressions(); // stage 1: extract hoists and blocks - // @ts-expect-error const { variableHoists, blocks } = extractHoistsAndBlocks(parsedExpressions); @@ -167,7 +167,7 @@ export class SheetTemplater { if (sheetCell === null) { throw new Error( `cannot find the cell referenced by variable hoist on` + - ` col ${col} row ${row}`, + ` col ${col} row ${row}`, ); } @@ -191,6 +191,15 @@ export class SheetTemplater { globalVariables[expr.identifier] = result.result; } + // stage 3: block expansion + // @ts-expect-error will be used later + const localVariables = this.expandBlocks( + parsedExpressions, + blocks, + (fName) => this.functions[fName]?.call, + (vName) => globalVariables[vName] ?? data[vName], + ); + return { sym: resultSymbol, status: "success", @@ -199,8 +208,107 @@ export class SheetTemplater { }; } - // @ts-expect-error will be used later - private evaluateExpression( + private expandBlocks( + sheet: Sheet, + blocks: Block[], + lookupFunction: ( + name: string, + ) => TemplaterFunction["call"] | undefined, + lookupVariable: (name: string) => any | undefined, + ): Result>>> { + const issues: Issue[] = []; + let localVariables: Record< + number, + Record> + > = {}; + + for (const block of blocks) { + // expand inner blocks first + const result = this.expandBlocks( + sheet, + block.innerBlocks, + lookupFunction, + lookupVariable, + ); + + if (result.status === "failed") return result; + issues.push(...result.issues); + + localVariables = deepmerge(localVariables, result.result); + + const repeatAmountResult = evaluateExpression( + block.arg, + { + col: block.start.col, + row: block.start.row, + callTree: [`${block.identifier} block`], + }, + (fName) => lookupFunction(fName), + (vName) => lookupVariable(vName), + ); + + if (repeatAmountResult.status === "failed") { + return repeatAmountResult; + } + + issues.push(...repeatAmountResult.issues); + const repeatAmount = repeatAmountResult.result; + + if (block.identifier === "repeatRow") { + for (let i = 0; i < repeatAmount; i++) { + sheet.cloneMapRow({ + row: block.start.row, + colStart: block.start.col, + colEnd: block.end.col, + count: repeatAmount, + map: ({ relativeCol, relativeRow, previousData }) => { + const row = block.start.row + relativeRow; + const col = block.start.col + relativeCol; + const ident = block.indexVariableIdentifier; + + if (!localVariables[row]) { + localVariables[row] = { [col]: { [ident]: i } }; + } else if (!localVariables[row][col]) { + localVariables[row][col] = { [ident]: i }; + } else if (!localVariables[row][col][ident]) { + localVariables[row][col][ident] = i; + } + + return previousData; + }, + }); + } + } else if (block.identifier === "repeatCol") { + for (let i = 0; i < repeatAmount; i++) { + sheet.cloneMapCol({ + col: block.start.col, + rowStart: block.start.row, + rowEnd: block.end.row, + count: repeatAmount, + map: ({ relativeCol, relativeRow, previousData }) => { + const row = block.start.row + relativeRow; + const col = block.start.col + relativeCol; + const ident = block.indexVariableIdentifier; + + if (!localVariables[row]) { + localVariables[row] = { [col]: { [ident]: i } }; + } else if (!localVariables[row][col]) { + localVariables[row][col] = { [ident]: i }; + } else if (!localVariables[row][col][ident]) { + localVariables[row][col][ident] = i; + } + + return previousData; + }, + }); + } + } + } + + return success(localVariables, issues); + } + + private evaluateExpressionCell( cell: ExpressionCell, sheetCell: SheetT, {