PHP and Javascript implementations of a simple markdown parser
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

spreadsheet.js 80KB

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