runtime.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. function range(start, end, columnStep, rowStep) {
  2. columnStep = integer(columnStep ?? 1);
  3. rowStep = integer(rowStep ?? 1);
  4. start = parse_cell_name(start) ?? { column: "A", row: 0 };
  5. end = parse_cell_name(end) ?? start;
  6. if (end.column.length > 1 || start.column.length > 1)
  7. throw new TypeError("Only single-letter column names are allowed (TODO)");
  8. const cells = [];
  9. for (
  10. let col = Math.min(start.column.charCodeAt(0), end.column.charCodeAt(0));
  11. col <= Math.max(start.column.charCodeAt(0), end.column.charCodeAt(0));
  12. col += columnStep
  13. ) {
  14. for (
  15. let row = Math.min(start.row, end.row);
  16. row <= Math.max(start.row, end.row);
  17. row += rowStep
  18. ) {
  19. cells.push(String.fromCharCode(col) + row);
  20. }
  21. }
  22. return cells;
  23. }
  24. // FIXME: Remove this and use String.split() eventually
  25. function split(str, sep) {
  26. const parts = [];
  27. let splitIndex = -1;
  28. for (;;) {
  29. splitIndex = str.indexOf(sep);
  30. if (splitIndex == -1) {
  31. if (str.length) parts.push(str);
  32. return parts;
  33. }
  34. parts.push(str.substring(0, splitIndex));
  35. str = str.slice(splitIndex + sep.length);
  36. }
  37. }
  38. function R(fmt, ...args) {
  39. if (args.length !== 0) throw new TypeError("R`` format must be literal");
  40. fmt = fmt[0];
  41. return range(...split(fmt, ":"));
  42. }
  43. function select(criteria, t, f) {
  44. if (criteria) return t;
  45. return f;
  46. }
  47. function sumIf(condition, cells) {
  48. let sum = null;
  49. for (let name of cells) {
  50. let cell = thisSheet[name];
  51. if (condition(cell)) sum = sum === null ? cell : sum + cell;
  52. }
  53. return sum;
  54. }
  55. function countIf(condition, cells) {
  56. let count = 0;
  57. for (let name of cells) {
  58. let cell = thisSheet[name];
  59. if (condition(cell)) count++;
  60. }
  61. return count;
  62. }
  63. function now() {
  64. return new Date();
  65. }
  66. function repeat(count, str) {
  67. return Array(count + 1).join(str);
  68. }
  69. function randRange(min, max) {
  70. return Math.random() * (max - min) + min;
  71. }
  72. function integer(value) {
  73. return value | 0;
  74. }
  75. function sheet(name) {
  76. return workbook.sheet(name);
  77. }
  78. // Cheat the system and add documentation
  79. range.__documentation = JSON.stringify({
  80. name: "range",
  81. argc: 2,
  82. argnames: ["start", "end", "column step", "row step"],
  83. doc:
  84. "Generates a list of cell names in a rectangle defined by two " +
  85. "_top left_ and _bottom right_ cells `start` and `end`, spaced" +
  86. " `column step` columns, and `row step` rows apart.",
  87. examples: {
  88. 'range("A1", "C4")': "Generate a range A1:C4",
  89. 'range("A1", "C4", 2)': "Generate a range A1:C4, skipping every other column",
  90. },
  91. });
  92. select.__documentation = JSON.stringify({
  93. name: "select",
  94. argc: 3,
  95. argnames: ["criteria", "true value", "false value"],
  96. doc: "Selects between the two `true` and `false` values based on the value of `criteria`",
  97. examples: {
  98. "select(A1, A2, A3)": "Evaluates to A2 if A1 is true, A3 otherwise",
  99. },
  100. });
  101. sumIf.__documentation = JSON.stringify({
  102. name: "sumIf",
  103. argc: 2,
  104. argnames: ["condition", "cell names"],
  105. doc:
  106. "Calculates the sum of cells the value of which evaluates to true when passed to `condition`",
  107. examples: {
  108. 'sumIf(x => x instanceof Number, range("A1", "C4"))':
  109. "Calculates the sum of all numbers within A1:C4",
  110. },
  111. });
  112. countIf.__documentation = JSON.stringify({
  113. name: "countIf",
  114. argc: 2,
  115. argnames: ["condition", "cell names"],
  116. doc: "Counts cells the value of which evaluates to true when passed to `condition`",
  117. examples: {
  118. 'countIf(x => x instanceof Number, range("A1", "C4"))':
  119. "Counts the number of cells which have numbers within A1:C4",
  120. },
  121. });
  122. now.__documentation = JSON.stringify({
  123. name: "now",
  124. argc: 0,
  125. argnames: [],
  126. doc: "Returns a Date instance for the current moment",
  127. examples: {},
  128. });
  129. repeat.__documentation = JSON.stringify({
  130. name: "repeat",
  131. argc: 2,
  132. argnames: ["string", "count"],
  133. doc: "Returns a string equivalent to `string` repeated `count` times",
  134. examples: {
  135. 'repeat("a", 10)': 'Generates the string "aaaaaaaaaa"',
  136. },
  137. });
  138. randRange.__documentation = JSON.stringify({
  139. name: "randRange",
  140. argc: 2,
  141. argnames: ["start", "end"],
  142. doc: "Returns a random number in the range (`start`, `end`)",
  143. examples: {},
  144. });
  145. integer.__documentation = JSON.stringify({
  146. name: "integer",
  147. argc: 1,
  148. argnames: ["value"],
  149. doc: "Returns the integer value of `value`",
  150. examples: {
  151. "A1 = integer(A0)": "Sets the value of the cell A1 to the integer value of the cell A0",
  152. },
  153. });
  154. sheet.__documentation = JSON.stringify({
  155. name: "sheet",
  156. argc: 1,
  157. argnames: ["name or index"],
  158. doc: "Returns a reference to another sheet, identified by _name_ or _index_",
  159. examples: {
  160. "sheet('Sheet 1').A4": "Read the value of the cell A4 in a sheet named 'Sheet 1'",
  161. "sheet(0).A0 = 123": "Set the value of the cell A0 in the first sheet to 123",
  162. },
  163. });