PHP and Javascript implementations of a simple markdown parser
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

spreadsheet.js 83KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794
  1. class CellException extends Error {
  2. errorSymbol;
  3. constructor(message, errorSymbol='#ERROR') {
  4. super(message);
  5. this.errorSymbol = errorSymbol;
  6. }
  7. }
  8. class CellSyntaxException extends CellException {
  9. constructor(message, errorSymbol='#SYNTAX') {
  10. super(message, errorSymbol);
  11. }
  12. }
  13. class CellEvaluationException extends CellException {}
  14. class CellDependencyException extends CellException {
  15. constructor(message, errorSymbol='#REF') {
  16. super(message, errorSymbol);
  17. }
  18. }
  19. class CellExpressionTokenType {
  20. static Name = new CellExpressionTokenType('Name');
  21. static Address = new CellExpressionTokenType('Address');
  22. static NameOrAddress = new CellExpressionTokenType('NameOrAddress');
  23. static String = new CellExpressionTokenType('String');
  24. static Number = new CellExpressionTokenType('Number');
  25. static OpenParen = new CellExpressionTokenType('OpenParen');
  26. static CloseParen = new CellExpressionTokenType('CloseParen');
  27. static Colon = new CellExpressionTokenType('Colon');
  28. static Plus = new CellExpressionTokenType('Plus');
  29. static Minus = new CellExpressionTokenType('Minus');
  30. static Multiply = new CellExpressionTokenType('Multiply');
  31. static Divide = new CellExpressionTokenType('Divide');
  32. static Comma = new CellExpressionTokenType('Comma');
  33. static Semicolon = new CellExpressionTokenType('Semicolon');
  34. static Ampersand = new CellExpressionTokenType('Ampersand');
  35. static LessThan = new CellExpressionTokenType('LessThan');
  36. static LessThanEqual = new CellExpressionTokenType('LessThanEqual');
  37. static GreaterThan = new CellExpressionTokenType('GreaterThan');
  38. static GreaterThanEqual = new CellExpressionTokenType('GreaterThanEqual');
  39. static Equal = new CellExpressionTokenType('Equal');
  40. static Unequal = new CellExpressionTokenType('Unequal');
  41. static Not = new CellExpressionTokenType('Not');
  42. /** @type {string} */
  43. #name;
  44. /** @type {string} */
  45. get name() { return this.#name; }
  46. constructor(name) {
  47. this.#name = name;
  48. }
  49. /**
  50. * @returns {boolean}
  51. */
  52. isPotentialName() {
  53. return this === CellExpressionTokenType.Name || this === CellExpressionTokenType.NameOrAddress;
  54. }
  55. /**
  56. * @returns {boolean}
  57. */
  58. isPotentialAddress() {
  59. return this === CellExpressionTokenType.Address || this === CellExpressionTokenType.NameOrAddress;
  60. }
  61. toString() {
  62. return this.#name;
  63. }
  64. equals(other) {
  65. if (!(other instanceof CellExpressionTokenType)) return false;
  66. return other.#name == this.#name;
  67. }
  68. }
  69. class CellExpressionOperation {
  70. /** Arg is int or float */
  71. static Number = new CellExpressionOperation('Number');
  72. /** Arg is string without quotes */
  73. static String = new CellExpressionOperation('String');
  74. /** Arg is bool */
  75. static Boolean = new CellExpressionOperation('Boolean');
  76. /** Arg is reference address (e.g. "A5") */
  77. static Reference = new CellExpressionOperation('Reference');
  78. /** Args are start and end addresses (e.g. "A5", "C7") */
  79. static Range = new CellExpressionOperation('Range');
  80. /** Args are two operand CellExpressions. */
  81. static Add = new CellExpressionOperation('Add');
  82. /** Args are two operand CellExpressions */
  83. static Subtract = new CellExpressionOperation('Subtract');
  84. /** Args are two operand CellExpressions */
  85. static Multiply = new CellExpressionOperation('Multiply');
  86. /** Args are two operand CellExpressions */
  87. static Divide = new CellExpressionOperation('Divide');
  88. /** Args are two operand CellExpressions. */
  89. static Concatenate = new CellExpressionOperation('Concatenate');
  90. /** Arg is operand expression */
  91. static UnaryMinus = new CellExpressionOperation('UnaryMinus');
  92. /** Args are two operand CellExpressions. */
  93. static GreaterThan = new CellExpressionOperation('GreaterThan');
  94. /** Args are two operand CellExpressions. */
  95. static GreaterThanEqual = new CellExpressionOperation('GreaterThanEqual');
  96. /** Args are two operand CellExpressions. */
  97. static LessThan = new CellExpressionOperation('LessThan');
  98. /** Args are two operand CellExpressions. */
  99. static LessThanEqual = new CellExpressionOperation('LessThanEqual');
  100. /** Args are two operand CellExpressions. */
  101. static Equal = new CellExpressionOperation('Equal');
  102. /** Args are two operand CellExpressions. */
  103. static Unequal = new CellExpressionOperation('Unequal');
  104. /** Arg is operand expression. */
  105. static UnaryNot = new CellExpressionOperation('UnaryNot');
  106. /** Args are 0+ CellExpressions */
  107. static Function = new CellExpressionOperation('Function');
  108. /** @type {string} */
  109. name;
  110. constructor(name) {
  111. this.name = name;
  112. }
  113. toString() {
  114. return `${this.constructor.name}.${this.name}`;
  115. }
  116. equals(other) {
  117. if (!(other instanceof CellExpressionOperation)) return false;
  118. return other.name == this.name;
  119. }
  120. }
  121. /**
  122. * Collection of all calculated cells in a table.
  123. */
  124. class CellExpressionSet {
  125. /** @type {SpreadsheetGrid} */
  126. #grid;
  127. /**
  128. * @param {SpreadsheetGrid} grid
  129. */
  130. constructor(grid) {
  131. this.#grid = grid;
  132. }
  133. /**
  134. * Populates the `outputValue` fields of every cell in the table. Cells
  135. * with formulas will attempt to be calculated or populated with error
  136. * values.
  137. */
  138. calculateCells() {
  139. const rowCount = this.#grid.rowCount;
  140. const colCount = this.#grid.columnCount;
  141. // Make queue of cell addresses with expressions in them
  142. /** @type {CellAddress[]} */
  143. var expressionAddressQueue = [];
  144. var range = new CellAddressRange(new CellAddress(0, 0), new CellAddress(colCount - 1, rowCount - 1));
  145. for (const address of range.cellsIn(this.#grid)) {
  146. const cell = this.#grid.cellAt(address);
  147. const value = cell.originalValue;
  148. if (value.type != CellValue.TYPE_FORMULA) {
  149. cell.outputValue = value;
  150. cell.isCalculated = false;
  151. continue;
  152. }
  153. try {
  154. const expression = CellExpression.parse(value.formattedValue, address);
  155. if (!expression) {
  156. throw new CellSyntaxException("Invalid expression");
  157. }
  158. cell.parsedExpression = expression;
  159. cell.isCalculated = true;
  160. expressionAddressQueue.push(address);
  161. this.#enqueueFilledBlanks(expression, expressionAddressQueue);
  162. } catch (e) {
  163. if (e instanceof CellSyntaxException || e instanceof CellEvaluationException) {
  164. cell.outputValue = CellValue.fromValue(e);
  165. } else {
  166. throw e;
  167. }
  168. }
  169. }
  170. // Try to evaluate each cell. If one depends on cells not yet calculated,
  171. // move it to the back of the queue and try again later.
  172. this.#processExpressionQueue(expressionAddressQueue);
  173. this.#processCircularReferences(expressionAddressQueue);
  174. }
  175. /**
  176. * @param {CellAddress[]} addresses
  177. */
  178. #processExpressionQueue(addresses) {
  179. var requeueCount = 0;
  180. while (addresses.length > 0 && requeueCount < addresses.length) {
  181. const address = addresses[0];
  182. addresses.splice(0, 1);
  183. const cell = this.#grid.cellAt(address);
  184. const value = cell.originalValue;
  185. try {
  186. const result = this.#evaluate(cell.parsedExpression, address);
  187. cell.isCalculated = true;
  188. if (result instanceof CellValue) {
  189. cell.outputValue = result;
  190. requeueCount = 0;
  191. } else if (Array.isArray(result)) {
  192. if (result.length == 1) {
  193. cell.outputValue = result[0];
  194. requeueCount = 0;
  195. } else {
  196. throw new CellEvaluationException(`Expression resolved to ${result.length} values, single value expected`);
  197. }
  198. } else {
  199. throw new CellEvaluationException(`Expression resolved to ${result && result.constructor ? result.constructor.name : typeof result}, expected CellValue`);
  200. }
  201. } catch (e) {
  202. if (e instanceof CellDependencyException) {
  203. // Depends on a value that hasn't been calculated yet
  204. addresses.push(address);
  205. requeueCount++;
  206. } else if (e instanceof CellSyntaxException || e instanceof CellEvaluationException) {
  207. cell.outputValue = CellValue.fromValue(e);
  208. requeueCount = 0;
  209. } else {
  210. throw e;
  211. }
  212. }
  213. }
  214. }
  215. /**
  216. * @param {CellExpression} expression
  217. * @param {CellAddress[]} addresses
  218. */
  219. #enqueueFilledBlanks(expression, addresses) {
  220. for (const range of expression.fillRanges ?? []) {
  221. for (const filledAddress of range.cellsIn(this.#grid)) {
  222. const filledCell = this.#grid.cellAt(filledAddress);
  223. if (filledCell.originalValue.type == CellValue.TYPE_BLANK &&
  224. (!filledCell.outputValue ||
  225. filledCell.outputValue.type == CellValue.TYPE_BLANK)) {
  226. filledCell.parsedExpression = expression.transpose(expression.location, filledAddress);
  227. filledCell.isCalculated = true;
  228. addresses.push(filledAddress);
  229. }
  230. }
  231. }
  232. }
  233. /**
  234. * @param {CellAddress[]} addresses
  235. */
  236. #processCircularReferences(addresses) {
  237. while (addresses.length > 0) {
  238. const address = addresses[0];
  239. addresses.splice(0, 1);
  240. // We defered these cells repeatedly and couldn't resolve them even
  241. // after everything else was calculated.
  242. const cell = this.#grid.cellAt(address);
  243. cell.outputValue = CellValue.fromValue(new CellDependencyException(`Circular reference at ${address.name}`));
  244. }
  245. }
  246. /**
  247. * @param {CellExpression} expr
  248. * @param {CellAddress} address
  249. * @returns {CellValue|CellValue[]}
  250. */
  251. #evaluate(expr, address) {
  252. const result = this.#preevaluate(expr, address);
  253. if (result instanceof CellValue) {
  254. if (expr.outputType !== null) {
  255. return CellValue.fromValue(result.value, expr.outputType ?? result.type, expr.outputDecimals);
  256. }
  257. }
  258. return result;
  259. }
  260. /**
  261. * @param {CellExpression} expr
  262. * @param {CellAddress} address
  263. * @returns {CellValue|CellValue[]}
  264. */
  265. #preevaluate(expr, address) {
  266. switch (expr.op) {
  267. case CellExpressionOperation.Number:
  268. case CellExpressionOperation.String:
  269. case CellExpressionOperation.Boolean:
  270. return expr.arguments[0];
  271. case CellExpressionOperation.Reference: {
  272. const refAddress = expr.arguments[0];
  273. const cell = this.#grid.cellAt(refAddress);
  274. if (cell === null) {
  275. throw new CellEvaluationException(`No cell at ${refAddress.name}`, '#REF');
  276. }
  277. if (cell.outputValue === null) {
  278. throw new CellDependencyException(`Need calculated value for ${refAddress} to evaluate`);
  279. }
  280. return cell.outputValue;
  281. }
  282. case CellExpressionOperation.Range: {
  283. const range = expr.arguments[0];
  284. var values = [];
  285. for (const rAddress of range.cellsIn(this.#grid)) {
  286. const cell = this.#grid.cellAt(rAddress);
  287. if (rAddress.equals(address)) continue;
  288. const val = this.#grid.outputValueAt(rAddress);
  289. if (val === null) {
  290. throw new CellDependencyException(`Need calculated value for ${rAddress.name} to evaluate`);
  291. }
  292. values.push(val);
  293. }
  294. return values;
  295. }
  296. case CellExpressionOperation.Add: {
  297. const op1 = this.#evaluate(expr.arguments[0], address);
  298. const op2 = this.#evaluate(expr.arguments[1], address);
  299. return op1.add(op2);
  300. }
  301. case CellExpressionOperation.Subtract: {
  302. const op1 = this.#evaluate(expr.arguments[0], address);
  303. const op2 = this.#evaluate(expr.arguments[1], address);
  304. return op1.subtract(op2);
  305. }
  306. case CellExpressionOperation.Multiply: {
  307. const op1 = this.#evaluate(expr.arguments[0], address);
  308. const op2 = this.#evaluate(expr.arguments[1], address);
  309. return op1.multiply(op2);
  310. }
  311. case CellExpressionOperation.Divide: {
  312. const op1 = this.#evaluate(expr.arguments[0], address);
  313. const op2 = this.#evaluate(expr.arguments[1], address);
  314. return op1.divide(op2);
  315. }
  316. case CellExpressionOperation.UnaryMinus: {
  317. const op = this.#evaluate(expr.arguments[0], address);
  318. return CellValue.fromValue(0).subtract(op);
  319. }
  320. case CellExpressionOperation.GreaterThan: {
  321. const op1 = this.#evaluate(expr.arguments[0], address);
  322. const op2 = this.#evaluate(expr.arguments[1], address);
  323. return op1.gt(op2);
  324. }
  325. case CellExpressionOperation.GreaterThanEqual: {
  326. const op1 = this.#evaluate(expr.arguments[0], address);
  327. const op2 = this.#evaluate(expr.arguments[1], address);
  328. return op1.gte(op2);
  329. }
  330. case CellExpressionOperation.LessThan: {
  331. const op1 = this.#evaluate(expr.arguments[0], address);
  332. const op2 = this.#evaluate(expr.arguments[1], address);
  333. return op1.lt(op2);
  334. }
  335. case CellExpressionOperation.LessThanEqual: {
  336. const op1 = this.#evaluate(expr.arguments[0], address);
  337. const op2 = this.#evaluate(expr.arguments[1], address);
  338. return op1.lte(op2);
  339. }
  340. case CellExpressionOperation.Equal: {
  341. const op1 = this.#evaluate(expr.arguments[0], address);
  342. const op2 = this.#evaluate(expr.arguments[1], address);
  343. return op1.eq(op2);
  344. }
  345. case CellExpressionOperation.Unequal: {
  346. const op1 = this.#evaluate(expr.arguments[0], address);
  347. const op2 = this.#evaluate(expr.arguments[1], address);
  348. return op1.neq(op2);
  349. }
  350. case CellExpressionOperation.UnaryNot: {
  351. const op = this.#evaluate(expr.arguments[0], address);
  352. return op.not();
  353. }
  354. case CellExpressionOperation.Concatenate: {
  355. const op1 = this.#evaluate(expr.arguments[0], address);
  356. const op2 = this.#evaluate(expr.arguments[1], address);
  357. return op1.concatenate(op2);
  358. }
  359. case CellExpressionOperation.Function:
  360. return this.#callFunction(expr.qualifier, expr.arguments, address);
  361. }
  362. throw new CellSyntaxException(`Unhandled operation ${expr.op.name}`);
  363. }
  364. /**
  365. * @param {string} functionName
  366. * @param {Array} args
  367. * @param {CellAddress} address
  368. * @returns {CellValue}
  369. */
  370. #callFunction(functionName, args, address) {
  371. switch (functionName.toUpperCase()) {
  372. case 'ABS': return this.#funcAbs(args, address);
  373. case 'AND': return this.#funcAnd(args, address);
  374. case 'AVERAGE': return this.#funcAverage(args, address);
  375. case 'CEILING': return this.#funcCeiling(args, address);
  376. case 'EXP': return this.#funcExp(args, address);
  377. case 'FLOOR': return this.#funcFloor(args, address);
  378. case 'IF': return this.#funcIf(args, address);
  379. case 'IFS': return this.#funcIfs(args, address);
  380. case 'LN': return this.#funcLn(args, address);
  381. case 'LOG': return this.#funcLog(args, address);
  382. case 'LOWER': return this.#funcLower(args, address);
  383. case 'MAX': return this.#funcMax(args, address);
  384. case 'MIN': return this.#funcMin(args, address);
  385. case 'MOD': return this.#funcMod(args, address);
  386. case 'NOT': return this.#funcNot(args, address);
  387. case 'OR': return this.#funcOr(args, address);
  388. case 'POWER': return this.#funcPower(args, address);
  389. case 'ROUND': return this.#funcRound(args, address);
  390. case 'SQRT': return this.#funcSqrt(args, address);
  391. case 'SUBSTITUTE': return this.#funcSubstitute(args, address);
  392. case 'SUM': return this.#funcSum(args, address);
  393. case 'UPPER': return this.#funcUpper(args, address);
  394. case 'XOR': return this.#funcXor(args, address);
  395. default:
  396. throw new CellSyntaxException(`Unknown function "${functionName}"`);
  397. }
  398. }
  399. /**
  400. * @param {string} functionName
  401. * @param {number} minArgs
  402. * @param {number} maxArgs
  403. * @param {Array} args
  404. * @param {CellAddress} address
  405. * @returns {Array}
  406. */
  407. #assertNumericArguments(functionName, minArgs, maxArgs, args, address) {
  408. const argCount = args.length;
  409. if (argCount < minArgs || argCount > maxArgs) {
  410. if (minArgs == maxArgs) {
  411. throw new CellSyntaxException(`${functionName}() expects ${minArgs} arguments, got ${argCount}`);
  412. }
  413. throw new CellSyntaxException(`${functionName}() expects between ${minArgs} and ${maxArgs} arguments, got ${argCount}`);
  414. }
  415. var out = [];
  416. for (const argument of args) {
  417. const evaled = this.#evaluate(argument, address);
  418. if (!(evaled instanceof CellValue) || !evaled.isNumeric()) {
  419. throw new CellEvaluationException(`${functionName}() expects numeric arguments`);
  420. }
  421. out.push(evaled);
  422. }
  423. return out;
  424. }
  425. /**
  426. * @param {string} functionName
  427. * @param {Array} args
  428. * @param {CellAddress} address
  429. * @param {boolean} errorOnNonnumeric
  430. * @param {Array}
  431. */
  432. #flattenedNumericArguments(functionName, args, address, errorOnNonnumeric=true) {
  433. var flattened = [];
  434. for (const argument of args) {
  435. const evaled = this.#evaluate(argument, address);
  436. if (evaled instanceof CellValue) {
  437. if (!evaled.isNumeric()) {
  438. if (errorOnNonnumeric) {
  439. throw new CellEvaluationException(`${functionName} requires numeric arguments`);
  440. }
  441. continue;
  442. }
  443. flattened.push(evaled);
  444. } else if (Array.isArray(evaled)) {
  445. const arr = evaled;
  446. for (const arrayArgument of arr) {
  447. if (arrayArgument instanceof CellValue) {
  448. if (!arrayArgument.isNumeric()) {
  449. if (errorOnNonnumeric) {
  450. throw new CellEvaluationException(`${functionName} requires numeric arguments`);
  451. }
  452. continue;
  453. }
  454. flattened.push(arrayArgument);
  455. }
  456. }
  457. }
  458. }
  459. return flattened;
  460. }
  461. /**
  462. * @param {Array} args
  463. * @param {CellAddress} address
  464. * @returns {CellValue}
  465. */
  466. #funcAbs(args, address) {
  467. const evaled = this.#assertNumericArguments('ABS', 1, 1, args, address);
  468. const arg = evaled[0];
  469. if (arg.value < 0.0) {
  470. return CellValue.fromValue(0).subtract(arg);
  471. }
  472. return arg;
  473. }
  474. /**
  475. * @param {Array} args
  476. * @param {CellAddress} address
  477. * @returns {CellValue}
  478. */
  479. #funcAnd(args, address) {
  480. if (args.length == 0) {
  481. throw new CellEvaluationException("AND requires one or more arguments");
  482. }
  483. const values = args.map((arg) => this.#evaluate(arg, address));
  484. for (const value of values) {
  485. const result = value.booleanValue();
  486. if (result === null) {
  487. throw new CellEvaluationException("AND requires boolean arguments");
  488. }
  489. if (!result) return CellValue.fromValue(false);
  490. }
  491. return CellValue.fromValue(true);
  492. }
  493. /**
  494. * @param {Array} args
  495. * @param {CellAddress} address
  496. * @returns {CellValue}
  497. */
  498. #funcAverage(args, address) {
  499. var sum = CellValue.fromValue(0);
  500. var count = 0;
  501. for (const arg of args) {
  502. const val = this.#evaluate(arg, address);
  503. if (Array.isArray(val)) {
  504. for (const elem of val) {
  505. if (!elem.isNumeric()) continue;
  506. sum = sum.add(elem);
  507. count++;
  508. }
  509. } else if (val.isNumeric()) {
  510. sum = sum.add(val);
  511. count++;
  512. }
  513. }
  514. return (count > 0) ? sum.divide(CellValue.fromValue(count)) : CellValue.fromValue(0);
  515. }
  516. /**
  517. * @param {Array} args
  518. * @param {CellAddress} address
  519. * @returns {CellValue}
  520. */
  521. #funcCeiling(args, address) {
  522. const evaled = this.#assertNumericArguments('CEILING', 1, 1, args, address);
  523. const arg = evaled[0];
  524. const newValue = Math.ceil(arg.value);
  525. return CellValue.fromValue(newValue, arg.type);
  526. }
  527. /**
  528. * @param {Array} args
  529. * @param {CellAddress} address
  530. * @returns {CellValue}
  531. */
  532. #funcExp(args, address) {
  533. const evaled = this.#assertNumericArguments('EXP', 1, 1, args, address);
  534. const arg = evaled[0];
  535. const newValue = Math.exp(arg.value);
  536. return CellValue.fromValue(newValue, arg.type);
  537. }
  538. /**
  539. * @param {Array} args
  540. * @param {CellAddress} address
  541. * @returns {CellValue}
  542. */
  543. #funcFloor(args, address) {
  544. const evaled = this.#assertNumericArguments('FLOOR', 1, 1, args, address);
  545. const arg = evaled[0];
  546. const newValue = Math.floor(arg.value);
  547. return CellValue.fromValue(newValue, arg.type);
  548. }
  549. /**
  550. * @param {Array} args
  551. * @param {CellAddress} address
  552. * @returns {CellValue}
  553. */
  554. #funcIf(args, address) {
  555. if (args.length != 3) {
  556. throw new CellEvaluationException("IF expects three arguments");
  557. }
  558. const evaled = args.map((arg) => this.#evaluate(arg, address));
  559. const test = evaled[0].booleanValue();
  560. if (test === null) {
  561. throw new CellEvaluationException("IF expects a boolean for the first argument");
  562. }
  563. if (test) {
  564. return evaled[1];
  565. } else {
  566. return evaled[2];
  567. }
  568. }
  569. /**
  570. * @param {Array} args
  571. * @param {CellAddress} address
  572. * @returns {CellValue}
  573. */
  574. #funcIfs(args, address) {
  575. if (args.length < 3) {
  576. throw new CellEvaluationException("IFS expects at least 3 arguments");
  577. }
  578. if ((args.length % 2) != 1) {
  579. throw new CellEvaluationException("IFS expects an odd number of arguments");
  580. }
  581. const evaled = args.map((arg) => this.#evaluate(arg, address));
  582. for (var i = 0; i < evaled.length - 1; i += 2) {
  583. const test = evaled[i].booleanValue();
  584. if (test === null) {
  585. throw new CellEvaluationException(`IFS expects a boolean for argument ${i + 1}`);
  586. }
  587. if (test) {
  588. return evaled[i + 1];
  589. }
  590. }
  591. return evaled[evaled.length - 1];
  592. }
  593. /**
  594. * @param {Array} args
  595. * @param {CellAddress} address
  596. * @returns {CellValue}
  597. */
  598. #funcLn(args, address) {
  599. const evaled = this.#assertNumericArguments('LN', 1, 1, args, address);
  600. const arg = evaled[0];
  601. const newValue = Math.log(arg.value);
  602. return CellValue.fromValue(newValue, arg.type);
  603. }
  604. /**
  605. * @param {Array} args
  606. * @param {CellAddress} address
  607. * @returns {CellValue}
  608. */
  609. #funcLog(args, address) {
  610. const evaled = this.#assertNumericArguments('LOG', 1, 2, args, address);
  611. const exponent = evaled[0];
  612. const base = (evaled.length > 1) ? evaled[1].value : 10.0;
  613. const newValue = Math.log(exponent.value) / Math.log(base);
  614. return CellValue.fromValue(newValue, exponent.type);
  615. }
  616. /**
  617. * @param {Array} args
  618. * @param {CellAddress} address
  619. * @returns {CellValue}
  620. */
  621. #funcLower(args, address) {
  622. if (args.length != 1) {
  623. throw new CellEvaluationException("LOWER requires one argument");
  624. }
  625. const evaled = args.map((arg) => this.#evaluate(arg, address));
  626. const s = evaled[0].stringValue(true);
  627. if (s === null) {
  628. throw new CellEvaluationException("LOWER requires one string argument");
  629. }
  630. return CellValue.fromValue(s.toLowerCase());
  631. }
  632. /**
  633. * @param {Array} args
  634. * @param {CellAddress} address
  635. * @returns {CellValue}
  636. */
  637. #funcMax(args, address) {
  638. var maxValue = null;
  639. const flattened = this.#flattenedNumericArguments('MAX', args, address);
  640. if (flattened.length == 0) {
  641. throw new CellEvaluationException("MAX requires at least one numeric argument");
  642. }
  643. for (const argument of flattened) {
  644. if (maxValue === null || argument.value > maxValue.value) {
  645. maxValue = argument;
  646. }
  647. }
  648. return maxValue ?? CellValue.fromValue(0);
  649. }
  650. /**
  651. * @param {Array} args
  652. * @param {CellAddress} address
  653. * @returns {CellValue}
  654. */
  655. #funcMin(args, address) {
  656. var minValue = null;
  657. const flattened = this.#flattenedNumericArguments('MIN', args, address);
  658. if (flattened.length == 0) {
  659. throw new CellEvaluationException("MIN requires at least one numeric argument");
  660. }
  661. for (const argument of flattened) {
  662. if (minValue === null || argument.value < minValue.value) {
  663. minValue = argument;
  664. }
  665. }
  666. return minValue ?? CellValue.fromValue(0);
  667. }
  668. /**
  669. * @param {Array} args
  670. * @param {CellAddress} address
  671. * @returns {CellValue}
  672. */
  673. #funcMod(args, address) {
  674. if (args.length != 2) {
  675. throw new CellEvaluationException("MOD requires two numeric arguments");
  676. }
  677. const values = args.map((arg) => this.#evaluate(arg, address));
  678. return values[0].modulo(values[1]);
  679. }
  680. /**
  681. * @param {Array} args
  682. * @param {CellAddress} address
  683. * @returns {CellValue}
  684. */
  685. #funcNot(args, address) {
  686. if (args.length == 0) {
  687. throw new CellEvaluationException("NOT expects one argument");
  688. }
  689. const evaled = args.map((arg) => this.#evaluate(arg, address));
  690. const b = evaled[0].booleanValue();
  691. if (b === null) {
  692. throw new CellEvaluationException("NOT expects boolean argument");
  693. }
  694. return CellValue.fromValue(!b);
  695. }
  696. /**
  697. * @param {Array} args
  698. * @param {CellAddress} address
  699. * @returns {CellValue}
  700. */
  701. #funcOr(args, address) {
  702. if (args.length == 0) {
  703. throw new CellEvaluationException("OR requires one or more arguments");
  704. }
  705. const values = args.map((arg) => this.#evaluate(arg, address));
  706. for (const value of values) {
  707. const result = value.booleanValue();
  708. if (result === null) {
  709. throw new CellEvaluationException("OR requires boolean arguments");
  710. }
  711. if (result) return CellValue.fromValue(true);
  712. }
  713. return CellValue.fromValue(false);
  714. }
  715. /**
  716. * @param {Array} args
  717. * @param {CellAddress} address
  718. * @returns {CellValue}
  719. */
  720. #funcPower(args, address) {
  721. const evaled = this.#assertNumericArguments('POWER', 2, 2, args, address);
  722. const base = evaled[0];
  723. const exp = evaled[1];
  724. const val = Math.pow(base.value, exp.value);
  725. return CellValue.fromValue(val, base.type);
  726. }
  727. /**
  728. * @param {Array} args
  729. * @param {CellAddress} address
  730. * @returns {CellValue}
  731. */
  732. #funcRound(args, address) {
  733. const evaled = this.#assertNumericArguments('ROUND', 1, 2, args, address);
  734. const val = evaled[0];
  735. const places = evaled.length > 1 ? evaled[1].value : 0;
  736. const divider = Math.pow(10.0, places);
  737. const newValue = Math.round(val.value * divider) / divider;
  738. return CellValue.fromValue(newValue, val.type);
  739. }
  740. /**
  741. * @param {Array} args
  742. * @param {CellAddress} address
  743. * @returns {CellValue}
  744. */
  745. #funcSqrt(args, address) {
  746. if (args.length != 1) {
  747. throw new CellEvaluationException("SQRT expects 1 numeric argument");
  748. }
  749. const values = args.map((arg) => this.#evaluate(arg, address));
  750. const val = values[0].numericValue();
  751. if (val === null) {
  752. throw new CellEvaluationException("SQRT expects 1 numeric argument");
  753. }
  754. return CellValue.fromValue(Math.sqrt(val));
  755. }
  756. /**
  757. * @param {Array} args
  758. * @param {CellAddress} address
  759. * @returns {CellValue}
  760. */
  761. #funcSubstitute(args, address) {
  762. if (args.length != 3) {
  763. throw new CellEvaluationException("SUBSTITUTE expects 3 string arguments");
  764. }
  765. const values = args.map((arg) => this.#evaluate(arg, address));
  766. const text = values[0].stringValue();
  767. const search = values[1].stringValue();
  768. const replace = values[2].stringValue();
  769. if (text === null || search === null || replace === null) {
  770. throw new CellEvaluationException("SUBSTITUTE expects 3 string arguments");
  771. }
  772. const result = text.replaceAll(search, replace);
  773. return CellValue.fromValue(result);
  774. }
  775. /**
  776. * @param {Array} args
  777. * @param {CellAddress} address
  778. * @returns {CellValue}
  779. */
  780. #funcSum(args, address) {
  781. var sum = CellValue.fromValue(0);
  782. for (const arg of args) {
  783. const val = this.#evaluate(arg, address);
  784. if (Array.isArray(val)) {
  785. for (const elem of val) {
  786. if (elem.isNumeric()) sum = sum.add(elem);
  787. }
  788. } else if (val.isNumeric()) {
  789. sum = sum.add(val);
  790. }
  791. }
  792. return sum;
  793. }
  794. /**
  795. * @param {Array} args
  796. * @param {CellAddress} address
  797. * @returns {CellValue}
  798. */
  799. #funcUpper(args, address) {
  800. if (args.length != 1) {
  801. throw new CellEvaluationException("UPPER requires one argument");
  802. }
  803. const evaled = args.map((arg) => this.#evaluate(arg, address));
  804. const s = evaled[0].stringValue(true);
  805. if (s === null) {
  806. throw new CellEvaluationException("UPPER requires one string argument");
  807. }
  808. return CellValue.fromValue(s.toUpperCase());
  809. }
  810. /**
  811. * @param {Array} args
  812. * @param {CellAddress} address
  813. * @returns {CellValue}
  814. */
  815. #funcXor(args, address) {
  816. if (args.length == 0) {
  817. throw new CellEvaluationException("XOR requires one or more arguments");
  818. }
  819. const values = args.map((arg) => this.#evaluate(arg, address));
  820. var result = null;
  821. for (const value of values) {
  822. const b = value.booleanValue();
  823. if (b === null) {
  824. throw new CellEvaluationException("XOR requires boolean arguments");
  825. }
  826. result = (result === null) ? b : (result ^ b);
  827. }
  828. return CellValue.fromValue(result != 0);
  829. }
  830. }
  831. /**
  832. * A spreadsheet formula expression.
  833. */
  834. class CellExpression {
  835. /** @type {CellExpressionOperation} */
  836. op;
  837. /** @type {Array} */
  838. arguments;
  839. /** @type {string|null} */
  840. qualifier;
  841. /** @type {string|null} */
  842. outputType = null;
  843. /** @type {number|null} */
  844. outputDecimals = null;
  845. /**
  846. * Address ranges to copy this expression into for any blank cells.
  847. * @type {CellAddressRange[]|null} fillRanges
  848. */
  849. fillRanges = null;
  850. /** @type {CellAddress|null} */
  851. location = null;
  852. /**
  853. * @param {CellExpressionOperation} op
  854. * @param {Array} args
  855. * @param {string|null} qualifier
  856. */
  857. constructor(op, args, qualifier = null) {
  858. this.op = op;
  859. this.arguments = args;
  860. this.qualifier = qualifier;
  861. }
  862. /**
  863. * @param {string} expression
  864. * @param {CellAddress} address
  865. * @returns {CellExpression|null}
  866. */
  867. static parse(expression, address) {
  868. const tokens = this.expressionToTokens(expression);
  869. if (tokens.length == 0) return null;
  870. const expr = this.expressionFromTokens(tokens, address);
  871. expr.location = address;
  872. return expr;
  873. }
  874. /**
  875. * Writes an expression tree to console.error for debugging purposes.
  876. * @param {CellExpression} expression
  877. * @param {string} indent
  878. */
  879. static dumpExpression(expression, indent = '') {
  880. if (expression.arguments.length == 0) {
  881. console.error(indent + "expr " + expression.op.name + '()');
  882. } else {
  883. console.error(indent + expression.op.name + '(');
  884. for (const argument of expression.arguments) {
  885. if (typeof argument == 'number') {
  886. console.error(indent + `\t${argument}`);
  887. } else if (typeof argument == 'string') {
  888. console.error(indent + `\t"${argument}"`);
  889. } else if (typeof argument == 'boolean') {
  890. console.error(indent + "\t" + (argument ? "true" : "false"));
  891. } else if (argument instanceof CellAddress) {
  892. console.error(indent + "\t" + argument.name);
  893. } else if (argument instanceof CellAddressRange) {
  894. console.error(indent + "\t" + argument.name);
  895. } else if (argument instanceof CellValue) {
  896. console.error(indent + "\t" + argument.type + " " + argument.formattedValue);
  897. } else if (argument instanceof CellExpression) {
  898. this.dumpExpression(argument, indent + "\t");
  899. } else {
  900. console.error(indent + "\t" + typeof argument);
  901. }
  902. }
  903. console.error(indent + ')');
  904. }
  905. }
  906. #clone() {
  907. const cp = new CellExpression();
  908. cp.op = this.op;
  909. cp.arguments = this.arguments.slice();
  910. cp.qualifier = this.qualifier;
  911. cp.outputType = this.outputType;
  912. cp.outputDecimals = this.outputDecimals;
  913. cp.fillRanges = this.fillRanges !== null ? this.fillRanges.slice() : null;
  914. cp.location = this.location;
  915. return cp;
  916. }
  917. /**
  918. * @param {CellAddress} start
  919. * @param {CellAddress} end
  920. * @returns {CellExpression|null}
  921. */
  922. transpose(start, end) {
  923. var transposed = this.#clone(); // structuredClone makes a mess of typing
  924. transposed.arguments = [];
  925. for (const argument of this.arguments) {
  926. if (argument instanceof CellExpression) {
  927. transposed.arguments.push(argument.transpose(start, end));
  928. } else if (argument instanceof CellAddress) {
  929. transposed.arguments.push(argument.transpose(start, end));
  930. } else if (argument instanceof CellAddressRange) {
  931. transposed.arguments.push(argument.transpose(start, end));
  932. } else {
  933. transposed.arguments.push(argument);
  934. }
  935. }
  936. return transposed;
  937. }
  938. // -- Tokenizing -----
  939. /**
  940. * Converts an expression into an array of tokens.
  941. * @param {string} text - expression
  942. * @returns {CellExpressionToken[]} tokens
  943. */
  944. static expressionToTokens(text) {
  945. var tokens = [];
  946. var pos = [0];
  947. this.#skipWhitespace(text, pos);
  948. if (text.substring(pos[0], pos[0] + 1) == '=') {
  949. // Ignore equals
  950. pos[0]++;
  951. }
  952. this.#skipWhitespace(text, pos);
  953. var l = text.length;
  954. while (pos[0] < l) {
  955. tokens.push(this.#readNextToken(text, pos));
  956. this.#skipWhitespace(text, pos);
  957. }
  958. return tokens;
  959. }
  960. /**
  961. * @param {string} text
  962. * @param {number[]} pos
  963. * @returns {CellExpressionToken}
  964. */
  965. static #readNextToken(text, pos) {
  966. // Single char tokens
  967. var token;
  968. if (token = this.#readNextSimpleToken(text, pos, '==', CellExpressionTokenType.Equal)) return token;
  969. if (token = this.#readNextSimpleToken(text, pos, '!=', CellExpressionTokenType.Unequal)) return token;
  970. if (token = this.#readNextSimpleToken(text, pos, '<=', CellExpressionTokenType.LessThanEqual)) return token;
  971. if (token = this.#readNextSimpleToken(text, pos, '>=', CellExpressionTokenType.GreaterThanEqual)) return token;
  972. if (token = this.#readNextSimpleToken(text, pos, '<', CellExpressionTokenType.LessThan)) return token;
  973. if (token = this.#readNextSimpleToken(text, pos, '>', CellExpressionTokenType.GreaterThan)) return token;
  974. if (token = this.#readNextSimpleToken(text, pos, '!', CellExpressionTokenType.Not)) return token;
  975. if (token = this.#readNextSimpleToken(text, pos, '+', CellExpressionTokenType.Plus)) return token;
  976. if (token = this.#readNextSimpleToken(text, pos, '-', CellExpressionTokenType.Minus)) return token;
  977. if (token = this.#readNextSimpleToken(text, pos, '*', CellExpressionTokenType.Multiply)) return token;
  978. if (token = this.#readNextSimpleToken(text, pos, '/', CellExpressionTokenType.Divide)) return token;
  979. if (token = this.#readNextSimpleToken(text, pos, ',', CellExpressionTokenType.Comma)) return token;
  980. if (token = this.#readNextSimpleToken(text, pos, '(', CellExpressionTokenType.OpenParen)) return token;
  981. if (token = this.#readNextSimpleToken(text, pos, ')', CellExpressionTokenType.CloseParen)) return token;
  982. if (token = this.#readNextSimpleToken(text, pos, ':', CellExpressionTokenType.Colon)) return token;
  983. if (token = this.#readNextSimpleToken(text, pos, ';', CellExpressionTokenType.Semicolon)) return token;
  984. if (token = this.#readNextSimpleToken(text, pos, '&', CellExpressionTokenType.Ampersand)) return token;
  985. // Other tokens
  986. if (token = this.#readNextAddressToken(text, pos)) return token;
  987. if (token = this.#readNextNameToken(text, pos)) return token;
  988. if (token = this.#readNextNumberToken(text, pos)) return token;
  989. if (token = this.#readNextStringToken(text, pos)) return token;
  990. throw new CellSyntaxException(`Unexpected character "${text.substring(pos[0], pos[0] + 1)}" at ${pos[0]}`);
  991. }
  992. /**
  993. * @param {string} text
  994. * @param {number[]} pos
  995. */
  996. static #skipWhitespace(text, pos) {
  997. const l = text.length;
  998. while (pos[0] < l) {
  999. const ch = text.substring(pos[0], pos[0] + 1);
  1000. if (ch == ' ' || ch == "\t" || ch == "\n" || ch == "\r") {
  1001. pos[0]++;
  1002. } else {
  1003. return;
  1004. }
  1005. }
  1006. }
  1007. /**
  1008. * @param {string} text
  1009. * @param {number[]} pos
  1010. * @param {string} target
  1011. * @param {CellExpressionTokenType} type
  1012. * @returns {CellExpressionToken|null}
  1013. */
  1014. static #readNextSimpleToken(text, pos, target, type) {
  1015. const remaining = text.length - pos[0];
  1016. const l = target.length;
  1017. if (l > remaining) return null;
  1018. const test = text.substring(pos[0], pos[0] + l);
  1019. if (test.toUpperCase() != target.toUpperCase()) return null;
  1020. pos[0] += l;
  1021. return new CellExpressionToken(type, test);
  1022. }
  1023. /**
  1024. * @param {string} text
  1025. * @param {number[]} pos
  1026. * @returns {CellExpressionToken|null}
  1027. */
  1028. static #readNextAddressToken(text, pos) {
  1029. var p = [ pos[0] ];
  1030. var ch = text.substring(p[0], p[0] + 1);
  1031. var address = '';
  1032. var isName = true;
  1033. if (ch == '$') {
  1034. address += ch;
  1035. isName = false;
  1036. p[0]++;
  1037. }
  1038. var col = this.#readChars(text, p, (s) => this.#isLetter(s), 1, 2);
  1039. if (col === null) return null;
  1040. address += col;
  1041. ch = text.substring(p[0], p[0] + 1);
  1042. if (ch == '$') {
  1043. address += ch;
  1044. isName = false;
  1045. p[0]++;
  1046. const row = this.#readChars(text, p, this.#isDigit, 1);
  1047. if (row === null) return null;
  1048. address += row;
  1049. } else {
  1050. const row = this.#readChars(text, p, this.#isDigit, 0);
  1051. if (row === null) return null;
  1052. address += row;
  1053. }
  1054. pos[0] = p[0];
  1055. return new CellExpressionToken(
  1056. isName ? CellExpressionTokenType.NameOrAddress : CellExpressionTokenType.Address,
  1057. address);
  1058. }
  1059. /**
  1060. * @param {string} text
  1061. * @param {number[]} pos
  1062. * @returns {CellExpressionToken|null}
  1063. */
  1064. static #readNextNameToken(text, pos) {
  1065. var p = [ pos[0] ];
  1066. const name = this.#readChars(text, p, (s) => this.#isLetter(s), 1);
  1067. if (name === null) return null;
  1068. pos[0] = p[0];
  1069. if (CellAddress.isAddress(name)) {
  1070. return new CellExpressionToken(CellExpressionTokenType.NameOrAddress, name);
  1071. }
  1072. return new CellExpressionToken(CellExpressionTokenType.Name, name);
  1073. }
  1074. /**
  1075. * @param {string} text
  1076. * @param {number[]} pos
  1077. * @returns {CellExpressionToken|null}
  1078. */
  1079. static #readNextNumberToken(text, pos) {
  1080. var ch = text.substring(pos[0], pos[0] + 1);
  1081. if (!this.#isDigit(ch)) return null;
  1082. const l = text.length;
  1083. var numStr = ch;
  1084. pos[0]++;
  1085. while (pos[0] < l) {
  1086. ch = text.substring(pos[0], pos[0] + 1);
  1087. if (this.#isDigit(ch)) {
  1088. pos[0]++;
  1089. numStr += ch;
  1090. } else {
  1091. break;
  1092. }
  1093. }
  1094. if (pos[0] < l) {
  1095. ch = text.substring(pos[0], pos[0] + 1);
  1096. if (ch == '.') {
  1097. numStr += ch;
  1098. pos[0]++;
  1099. while (pos[0] < l) {
  1100. ch = text.substring(pos[0], pos[0] + 1);
  1101. if (this.#isDigit(ch)) {
  1102. pos[0]++;
  1103. numStr += ch;
  1104. } else {
  1105. break;
  1106. }
  1107. }
  1108. }
  1109. }
  1110. return new CellExpressionToken(CellExpressionTokenType.Number, numStr);
  1111. }
  1112. /**
  1113. * @param {string} text
  1114. * @param {number[]} pos
  1115. * @returns {CellExpressionToken|null}
  1116. */
  1117. static #readNextStringToken(text, pos) {
  1118. var ch = text.substring(pos[0], pos[0] + 1);
  1119. if (ch !== '"') return null;
  1120. var str = '';
  1121. pos[0]++;
  1122. const l = text.length;
  1123. var inEscape = false;
  1124. while (pos[0] < l) {
  1125. ch = text.substring(pos[0], pos[0] + 1);
  1126. pos[0]++;
  1127. if (inEscape) {
  1128. inEscape = false;
  1129. if (ch == '\\' || ch == '"') {
  1130. str += ch;
  1131. } else {
  1132. throw new CellSyntaxException(`Bad string escape sequence "\\${ch}"`);
  1133. }
  1134. } else if (ch == '\\') {
  1135. inEscape = true;
  1136. } else if (ch == '"') {
  1137. return new CellExpressionToken(CellExpressionTokenType.String, str);
  1138. } else {
  1139. str += ch;
  1140. }
  1141. }
  1142. throw new CellSyntaxException('Unterminated string');
  1143. }
  1144. /**
  1145. * Reads the next chars that pass a test function and returns the result.
  1146. * @param {string} text
  1147. * @param {number[]} pos
  1148. * @param {function} charTest
  1149. * @param {number|null} minimumLength
  1150. * @param {number|null} maximumLength
  1151. * @returns {string|null}
  1152. */
  1153. static #readChars(text, pos, charTest, minimumLength = null, maximumLength = null) {
  1154. var p = pos[0];
  1155. const l = text.length;
  1156. var s = '';
  1157. var sl = 0;
  1158. while (p < l && (maximumLength === null || sl < maximumLength)) {
  1159. const ch = text.substring(p, p + 1);
  1160. if (!charTest(ch)) break;
  1161. s += ch;
  1162. sl++;
  1163. p++;
  1164. }
  1165. if (p < l && charTest(text.substring(p, p + 1))) {
  1166. return null;
  1167. }
  1168. if (minimumLength !== null && sl < minimumLength) {
  1169. return null;
  1170. }
  1171. pos[0] = p;
  1172. return s;
  1173. }
  1174. /**
  1175. * @param {string} ch
  1176. * @returns {boolean}
  1177. */
  1178. static #isLetter(ch) {
  1179. const ord = ch.codePointAt(0);
  1180. return (ord >= 65 && ord <= 90) || (ord >= 97 && ord <= 122);
  1181. }
  1182. /**
  1183. * @param {string} ch
  1184. * @returns {boolean}
  1185. */
  1186. static #isDigit(ch) {
  1187. const ord = ch.codePointAt(0);
  1188. return (ord >= 48 && ord <= 57);
  1189. }
  1190. // -- Parsing -----
  1191. /**
  1192. * @param {Array} tokens
  1193. * @param {CellAddress} address
  1194. * @returns {CellExpression|null}
  1195. */
  1196. static expressionFromTokens(tokens, address) {
  1197. var expr;
  1198. if (expr = this.#tryExpressionAndFormat(tokens, 0, tokens.length - 1, address)) return expr;
  1199. if (expr = this.#tryExpressionAndFill(tokens, 0, tokens.length - 1, address)) return expr;
  1200. if (expr = this.#tryExpression(tokens, 0, tokens.length - 1, address)) return expr;
  1201. return null;
  1202. }
  1203. /**
  1204. * @param {Array} tokens
  1205. * @param {number} start
  1206. * @param {number} end
  1207. * @param {CellAddress} address
  1208. * @returns {CellExpression|null}
  1209. */
  1210. static #tryExpressionAndFormat(tokens, start, end, address) {
  1211. for (var t = start + 1; t < end; t++) {
  1212. if (tokens[t].type == CellExpressionTokenType.Semicolon) {
  1213. const expr = this.#tryExpressionAndFill(tokens, start, t - 1, address) ??
  1214. this.#tryExpression(tokens, start, t - 1, address);
  1215. if (expr === null) return null;
  1216. const format = this.#tryFormat(tokens, t + 1, end, address);
  1217. if (format === null) return null;
  1218. [ expr.outputType, expr.outputDecimals ] = format;
  1219. return expr;
  1220. }
  1221. }
  1222. return null;
  1223. }
  1224. /**
  1225. * @param {CellExpressionToken[]} tokens
  1226. * @param {number} start
  1227. * @param {number} end
  1228. * @param {CellAddress} address
  1229. * @returns {CellExpression|null}
  1230. */
  1231. static #tryExpressionAndFill(tokens, start, end, address) {
  1232. var count = end - start + 1;
  1233. if (count < 2) return null;
  1234. if (!tokens[end].type.isPotentialName()) return null;
  1235. var name = tokens[end].content.toUpperCase();
  1236. if (name != 'FILL') return null;
  1237. const exp = this.#tryExpression(tokens, start, end - 1, address);
  1238. const columnIndex = address.columnIndex;
  1239. exp.fillRanges = [
  1240. new CellAddressRange(new CellAddress(columnIndex, -1), new CellAddress(columnIndex, -1)),
  1241. ];
  1242. return exp;
  1243. }
  1244. /**
  1245. * Tries to parse a format suffix after a semicolon. Examples:
  1246. *
  1247. * ; number
  1248. * ; number 3
  1249. * ; currency 2
  1250. * ; percent 0
  1251. * @param {CellExpressionToken[]} tokens
  1252. * @param {number} start
  1253. * @param {number} end
  1254. * @param {CellAddress} address
  1255. * @return {any[]} CellValue type and decimal places
  1256. */
  1257. static #tryFormat(tokens, start, end, address) {
  1258. const count = end - start + 1;
  1259. if (count < 0 || count > 2) return null;
  1260. if (!tokens[start].type.isPotentialName()) return null;
  1261. const type = tokens[start].content.toLowerCase();
  1262. if (!CellValue.isTypeNumeric(type)) return null;
  1263. var decimals;
  1264. if (count > 1) {
  1265. if (tokens[start + 1].type != CellExpressionTokenType.Number) return null;
  1266. decimals = parseInt(tokens[start + 1].content);
  1267. } else {
  1268. decimals = null;
  1269. }
  1270. return [ type, decimals ];
  1271. }
  1272. /**
  1273. * @param {CellExpressionToken[]} tokens
  1274. * @param {number} start
  1275. * @param {number} end
  1276. * @param {CellAddress} address
  1277. * @returns {CellExpression}
  1278. */
  1279. static #tryExpression(tokens, start, end, address) {
  1280. var expr;
  1281. if (expr = this.#tryParenExpression(tokens, start, end, address)) return expr;
  1282. if (expr = this.#tryNumber(tokens, start, end, address)) return expr;
  1283. if (expr = this.#tryString(tokens, start, end, address)) return expr;
  1284. if (expr = this.#tryBoolean(tokens, start, end, address)) return expr;
  1285. if (expr = this.#tryFunction(tokens, start, end, address)) return expr;
  1286. if (expr = this.#tryRange(tokens, start, end, address)) return expr;
  1287. if (expr = this.#tryReference(tokens, start, end, address)) return expr;
  1288. if (expr = this.#tryInfix(tokens, start, end, address)) return expr;
  1289. if (expr = this.#tryUnary(tokens, start, end, address)) return expr;
  1290. throw new CellSyntaxException("Invalid expression");
  1291. }
  1292. /**
  1293. * @param {CellExpressionToken[]} tokens
  1294. * @param {number} start
  1295. * @param {number} end
  1296. * @param {CellAddress} address
  1297. * @returns {CellExpression|null}
  1298. */
  1299. static #tryParenExpression(tokens, start, end, address) {
  1300. if (tokens[start].type != CellExpressionTokenType.OpenParen) return null;
  1301. if (tokens[end].type != CellExpressionTokenType.CloseParen) return null;
  1302. var parenLevel = 0;
  1303. for (var t = start + 1; t < end; t++) {
  1304. if (tokens[t].type == CellExpressionTokenType.OpenParen) {
  1305. parenLevel++;
  1306. } else if (tokens[t].type == CellExpressionTokenType.CloseParen) {
  1307. parenLevel--;
  1308. }
  1309. if (parenLevel < 0) return null;
  1310. }
  1311. if (parenLevel != 0) return null;
  1312. return this.#tryExpression(tokens, start + 1, end - 1, address);
  1313. }
  1314. /**
  1315. * @param {CellExpressionToken[]} tokens
  1316. * @param {number} start
  1317. * @param {number} end
  1318. * @param {CellAddress} address
  1319. * @returns {CellExpression|null}
  1320. */
  1321. static #tryNumber(tokens, start, end, address) {
  1322. if (tokens[end].type != CellExpressionTokenType.Number) return null;
  1323. if (end > start + 1) return null;
  1324. const val = CellValue.fromCellString(tokens[end].content);
  1325. if (end > start) {
  1326. if (tokens[start].type != CellExpressionTokenType.Minus) return null;
  1327. val.value = -val.value;
  1328. }
  1329. return new CellExpression(CellExpressionOperation.Number, [ val ]);
  1330. }
  1331. /**
  1332. * @param {CellExpressionToken[]} tokens
  1333. * @param {number} start
  1334. * @param {number} end
  1335. * @param {CellAddress} address
  1336. * @returns {CellExpression|null}
  1337. */
  1338. static #tryString(tokens, start, end, address) {
  1339. if (start != end) return null;
  1340. if (tokens[start].type != CellExpressionTokenType.String) return null;
  1341. const str = tokens[start].content;
  1342. return new CellExpression(CellExpressionOperation.String, [ new CellValue(str, str, CellValue.TYPE_STRING, 0) ]);
  1343. }
  1344. /**
  1345. * @param {CellExpressionToken[]} tokens
  1346. * @param {number} start
  1347. * @param {number} end
  1348. * @param {CellAddress} address
  1349. * @returns {CellExpression|null}
  1350. */
  1351. static #tryBoolean(tokens, start, end, address) {
  1352. if (start != end) return null;
  1353. if (!tokens[start].type.isPotentialName()) return null;
  1354. const str = tokens[start].content.toUpperCase();
  1355. if (str != 'TRUE' && str != 'FALSE') return null;
  1356. return new CellExpression(CellExpressionOperation.Boolean, [ new CellValue(str, str == 'TRUE', CellValue.TYPE_BOOLEAN) ]);
  1357. }
  1358. /**
  1359. * @param {CellExpressionToken[]} tokens
  1360. * @param {number} start
  1361. * @param {number} end
  1362. * @param {CellAddress} address
  1363. * @returns {CellExpression|null}
  1364. */
  1365. static #tryFunction(tokens, start, end, address) {
  1366. const count = end - start + 1;
  1367. if (count < 3) return null;
  1368. if (!tokens[start].type.isPotentialName()) return null;
  1369. const qualifier = tokens[start].content;
  1370. if (tokens[start + 1].type != CellExpressionTokenType.OpenParen) return null;
  1371. if (tokens[end].type != CellExpressionTokenType.CloseParen) return null;
  1372. const argList = this.#tryArgumentList(tokens, start + 2, end - 1, address);
  1373. if (argList === null) return null;
  1374. return new CellExpression(CellExpressionOperation.Function, argList, qualifier);
  1375. }
  1376. /**
  1377. * @param {CellExpressionToken[]} tokens
  1378. * @param {number} start
  1379. * @param {number} end
  1380. * @param {CellAddress} address
  1381. * @returns {Array|null}
  1382. */
  1383. static #tryArgumentList(tokens, start, end, address) {
  1384. const count = end - start + 1;
  1385. if (count == 0) return [];
  1386. var parenDepth = 0;
  1387. const argCount = 1;
  1388. /** @type {int[][]} */
  1389. var argTokens = []; // argindex -> [ start, end ]
  1390. var exprStartToken = start;
  1391. for (var i = start; i <= end; i++) {
  1392. if (tokens[i].type == CellExpressionTokenType.OpenParen) {
  1393. parenDepth++;
  1394. } else if (tokens[i].type == CellExpressionTokenType.CloseParen) {
  1395. parenDepth--;
  1396. } else if (tokens[i].type == CellExpressionTokenType.Comma && parenDepth == 0) {
  1397. const exprEndToken = i - 1;
  1398. argTokens.push([ exprStartToken, exprEndToken ]);
  1399. exprStartToken = i + 1;
  1400. }
  1401. }
  1402. argTokens.push([ exprStartToken, end ]);
  1403. var args = [];
  1404. for (const argToken of argTokens) {
  1405. const arg = this.#tryExpression(tokens, argToken[0], argToken[1], address);
  1406. if (arg === null) return null;
  1407. args.push(arg);
  1408. }
  1409. return args;
  1410. }
  1411. /**
  1412. * @param {CellExpressionToken[]} tokens
  1413. * @param {number} start
  1414. * @param {number} end
  1415. * @param {CellAddress} address
  1416. * @returns {CellExpression|null}
  1417. */
  1418. static #tryRange(tokens, start, end, address) {
  1419. const count = end - start + 1;
  1420. if (count != 3) return null;
  1421. if (!tokens[start].type.isPotentialAddress()) return null;
  1422. const first = tokens[start].content.toUpperCase();
  1423. if (tokens[start + 1].type != CellExpressionTokenType.Colon) return null;
  1424. if (!tokens[end].type.isPotentialAddress()) return null;
  1425. const last = tokens[end].content.toUpperCase();
  1426. const firstAddress = new CellAddress(first);
  1427. const lastAddress = new CellAddress(last);
  1428. const range = new CellAddressRange(firstAddress, lastAddress);
  1429. return new CellExpression(CellExpressionOperation.Range, [ range ]);
  1430. }
  1431. /**
  1432. * @param {CellExpressionToken[]} tokens
  1433. * @param {number} start
  1434. * @param {number} end
  1435. * @param {CellAddress} address
  1436. * @returns {CellExpression|null}
  1437. */
  1438. static #tryReference(tokens, start, end, address) {
  1439. if (start != end) return null;
  1440. if (!tokens[start].type.isPotentialAddress()) return null;
  1441. const ref = tokens[start].content.toUpperCase();
  1442. const refAddress = CellAddress.fromString(ref, address, true);
  1443. return new CellExpression(CellExpressionOperation.Reference, [ refAddress ]);
  1444. }
  1445. /**
  1446. * @param {CellExpressionToken[]} tokens
  1447. * @param {number} start
  1448. * @param {number} end
  1449. * @param {CellAddress} address
  1450. * @returns {CellExpression|null}
  1451. */
  1452. static #tryInfix(tokens, start, end, address) {
  1453. const count = end - start + 1;
  1454. if (count < 3) return null;
  1455. const opPriorities = {}
  1456. opPriorities[CellExpressionTokenType.Multiply.name] = 4;
  1457. opPriorities[CellExpressionTokenType.Divide.name] = 3;
  1458. opPriorities[CellExpressionTokenType.Plus.name] = 2;
  1459. opPriorities[CellExpressionTokenType.Minus.name] = 1;
  1460. opPriorities[CellExpressionTokenType.Ampersand.name] = 10;
  1461. opPriorities[CellExpressionTokenType.GreaterThan.name] = 20;
  1462. opPriorities[CellExpressionTokenType.GreaterThanEqual.name] = 20;
  1463. opPriorities[CellExpressionTokenType.LessThan.name] = 20;
  1464. opPriorities[CellExpressionTokenType.LessThanEqual.name] = 20;
  1465. opPriorities[CellExpressionTokenType.Equal.name] = 20;
  1466. opPriorities[CellExpressionTokenType.Unequal.name] = 20;
  1467. var candidates = [];
  1468. var parenLevel = 0;
  1469. var i;
  1470. for (i = start; i <= end; i++) {
  1471. if (tokens[i].type == CellExpressionTokenType.OpenParen) {
  1472. parenLevel++;
  1473. } else if (tokens[i].type == CellExpressionTokenType.CloseParen) {
  1474. parenLevel--;
  1475. } else if (parenLevel == 0 && i > start && i < end) {
  1476. const op = tokens[i].type.name;
  1477. const priority = opPriorities[op] ?? false;
  1478. if (priority === false) continue;
  1479. //console.info(`Found infix candidate at ${i} for ${op} priority ${priority}`);
  1480. candidates.push({ priority: priority, i: i });
  1481. }
  1482. }
  1483. candidates.sort((a, b) => a.priority - b.priority);
  1484. var bestCandidate = null;
  1485. var operand1, operand2;
  1486. for (const candidate of candidates) {
  1487. try {
  1488. i = candidate.i;
  1489. operand1 = this.#tryExpression(tokens, start, i - 1, address);
  1490. if (operand1 === null) continue;
  1491. operand2 = this.#tryExpression(tokens, i + 1, end, address);
  1492. if (operand2 === null) continue;
  1493. bestCandidate = candidate;
  1494. break;
  1495. } catch (e) {
  1496. if (!(e instanceof CellSyntaxException)) {
  1497. throw e;
  1498. }
  1499. }
  1500. }
  1501. if (bestCandidate === null) {
  1502. //console.info("No best candidate found");
  1503. return null;
  1504. }
  1505. i = bestCandidate.i;
  1506. //console.info(`Best candidate at token ${i}, priority ${bestCandidate.priority}`);
  1507. switch (tokens[bestCandidate.i].type) {
  1508. case CellExpressionTokenType.Plus:
  1509. return new CellExpression(CellExpressionOperation.Add, [ operand1, operand2 ]);
  1510. case CellExpressionTokenType.Minus:
  1511. return new CellExpression(CellExpressionOperation.Subtract, [ operand1, operand2 ]);
  1512. case CellExpressionTokenType.Multiply:
  1513. return new CellExpression(CellExpressionOperation.Multiply, [ operand1, operand2 ]);
  1514. case CellExpressionTokenType.Divide:
  1515. return new CellExpression(CellExpressionOperation.Divide, [ operand1, operand2 ]);
  1516. case CellExpressionTokenType.GreaterThan:
  1517. return new CellExpression(CellExpressionOperation.GreaterThan, [ operand1, operand2 ]);
  1518. case CellExpressionTokenType.GreaterThanEqual:
  1519. return new CellExpression(CellExpressionOperation.GreaterThanEqual, [ operand1, operand2 ]);
  1520. case CellExpressionTokenType.LessThan:
  1521. return new CellExpression(CellExpressionOperation.LessThan, [ operand1, operand2 ]);
  1522. case CellExpressionTokenType.LessThanEqual:
  1523. return new CellExpression(CellExpressionOperation.LessThanEqual, [ operand1, operand2 ]);
  1524. case CellExpressionTokenType.Equal:
  1525. return new CellExpression(CellExpressionOperation.Equal, [ operand1, operand2 ]);
  1526. case CellExpressionTokenType.Unequal:
  1527. return new CellExpression(CellExpressionOperation.Unequal, [ operand1, operand2 ]);
  1528. case CellExpressionTokenType.Ampersand:
  1529. return new CellExpression(CellExpressionOperation.Concatenate, [ operand1, operand2 ]);
  1530. }
  1531. return null;
  1532. }
  1533. /**
  1534. * @param {CellExpressionToken[]} tokens
  1535. * @param {number} start
  1536. * @param {number} end
  1537. * @param {CellAddress} address
  1538. * @returns {CellExpression|null}
  1539. */
  1540. static #tryUnary(tokens, start, end, address) {
  1541. const count = end - start + 1;
  1542. if (count < 2) return null;
  1543. const ops = [
  1544. [ CellExpressionTokenType.Minus, CellExpressionOperation.UnaryMinus ],
  1545. [ CellExpressionTokenType.Not, CellExpressionOperation.UnaryNot ],
  1546. ];
  1547. for (const op of ops) {
  1548. if (tokens[start].type != op[0]) continue;
  1549. const operand = this.#tryExpression(tokens, start + 1, end, address);
  1550. if (operand === null) return null;
  1551. return new CellExpression(op[1], [ operand ]);
  1552. }
  1553. return null;
  1554. }
  1555. /**
  1556. * @param {string} str
  1557. * @returns {boolean}
  1558. */
  1559. static #isReferenceName(str) {
  1560. return /^[a-z]+[0-9]*$/i.exec(str) !== null;
  1561. }
  1562. }
  1563. class CellExpressionToken {
  1564. /** @type {CellExpressionTokenType} */
  1565. type;
  1566. /** @type {string} */
  1567. content;
  1568. /**
  1569. * @param {CellExpressionTokenType} type
  1570. * @param {string} content
  1571. */
  1572. constructor(type, content) {
  1573. this.type = type;
  1574. this.content = content;
  1575. }
  1576. }
  1577. /**
  1578. * The location of a cell in a table. If the address was specified without a
  1579. * row, the address is considered "unresolved" and needs more context to
  1580. * uniquely identify a cell.
  1581. */
  1582. class CellAddress {
  1583. #name;
  1584. /**
  1585. * @type {string}
  1586. */
  1587. get name() { return this.#name; }
  1588. #isColumnFixed = false;
  1589. /**
  1590. * Whether the column should remain unchanged when transposed. This is
  1591. * symbolized by prefixing the column name with a `$` (e.g. `$C3`).
  1592. * @type {boolean}
  1593. */
  1594. get isColumnFixed() { return this.#isColumnFixed; }
  1595. #columnIndex = -1;
  1596. /**
  1597. * Zero-based column index.
  1598. * @type {number}
  1599. */
  1600. get columnIndex() { return this.#columnIndex; };
  1601. /**
  1602. * Letter code for the column.
  1603. * @type {string}
  1604. */
  1605. get columnLetter() { return CellAddress.#columnIndexToLetters(this.#columnIndex); }
  1606. #isRowFixed = false;
  1607. /**
  1608. * Whether the row should remain unchanged when transposed. This is
  1609. * symbolized by prefixing the row number with a `$` (e.g. `C$3`).
  1610. * @type {boolean}
  1611. */
  1612. get isRowFixed() { return this.#isRowFixed; }
  1613. #rowIndex = -1;
  1614. /**
  1615. * Zero-based row index.
  1616. * @type {number}
  1617. */
  1618. get rowIndex() { return this.#rowIndex; }
  1619. /**
  1620. * One-based row number. This is the human-facing row number.
  1621. */
  1622. get rowNumber() { return this.#rowIndex + 1; }
  1623. /**
  1624. * Whether this address has both a definite column and row.
  1625. * @type {boolean}
  1626. */
  1627. get isResolved() { return this.columnIndex >= 0 && this.rowIndex >= 0; }
  1628. /**
  1629. * @param {number} columnIndex - 0-based column index
  1630. * @param {number} rowIndex - 0-based row index
  1631. * @param {boolean} isColumnFixed - whether the column name is fixed in
  1632. * place during transpositions. Denoted with a `$` in front of the column letters.
  1633. * @param {boolean} isRowFixed - whether the row number is fixed in place
  1634. * during transpositions. Denoted with a `$` in front of the row digits.
  1635. */
  1636. constructor(columnIndex, rowIndex, isColumnFixed=false, isRowFixed=false) {
  1637. if (typeof columnIndex != 'number') {
  1638. throw new Error(`columnIndex must be number, got ${typeof columnIndex}`);
  1639. }
  1640. if (typeof rowIndex != 'number') {
  1641. throw new Error(`rowIndex must be number, got ${typeof rowIndex}`);
  1642. }
  1643. this.#columnIndex = columnIndex;
  1644. this.#rowIndex = rowIndex;
  1645. this.#isColumnFixed = isColumnFixed;
  1646. this.#isRowFixed = isRowFixed;
  1647. this.#name = CellAddress.#formatAddress(columnIndex, rowIndex, isColumnFixed, isRowFixed);
  1648. }
  1649. /**
  1650. * Tests if a string is formatted like an address.
  1651. * @param {string} text
  1652. * @returns {boolean}
  1653. */
  1654. static isAddress(text) {
  1655. return this.fromString(text) != null;
  1656. }
  1657. /**
  1658. * Returns a converted form of this address reference in a formula that has
  1659. * been copied from its original location. In other words, if a formula
  1660. * refers to a cell one to the left and that formula is copied to the next
  1661. * cell down, that copy's reference should point to the cell on the next
  1662. * row as well. Addresses with an absolute column or row (e.g. "A5") will
  1663. * not be altered on that axis.
  1664. *
  1665. * Examples:
  1666. * - C6.transpose(A5, A9) = C10 (A9-A5 = +4 rows, C6 + 4 rows = C10)
  1667. * - C6.transpose(A5, B9) = D10 (B9-A5 = +4 rows +1 cols, C6 + 4 rows + 1 col = D10)
  1668. * - C$6.transpose(A5, A9) = C6 (A9-A5 = +4 rows, but row is fixed, so still C6)
  1669. * - B.transpose(A5, A9) = B9 (A9-A4 = +4 rows, B has no row so last row used = B9)
  1670. * - A1.transpose(C3, A1) = null (out of bounds)
  1671. *
  1672. * @param {CellAddress} relativeFrom - original address of the formula
  1673. * @param {CellAddress} relativeTo - address where the formula is being repeated
  1674. * @param {boolean} resolveToRow - whether to fill in a row number if this address
  1675. * doesn't have one
  1676. * @returns {CellAddress|null} - resolved address, or `null` if out of bounds
  1677. */
  1678. transpose(relativeFrom, relativeTo, resolveToRow = true) {
  1679. if (!relativeFrom.isResolved || !relativeTo.isResolved) {
  1680. throw new CellEvaluationException("Can only transpose to and from resolved addresses");
  1681. }
  1682. var newColumnIndex = this.columnIndex;
  1683. if (!this.isColumnFixed) {
  1684. const columnDelta = relativeTo.columnIndex - relativeFrom.columnIndex;
  1685. newColumnIndex += columnDelta;
  1686. }
  1687. var newRowIndex = this.rowIndex;
  1688. if (!this.isResolved && resolveToRow) {
  1689. newRowIndex = relativeFrom.rowIndex;
  1690. }
  1691. if (newRowIndex != -1 && !this.isRowAbsolute) {
  1692. const rowDelta = relativeTo.rowIndex - relativeFrom.rowIndex;
  1693. newRowIndex += rowDelta;
  1694. }
  1695. if (newColumnIndex < 0 || newRowIndex < 0) return null;
  1696. return new CellAddress(newColumnIndex, newRowIndex);
  1697. }
  1698. equals(other) {
  1699. if (!(other instanceof CellAddress)) return false;
  1700. return other.columnIndex == this.columnIndex && other.rowIndex == this.rowIndex;
  1701. }
  1702. exactlyEquals(other) {
  1703. if (!(other instanceof CellAddress)) return false;
  1704. return other.name == this.name;
  1705. }
  1706. toString() {
  1707. return this.name;
  1708. }
  1709. /**
  1710. * Converts column letters (e.g. `A`, `C`, `AA`) to a 0-based column index.
  1711. * Assumes a validated well-formed column letter or else behavior is undefined.
  1712. *
  1713. * @param {string} letters
  1714. * @returns {number} column index
  1715. */
  1716. static #lettersToColumnIndex(letters) {
  1717. const ACodepoint = 'A'.codePointAt(0);
  1718. var columnIndex = 0;
  1719. for (var i = letters.length - 1; i >= 0; i--) {
  1720. const letterIndex = letters.codePointAt(i) - ACodepoint;
  1721. columnIndex = columnIndex * 26 + letterIndex;
  1722. }
  1723. return columnIndex;
  1724. }
  1725. /**
  1726. * Converts a column index to column letters (e.g. index 0 = `A`).
  1727. *
  1728. * @param {number} columnIndex
  1729. * @returns {string}
  1730. */
  1731. static #columnIndexToLetters(columnIndex) {
  1732. var letters = '';
  1733. if (columnIndex >= 0) {
  1734. const ACodepoint = 'A'.codePointAt(0);
  1735. var remaining = columnIndex;
  1736. do {
  1737. letters = String.fromCodePoint(ACodepoint + (remaining % 26)) + letters;
  1738. remaining = Math.floor(remaining / 26);
  1739. } while (remaining > 0);
  1740. }
  1741. return letters;
  1742. }
  1743. static #formatAddress(columnIndex, rowIndex, isColumnFixed, isRowFixed) {
  1744. var addr = '';
  1745. if (isColumnFixed && columnIndex >= 0) addr += '$';
  1746. if (columnIndex >= 0) addr += this.#columnIndexToLetters(columnIndex);
  1747. if (isRowFixed && rowIndex >= 0) addr += '$';
  1748. if (rowIndex >= 0) addr += `${rowIndex + 1}`;
  1749. return addr;
  1750. }
  1751. /**
  1752. * @param {string} address - cell address string
  1753. * @param {CellAddress|null} relativeTo - address to resolve relative addresses against
  1754. * @param {boolean} throwIfInvalid - whether to throw an error if address is invalid
  1755. * @returns {CellAddress|null} address, if parsable
  1756. * @throws
  1757. */
  1758. static fromString(address, relativeTo=null, throwIfInvalid=false) {
  1759. const groups = /^(\$?)([A-Z]{1,2}?)((?:\$(?=[0-9]))?)([0-9]*)$/i.exec(address);
  1760. if (groups === null) {
  1761. if (throwIfInvalid) throw new CellEvaluationException(`Bad address "${address}"`, '#REF');
  1762. return null;
  1763. }
  1764. const isColumnFixed = groups[1] == '$';
  1765. const letters = groups[2].toUpperCase();
  1766. const isRowFixed = groups[3] == '$';
  1767. const numbers = groups[4];
  1768. var columnIndex = this.#lettersToColumnIndex(letters);
  1769. var rowIndex = (numbers.length == 0) ? -1 : parseInt(numbers) - 1;
  1770. if (columnIndex < 0 && relativeTo != null) columnIndex = relativeTo.columnIndex;
  1771. if (rowIndex < 0 && relativeTo != null) rowIndex = relativeTo.rowIndex;
  1772. return new CellAddress(columnIndex, rowIndex, isColumnFixed, isRowFixed);
  1773. }
  1774. }
  1775. class CellAddressRange {
  1776. /** @type {boolean} */
  1777. isResolved;
  1778. /** @type {number} */
  1779. minColumnIndex;
  1780. /** @type {number} */
  1781. maxColumnIndex;
  1782. /** @type {number} */
  1783. minRowIndex;
  1784. /** @type {number} */
  1785. maxRowIndex;
  1786. /** @type {string} */
  1787. name;
  1788. /**
  1789. * @param {CellAddress} fromCell
  1790. * @param {CellAddress} toCell
  1791. */
  1792. constructor(fromCell, toCell) {
  1793. if (fromCell.isResolved != toCell.isResolved) {
  1794. throw new CellEvaluationException(`Cannot mix resolved and unresolved cell addresses in range: ${fromCell.name} and ${toCell.name}`);
  1795. }
  1796. this.minColumnIndex = Math.min(fromCell.columnIndex, toCell.columnIndex);
  1797. this.maxColumnIndex = Math.max(fromCell.columnIndex, toCell.columnIndex);
  1798. this.minRowIndex = Math.min(fromCell.rowIndex, toCell.rowIndex);
  1799. this.maxRowIndex = Math.max(fromCell.rowIndex, toCell.rowIndex);
  1800. this.isResolved = fromCell.isResolved;
  1801. this.name = (new CellAddress(this.minColumnIndex, this.minRowIndex)).name +
  1802. ':' +
  1803. (new CellAddress(this.maxColumnIndex, this.maxRowIndex)).name;
  1804. }
  1805. /**
  1806. * Creates an iterator for every `CellAddress` in this range within the
  1807. * confines of the given table's dimensions.
  1808. *
  1809. * @param {SpreadsheetGrid} table
  1810. * @returns {object} iterable object
  1811. */
  1812. cellsIn(table) {
  1813. const minCol = this.minColumnIndex < 0 ? 0 : this.minColumnIndex;
  1814. const maxCol = this.maxColumnIndex < 0 ? table.columnCount - 1 : Math.min(this.maxColumnIndex, table.columnCount - 1);
  1815. const minRow = this.minRowIndex < 0 ? 0 : this.minRowIndex;
  1816. const maxRow = this.maxRowIndex < 0 ? table.rowCount - 1 : Math.min(this.maxRowIndex, table.rowCount - 1);
  1817. const iterable = {};
  1818. iterable[Symbol.iterator] = function() {
  1819. var currentCol = minCol;
  1820. var currentRow = minRow;
  1821. return {
  1822. next() {
  1823. const col = currentCol;
  1824. const row = currentRow++;
  1825. if (currentRow > maxRow) {
  1826. currentRow = minRow;
  1827. currentCol++;
  1828. }
  1829. return {
  1830. value: new CellAddress(col, row),
  1831. done: col > maxCol || row > maxRow,
  1832. };
  1833. }
  1834. };
  1835. };
  1836. return iterable;
  1837. }
  1838. }
  1839. class CellValue {
  1840. /**
  1841. * Blank cell. `value` is `null`.
  1842. */
  1843. static TYPE_BLANK = 'blank';
  1844. /**
  1845. * Currency value. `value` is `number`.
  1846. */
  1847. static TYPE_CURRENCY = 'currency';
  1848. /**
  1849. * Regular number value. `value` is `number`.
  1850. */
  1851. static TYPE_NUMBER = 'number';
  1852. /**
  1853. * Percentage. `value` is `number`, represented as a ratio (100% = 1.0).
  1854. */
  1855. static TYPE_PERCENT = 'percent';
  1856. /**
  1857. * Unaltered text value. `value` is `string`.
  1858. */
  1859. static TYPE_STRING = 'string';
  1860. /**
  1861. * Boolean. `value` is `boolean`.
  1862. */
  1863. static TYPE_BOOLEAN = 'boolean';
  1864. /**
  1865. * A formula that has resulted in an error during parsing or evaluation.
  1866. * `value` is `string` error message.
  1867. */
  1868. static TYPE_ERROR = 'error';
  1869. /**
  1870. * A formula expression. `value` is `string` and includes the leading `=`.
  1871. */
  1872. static TYPE_FORMULA = 'formula';
  1873. // -- Properties -----
  1874. /**
  1875. * A blank value.
  1876. * @type {CellValue}
  1877. */
  1878. static BLANK = new CellValue('', null, CellValue.TYPE_BLANK);
  1879. /**
  1880. * Type of value. One of the `TYPE_` constants.
  1881. * @type {string}
  1882. */
  1883. type = CellValue.TYPE_STRING;
  1884. /**
  1885. * Number of decimal places shown in the formatted value.
  1886. * @type {number}
  1887. */
  1888. decimals = 0;
  1889. /**
  1890. * The string shown in the table cell to the user.
  1891. * @type {string}
  1892. */
  1893. formattedValue = '';
  1894. /**
  1895. * The PHP data value. E.g. a `float` for currency values or an `Exception`
  1896. * for errors.
  1897. * @type {any}
  1898. */
  1899. value = null;
  1900. /**
  1901. * Constructs a cell value explicitly. Values are not validated. Consider
  1902. * using `.fromCellString()` or `.fromValue()` to populate values more
  1903. * intelligently and consistently.
  1904. *
  1905. * @param {string} formattedValue
  1906. * @param {any} value
  1907. * @param {string} type
  1908. * @param {number} decimals
  1909. */
  1910. constructor(
  1911. formattedValue,
  1912. value = null,
  1913. type = CellValue.TYPE_STRING,
  1914. decimals = 0
  1915. ) {
  1916. this.formattedValue = formattedValue;
  1917. this.value = value;
  1918. this.type = type;
  1919. this.decimals = decimals;
  1920. }
  1921. /**
  1922. * Returns whether this value is a numeric type.
  1923. * @returns {boolean}
  1924. */
  1925. isNumeric() {
  1926. return CellValue.isTypeNumeric(this.type);
  1927. }
  1928. /**
  1929. * Creates a CellValue from formatted table cell contents. Attempts to
  1930. * detect formatted numbers including currency and percentages.
  1931. *
  1932. * @param {string} cellString
  1933. * @returns {CellValue}
  1934. */
  1935. static fromCellString(cellString) {
  1936. const cv = new CellValue(cellString);
  1937. cv.#populateFromCellString(cellString);
  1938. return cv;
  1939. }
  1940. /**
  1941. * Creates a CellValue from a PHP value. Based off PHP datatype, not
  1942. * string formatting; use `fromCellString` to parse formatted numbers.
  1943. *
  1944. * @param {any} value
  1945. * @param {string|null} type
  1946. * @param {number|null} decimals
  1947. * @returns {CellValue}
  1948. */
  1949. static fromValue(value, type = null, decimals = null) {
  1950. if (value === null) {
  1951. return new CellValue('', null, CellValue.TYPE_BLANK);
  1952. }
  1953. if (value instanceof Error) {
  1954. if (value instanceof CellException) {
  1955. return new CellValue(value.errorSymbol, value.message, CellValue.TYPE_ERROR);
  1956. }
  1957. return new CellValue('#ERROR', value.message, CellValue.TYPE_ERROR);
  1958. }
  1959. if (typeof value == 'boolean') {
  1960. const formatted = CellValue.formatType(value, CellValue.TYPE_BOOLEAN, 0);
  1961. return new CellValue(formatted, value, CellValue.TYPE_BOOLEAN);
  1962. }
  1963. if (typeof value == 'number') {
  1964. const resolvedType = type || CellValue.TYPE_NUMBER;
  1965. const resolvedDecimals = (decimals !== null) ? decimals :
  1966. (resolvedType == CellValue.TYPE_CURRENCY ? 2 : CellValue.#autodecimals(resolvedType == CellValue.TYPE_PERCENT ? value * 100.0 : value));
  1967. const formatted = CellValue.formatType(value, resolvedType, resolvedDecimals);
  1968. return new CellValue(formatted, value, resolvedType, resolvedDecimals);
  1969. }
  1970. if (typeof value != 'string') {
  1971. throw new CellEvaluationException(`Value of type ${typeof value} unsupported`);
  1972. }
  1973. const trimmed = value.trim();
  1974. if (trimmed.startsWith('=')) {
  1975. return new CellValue(trimmed, trimmed, CellValue.TYPE_FORMULA);
  1976. }
  1977. return new CellValue(trimmed, trimmed, CellValue.TYPE_STRING);
  1978. }
  1979. /**
  1980. * @param {string|null} cellString
  1981. */
  1982. #populateFromCellString(cellString) {
  1983. var matches = [];
  1984. cellString = (cellString !== null) ? cellString.trim() : null;
  1985. this.formattedValue = cellString;
  1986. // blank
  1987. if (cellString === null || cellString == '') {
  1988. this.type = CellValue.TYPE_BLANK;
  1989. this.value = null;
  1990. return;
  1991. }
  1992. // 'literal
  1993. if (cellString.startsWith("'")) {
  1994. const stripped = cellString.substring(1).trim();
  1995. this.type = CellValue.TYPE_STRING;
  1996. this.formattedValue = stripped;
  1997. this.value = stripped;
  1998. return;
  1999. }
  2000. // =TRUE
  2001. const caps = cellString.toUpperCase();
  2002. if (caps == 'TRUE') {
  2003. this.type = CellValue.TYPE_BOOLEAN;
  2004. this.formattedValue = caps;
  2005. this.value = true;
  2006. return;
  2007. }
  2008. // =FALSE
  2009. if (caps == 'FALSE') {
  2010. this.type = CellValue.TYPE_BOOLEAN;
  2011. this.formattedValue = caps;
  2012. this.value = false;
  2013. return;
  2014. }
  2015. // =A*B
  2016. if (cellString.startsWith('=')) {
  2017. this.type = CellValue.TYPE_FORMULA;
  2018. this.value = cellString;
  2019. return;
  2020. }
  2021. // -$1,234.56
  2022. var groups;
  2023. if (groups = /^([-]?)\$(-?[0-9,]*\.)([0-9]+)$/.exec(cellString)) {
  2024. const sign = groups[1];
  2025. const dollars = groups[2].replace(/,/g, '');
  2026. const cents = groups[3];
  2027. this.type = CellValue.TYPE_CURRENCY;
  2028. this.decimals = cents.length;
  2029. this.value = parseFloat(sign + dollars + cents);
  2030. return;
  2031. }
  2032. // -$1,234
  2033. if (groups = /^([-]?)\$(-?[0-9,]+)$/.exec(cellString)) {
  2034. const sign = groups[1];
  2035. const dollars = groups[2].replace(/,/g, '');
  2036. this.type = CellValue.TYPE_CURRENCY;
  2037. this.decimals = 0;
  2038. this.value = parseFloat(sign + dollars);
  2039. return;
  2040. }
  2041. // -1,234.56%
  2042. if (groups = /^([-]?[0-9,]*\.)([0-9,]+)%$/.exec(cellString)) {
  2043. const wholes = groups[1].replace(/,/, '');
  2044. const decimals = groups[2];
  2045. this.type = CellValue.TYPE_PERCENT;
  2046. this.decimals = decimals.length;
  2047. this.value = parseFloat(wholes + decimals) / 100.0;
  2048. return;
  2049. }
  2050. // -1,234%
  2051. if (groups = /^([-]?[0-9,]+)%$/.exec(cellString)) {
  2052. const wholes = groups[1].replace(/,/g, '');
  2053. this.type = CellValue.TYPE_PERCENT;
  2054. this.decimals = 0;
  2055. this.value = parseFloat(wholes) / 100.0;
  2056. return;
  2057. }
  2058. // -1,234.56
  2059. if (groups = /^([-]?[0-9,]*\.)([0-9]+)$/.exec(cellString)) {
  2060. const wholes = groups[1].replace(/,/g, '');
  2061. const decimals = groups[2];
  2062. this.type = CellValue.TYPE_NUMBER;
  2063. this.decimals = decimals.length;
  2064. this.value = parseFloat(wholes + decimals);
  2065. return;
  2066. }
  2067. // -1,234
  2068. if (groups = /^([-]?[0-9,]+)$/.exec(cellString)) {
  2069. const wholes = groups[1].replace(/,/g, '');
  2070. this.type = CellValue.TYPE_NUMBER;
  2071. this.decimals = 0;
  2072. this.value = parseFloat(wholes);
  2073. return;
  2074. }
  2075. this.type = CellValue.TYPE_STRING;
  2076. this.value = cellString;
  2077. }
  2078. /**
  2079. * Returns the boolean equivalent of this value if possible.
  2080. * @returns {boolean|null}
  2081. */
  2082. booleanValue() {
  2083. switch (this.type) {
  2084. case CellValue.TYPE_BLANK:
  2085. return false;
  2086. case CellValue.TYPE_BOOLEAN:
  2087. return this.value;
  2088. case CellValue.TYPE_CURRENCY:
  2089. case CellValue.TYPE_NUMBER:
  2090. case CellValue.TYPE_PERCENT:
  2091. return this.value != 0;
  2092. case CellValue.TYPE_ERROR:
  2093. case CellValue.TYPE_FORMULA:
  2094. case CellValue.TYPE_STRING:
  2095. return null;
  2096. }
  2097. }
  2098. /**
  2099. * Returns the numeric value of this value if possible.
  2100. * @returns {number|null}
  2101. */
  2102. numericValue() {
  2103. switch (this.type) {
  2104. case CellValue.TYPE_BLANK:
  2105. return 0.0;
  2106. case CellValue.TYPE_BOOLEAN:
  2107. return this.value ? 1.0 : 0.0;
  2108. case CellValue.TYPE_CURRENCY:
  2109. case CellValue.TYPE_NUMBER:
  2110. case CellValue.TYPE_PERCENT:
  2111. return this.value;
  2112. case CellValue.TYPE_ERROR:
  2113. case CellValue.TYPE_FORMULA:
  2114. case CellValue.TYPE_STRING:
  2115. return null;
  2116. }
  2117. }
  2118. /**
  2119. * Returns the string value of this value if possible.
  2120. * @param {boolean} formatted
  2121. * @returns {string|null}
  2122. */
  2123. stringValue(formatted = false) {
  2124. switch (this.type) {
  2125. case CellValue.TYPE_BLANK:
  2126. return '';
  2127. case CellValue.TYPE_BOOLEAN:
  2128. return this.value ? 'TRUE' : 'FALSE';
  2129. case CellValue.TYPE_CURRENCY:
  2130. case CellValue.TYPE_NUMBER:
  2131. case CellValue.TYPE_PERCENT:
  2132. return formatted ? this.formattedValue : `${this.value}`;
  2133. case CellValue.TYPE_STRING:
  2134. return formatted ? this.formattedValue : this.value;
  2135. case CellValue.TYPE_ERROR:
  2136. case CellValue.TYPE_FORMULA:
  2137. return null;
  2138. }
  2139. }
  2140. /**
  2141. * Returns the result of this value plus `b`.
  2142. *
  2143. * @param {CellValue} b
  2144. * @returns {CellValue} sum
  2145. */
  2146. add(b) {
  2147. return CellValue.#binaryNumericOperation(this, b, '+', (aVal, bVal) => aVal + bVal);
  2148. }
  2149. /**
  2150. * Returns the result of this value minus `b`.
  2151. *
  2152. * @param {CellValue} b
  2153. * @returns {CellValue} difference
  2154. */
  2155. subtract(b) {
  2156. return CellValue.#binaryNumericOperation(this, b, '-', (aVal, bVal) => aVal - bVal);
  2157. }
  2158. /**
  2159. * Returns the result of this value multiplied by `b`.
  2160. *
  2161. * @param {CellValue} b
  2162. * @returns {CellValue} product
  2163. */
  2164. multiply(b) {
  2165. return CellValue.#binaryNumericOperation(this, b, '*', (aVal, bVal) => aVal * bVal);
  2166. }
  2167. /**
  2168. * Returns the result of this value divided by `b`.
  2169. *
  2170. * @param {CellValue} b
  2171. * @returns {CellValue} quotient
  2172. * @throws {CellEvaluationException} on divide by zero
  2173. */
  2174. divide(b) {
  2175. return CellValue.#binaryNumericOperation(this, b, '/', (aVal, bVal) => {
  2176. if (bVal == 0) throw new CellEvaluationException("Division by zero", '#NAN');
  2177. return aVal / bVal
  2178. });
  2179. }
  2180. /**
  2181. * Returns the result of this value modulo by `b`.
  2182. *
  2183. * @param {CellValue} b
  2184. * @returns {CellValue} remainder
  2185. * @throws {CellEvaluationException} on divide by zero
  2186. */
  2187. modulo(b) {
  2188. return CellValue.#binaryNumericOperation(this, b, '%', (aVal, bVal) => {
  2189. if (bVal == 0) throw new CellEvaluationException("Division by zero", '#NAN');
  2190. return aVal % bVal
  2191. });
  2192. }
  2193. /**
  2194. * Returns the result of whether this value is greater than `b`.
  2195. *
  2196. * @param {CellValue} b
  2197. * @returns {CellValue}
  2198. */
  2199. gt(b) {
  2200. return CellValue.fromValue(CellValue.#compare(this, b) > 0);
  2201. }
  2202. /**
  2203. * Returns the result of whether tihs value is greater than or equal to `b`.
  2204. *
  2205. * @param {CellValue} b
  2206. * @returns {CellValue}
  2207. */
  2208. gte(b) {
  2209. return CellValue.fromValue(CellValue.#compare(this, b) >= 0);
  2210. }
  2211. /**
  2212. * Returns the result of whether this value is less than `b`.
  2213. * @param {CellValue} b
  2214. * @returns {CellValue}
  2215. */
  2216. lt(b) {
  2217. return CellValue.fromValue(CellValue.#compare(this, b) < 0);
  2218. }
  2219. /**
  2220. * Returns the result of whether this value is less than or equal to `b`.
  2221. * @param {CellValue} b
  2222. * @returns {CellValue}
  2223. */
  2224. lte(b) {
  2225. return CellValue.fromValue(CellValue.#compare(this, b) <= 0);
  2226. }
  2227. /**
  2228. * Returns the result of whether this value is equal to `b`.
  2229. * @param {CellValue} b
  2230. * @returns {CellValue}
  2231. */
  2232. eq(b) {
  2233. return CellValue.fromValue(CellValue.#compare(this, b) == 0);
  2234. }
  2235. /**
  2236. * Returns the result of whether this value is unequal to `b`.
  2237. * @param {CellValue} b
  2238. * @returns {CellValue}
  2239. */
  2240. neq(b) {
  2241. return CellValue.fromValue(CellValue.#compare(this, b) != 0);
  2242. }
  2243. /**
  2244. * Returns the boolean not of this value.
  2245. * @returns {CellValue}
  2246. */
  2247. not() {
  2248. switch (this.type) {
  2249. case CellValue.TYPE_BLANK:
  2250. return CellValue.fromValue(true);
  2251. case CellValue.TYPE_CURRENCY:
  2252. case CellValue.TYPE_NUMBER:
  2253. case CellValue.TYPE_PERCENT:
  2254. return CellValue.fromValue(this.value == 0);
  2255. case CellValue.TYPE_STRING:
  2256. throw new CellEvaluationException("Cannot perform NOT on string");
  2257. case CellValue.TYPE_BOOLEAN:
  2258. return CellValue.fromValue(!this.value);
  2259. case CellValue.TYPE_ERROR:
  2260. throw this.value;
  2261. case CellValue.TYPE_FORMULA:
  2262. throw new CellEvaluationException("Cannot perform NOT on expression");
  2263. }
  2264. }
  2265. /**
  2266. * @param {CellValue} b
  2267. * @returns {CellValue}
  2268. */
  2269. concatenate(b) {
  2270. const s1 = this.stringValue(true);
  2271. const s2 = b.stringValue(true);
  2272. if (s1 === null || s2 === null) {
  2273. throw new CellEvaluationException("Concatenation requires string arguments");
  2274. }
  2275. return CellValue.fromValue(`${s1}${s2}`);
  2276. }
  2277. /**
  2278. * @param {CellValue} a
  2279. * @param {CellValue} b
  2280. * @param {string} op
  2281. * @param {function} fn - takes two `number` arguments and returns a `number` result
  2282. * @returns {CellValue}
  2283. * @throws {CellEvaluationException}
  2284. */
  2285. static #binaryNumericOperation(a, b, op, fn) {
  2286. const ops = this.#resolveNumericOperands(a, b, op);
  2287. const aNum = ops[0];
  2288. const bNum = ops[1];
  2289. const type = ops[2];
  2290. const result = fn(aNum, bNum);
  2291. return CellValue.fromValue(result, type);
  2292. }
  2293. /**
  2294. * Determines what the result of a calculation with two operands should
  2295. * look like. Returns a tuple array of the A float value, the B float value,
  2296. * and the type of the result.
  2297. *
  2298. * @param {CellValue} a - operand A
  2299. * @param {CellValue} b - operand B
  2300. * @param {string} op - operator symbol
  2301. * @returns {Array} 3-element tuple array with A number value, B number value,
  2302. * and result type string
  2303. * @throws {CellEvaluationException} if types are incompatible for numeric operations
  2304. */
  2305. static #resolveNumericOperands(a, b, op) {
  2306. if (a.type == this.TYPE_ERROR) throw a.value;
  2307. if (b.type == this.TYPE_ERROR) throw b.value;
  2308. if (a.type == this.TYPE_STRING || b.type == this.TYPE_STRING) {
  2309. throw new CellEvaluationException("Cannot perform math on text values");
  2310. }
  2311. if (a.type == this.TYPE_BLANK) {
  2312. if (b.type == this.TYPE_BLANK) {
  2313. return [ 0, 0, this.TYPE_NUMBER, 0 ];
  2314. }
  2315. return [ 0, b.value, b.type ];
  2316. } else if (b.type == this.TYPE_BLANK) {
  2317. return [ a.value, 0, a.type ];
  2318. }
  2319. const isMultOrDiv = (op == '*' || op == '/' || op == '%');
  2320. if (a.type == b.type) {
  2321. return [ a.value, b.value, a.type ];
  2322. }
  2323. switch (a.type + b.type) {
  2324. case this.TYPE_CURRENCY + this.TYPE_NUMBER:
  2325. case this.TYPE_CURRENCY + this.TYPE_PERCENT:
  2326. return [ a.value, b.value, this.TYPE_CURRENCY ];
  2327. case this.TYPE_PERCENT + this.TYPE_CURRENCY:
  2328. return [ a.value, b.value,
  2329. isMultOrDiv ? this.TYPE_CURRENCY : this.TYPE_PERCENT ];
  2330. case this.TYPE_PERCENT + this.TYPE_NUMBER:
  2331. return [ a.value, b.value,
  2332. isMultOrDiv ? this.TYPE_NUMBER : this.TYPE_PERCENT ];
  2333. case this.TYPE_NUMBER + this.TYPE_CURRENCY:
  2334. return [ a.value, b.value, b.type ];
  2335. case this.TYPE_NUMBER + this.TYPE_PERCENT:
  2336. return [ a.value, b.value,
  2337. isMultOrDiv ? this.TYPE_NUMBER : b.type ];
  2338. case this.TYPE_BOOLEAN + this.TYPE_CURRENCY:
  2339. case this.TYPE_BOOLEAN + this.TYPE_NUMBER:
  2340. case this.TYPE_BOOLEAN + this.TYPE_PERCENT:
  2341. return [ a.value ? 1 : 0, b.value, b.type ];
  2342. case this.TYPE_CURRENCY + this.TYPE_BOOLEAN:
  2343. case this.TYPE_NUMBER + this.TYPE_BOOLEAN:
  2344. case this.TYPE_PERCENT + this.TYPE_BOOLEAN:
  2345. return [ a.value, b.value ? 1 : 0, a.type ];
  2346. }
  2347. throw new CellEvaluationException(`Unhandled operand types "${a.type}" and "${b.type}"`);
  2348. }
  2349. /**
  2350. * @param {CellValue} a
  2351. * @param {CellValue} b
  2352. * @returns {number}
  2353. */
  2354. static #compare(a, b) {
  2355. const args = CellValue.#resolveComparableArguments(a, b);
  2356. const valueA = args[0];
  2357. const valueB = args[1];
  2358. if (typeof valueA == 'string') {
  2359. return valueA.localeCompare(valueB, undefined, { sensitivity: 'accent' });
  2360. } else {
  2361. if (valueA < valueB) return -1;
  2362. if (valueA > valueB) return 1;
  2363. return 0;
  2364. }
  2365. }
  2366. /**
  2367. * @param {CellValue} a
  2368. * @param {CellValue} b
  2369. * @returns {Array}
  2370. */
  2371. static #resolveComparableArguments(a, b) {
  2372. if (a.type == CellValue.TYPE_ERROR) throw a.value;
  2373. if (b.type == CellValue.TYPE_ERROR) throw b.value;
  2374. if (a.type == CellValue.TYPE_FORMULA) throw new CellEvaluationException("Can't compare formula values");
  2375. if (b.type == CellValue.TYPE_FORMULA) throw new CellEvaluationException("Can't compare formula values");
  2376. const aNumValue = a.value;
  2377. const bNumValue = b.value;
  2378. const aStrValue = `${a.value}`;
  2379. const bStrValue = `${b.value}`;
  2380. switch (a.type) {
  2381. case CellValue.TYPE_BLANK:
  2382. aNumValue = 0;
  2383. aStrValue = '';
  2384. break;
  2385. case CellValue.TYPE_BOOLEAN:
  2386. aNumValue = (aNumValue) ? 1 : 0;
  2387. break;
  2388. }
  2389. switch (b.type) {
  2390. case CellValue.TYPE_BLANK:
  2391. bNumValue = 0;
  2392. bStrValue = '';
  2393. break;
  2394. case CellValue.TYPE_BOOLEAN:
  2395. bNumValue = (bNumValue) ? 1 : 0;
  2396. break;
  2397. }
  2398. if (a.type == CellValue.TYPE_STRING || b.type == CellValue.TYPE_STRING) {
  2399. return [ aStrValue, bStrValue ];
  2400. }
  2401. return [ aNumValue, bNumValue ];
  2402. }
  2403. /**
  2404. * Returns a formatted string for the given raw value, value type, and
  2405. * decimal places.
  2406. * @param {any} value
  2407. * @param {string} type
  2408. * @param {number} decimals
  2409. * @returns {string}
  2410. */
  2411. static formatType(value, type, decimals) {
  2412. switch (type) {
  2413. case CellValue.TYPE_BLANK: return '';
  2414. case CellValue.TYPE_CURRENCY: return CellValue.#formatCurrency(value, decimals);
  2415. case CellValue.TYPE_NUMBER: return CellValue.#formatNumber(value, decimals);
  2416. case CellValue.TYPE_PERCENT: return CellValue.#formatPercent(value, decimals);
  2417. case CellValue.TYPE_BOOLEAN: return value ? 'TRUE' : 'FALSE';
  2418. case CellValue.TYPE_STRING: return `${value}`;
  2419. case CellValue.TYPE_FORMULA: return `${value}`;
  2420. }
  2421. }
  2422. /**
  2423. * @param {number} value
  2424. * @param {number} decimals
  2425. * @returns {string}
  2426. */
  2427. static #formatNumber(value, decimals) {
  2428. return (value).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
  2429. }
  2430. /**
  2431. * @param {number} dollars
  2432. * @param {number} decimals
  2433. * @returns {string}
  2434. */
  2435. static #formatCurrency(dollars, decimals) {
  2436. var s = (dollars).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
  2437. if (s.startsWith('-')) {
  2438. return '-$' + s.substring(1);
  2439. }
  2440. return '$' + s;
  2441. }
  2442. /**
  2443. * @param {number} value
  2444. * @param {number} decimals
  2445. * @returns {string}
  2446. */
  2447. static #formatPercent(value, decimals) {
  2448. const dec = value * 100.0;
  2449. return (dec).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) + '%';
  2450. }
  2451. /**
  2452. * Determines a good number of decimal places to format a value to.
  2453. * @param {any} value
  2454. * @param {number} maxDigits
  2455. * @returns {number}
  2456. */
  2457. static #autodecimals(value, maxDigits = 6) {
  2458. if (value instanceof CellValue) {
  2459. return CellValue.#autodecimals(value.value);
  2460. }
  2461. if (typeof value == 'number') {
  2462. var s = (value).toLocaleString(undefined, { maximumFractionDigits: maxDigits });
  2463. if (/\./.exec(s) === null) return 0;
  2464. var fraction = s.split('.')[1];
  2465. return Math.min(maxDigits, fraction.length);
  2466. }
  2467. return 0;
  2468. }
  2469. /**
  2470. * @param {string} type
  2471. * @returns {boolean}
  2472. */
  2473. static isTypeNumeric(type) {
  2474. return type == CellValue.TYPE_NUMBER ||
  2475. type == CellValue.TYPE_PERCENT ||
  2476. type == CellValue.TYPE_CURRENCY ||
  2477. type == CellValue.TYPE_BOOLEAN;
  2478. }
  2479. toString() {
  2480. return `[CellValue type=${this.type} value=${this.value} "${this.formattedValue}"]`;
  2481. }
  2482. }
  2483. class SpreadsheetGrid {
  2484. /**
  2485. * Indexed by column then row.
  2486. * @type {SpreadsheetCell[][]}
  2487. */
  2488. cells;
  2489. /** @type {number} */
  2490. columnCount;
  2491. /** @type {number} */
  2492. rowCount;
  2493. /**
  2494. * @param {number} columnCount
  2495. * @param {number} rowCount
  2496. */
  2497. constructor(columnCount, rowCount) {
  2498. this.columnCount = columnCount;
  2499. this.rowCount = rowCount;
  2500. this.cells = new Array(columnCount);
  2501. for (var c = 0; c < columnCount; c++) {
  2502. this.cells[c] = new Array(rowCount);
  2503. for (var r = 0; r < rowCount; r++) {
  2504. this.cells[c][r] = new SpreadsheetCell();
  2505. }
  2506. }
  2507. }
  2508. /**
  2509. * @param {CellAddress} address
  2510. * @returns {SpreadsheetCell|null} cell, or `null` if no cell available
  2511. * @throws {CellEvaluationException} if the address it out of bounds
  2512. */
  2513. cellAt(address) {
  2514. const c = address.columnIndex, r = address.rowIndex;
  2515. if (c < 0 || c >= this.cells.length) throw new CellEvaluationException(`Unresolved cell address ${address.name}`, '#REF');
  2516. const row = this.cells[c];
  2517. if (r < 0 || r >= row.length) throw new CellEvaluationException(`Unresolved cell address ${address.name}`, '#REF');
  2518. return row[r];
  2519. }
  2520. valueAt(address) {
  2521. return this.cellAt(address)?.originalValue;
  2522. }
  2523. outputValueAt(address) {
  2524. return this.cellAt(address)?.outputValue;
  2525. }
  2526. }
  2527. class SpreadsheetCell {
  2528. /**
  2529. * @type {CellValue}
  2530. */
  2531. originalValue = CellValue.BLANK;
  2532. /**
  2533. * @type {CellValue|null}
  2534. */
  2535. outputValue = null;
  2536. /** @type {boolean} */
  2537. isCalculated = false;
  2538. /** @type {CellExpression|null} */
  2539. parsedExpression = null;
  2540. /**
  2541. * @type {CellValue|null}
  2542. */
  2543. get resolvedValue() { return this.outputValue ?? this.originalValue; }
  2544. }
  2545. class SpreadsheetBlockReader extends MDBlockReader {
  2546. readBlock(state) {
  2547. return null;
  2548. }
  2549. postProcess(state, blocks) {
  2550. for (const block of blocks) {
  2551. if (block instanceof MDTableBlock) {
  2552. this.#processTable(block, state);
  2553. }
  2554. }
  2555. }
  2556. /**
  2557. * @param {MDTableBlock} tableBlock
  2558. * @param {MDState} state
  2559. */
  2560. #processTable(tableBlock, state) {
  2561. // Measure table
  2562. const rowCount = tableBlock.bodyRows.length;
  2563. var columnCount = 0;
  2564. for (const row of tableBlock.bodyRows) {
  2565. columnCount = Math.max(columnCount, row.cells.length);
  2566. }
  2567. // Create and populate grid
  2568. const grid = new SpreadsheetGrid(columnCount, rowCount);
  2569. for (var c = 0; c < columnCount; c++) {
  2570. for (var r = 0; r < rowCount; r++) {
  2571. const cellBlock = tableBlock.bodyRows[r].cells[c];
  2572. if (cellBlock === undefined) continue;
  2573. const cellText = cellBlock.toPlaintext(state);
  2574. const gridCell = grid.cells[c][r];
  2575. gridCell.originalValue = CellValue.fromCellString(cellText);
  2576. }
  2577. }
  2578. // Calculate
  2579. const expressions = new CellExpressionSet(grid);
  2580. expressions.calculateCells();
  2581. // See if anything was calculated. If not, don't mess with table.
  2582. var isCalculated = false;
  2583. for (var c = 0; c < columnCount && !isCalculated; c++) {
  2584. for (var r = 0; r < rowCount; r++) {
  2585. if (grid.cells[c][r].isCalculated) {
  2586. isCalculated = true;
  2587. break;
  2588. }
  2589. }
  2590. }
  2591. if (!isCalculated) return;
  2592. // Copy results back to table
  2593. for (var c = 0; c < columnCount; c++) {
  2594. for (var r = 0; r < rowCount; r++) {
  2595. const cellBlock = tableBlock.bodyRows[r].cells[c];
  2596. if (cellBlock === undefined) continue;
  2597. const gridCell = grid.cells[c][r];
  2598. const gridValue = gridCell.outputValue;
  2599. const cellText = gridValue.formattedValue;
  2600. cellBlock.content = new MDInlineBlock(new MDTextSpan(cellText));
  2601. if (gridCell.isCalculated) {
  2602. cellBlock.cssClasses.push('calculated');
  2603. }
  2604. cellBlock.cssClasses.push(`spreadsheet-type-${gridValue.type}`);
  2605. if (gridValue.type == CellValue.TYPE_ERROR) {
  2606. cellBlock.attributes['title'] = gridValue.value;
  2607. }
  2608. const gridNumber = gridValue.numericValue();
  2609. if (gridNumber !== null) {
  2610. cellBlock.attributes['data-numeric-value'] = `${gridNumber}`;
  2611. }
  2612. const gridString = gridValue.stringValue(false);
  2613. if (gridString !== null) {
  2614. cellBlock.attributes['data-string-value'] = gridString;
  2615. }
  2616. }
  2617. }
  2618. }
  2619. }