PHP and Javascript implementations of a simple markdown parser
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

spreadsheet.js 82KB

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