|
| 1 | +import { asFloat } from './numerics/numeric'; |
| 2 | +import { BoxedExpression } from './public'; |
| 3 | + |
| 4 | +/** |
| 5 | + * |
| 6 | + * Iterate over all the expressions in an expression tree. |
| 7 | + * |
| 8 | + * Some expressions are not iterated because they are evaluated |
| 9 | + * to an "elementary" collection, for example "Fill". |
| 10 | + * |
| 11 | + * Some expressions are infinite and not iterable, for example |
| 12 | + * "Repeat", "Cycle", ... |
| 13 | + * |
| 14 | + * @param col |
| 15 | + * |
| 16 | + * @returns |
| 17 | + */ |
| 18 | +export function* iterable( |
| 19 | + col: BoxedExpression, |
| 20 | + exclude?: string[] |
| 21 | +): Generator<BoxedExpression> { |
| 22 | + const ce = col.engine; |
| 23 | + const h = col.head; |
| 24 | + |
| 25 | + if (typeof h === 'string' && exclude?.includes(h)) { |
| 26 | + yield col; |
| 27 | + return; |
| 28 | + } |
| 29 | + |
| 30 | + const iter = iterator(col); |
| 31 | + if (iter) { |
| 32 | + let i = 0; |
| 33 | + while (true) { |
| 34 | + const { done, value } = iter.next(); |
| 35 | + if (done) return; |
| 36 | + if (i++ > ce.iterationLimit) { |
| 37 | + yield ce.error('iteration-limit-exceeded'); |
| 38 | + return; |
| 39 | + } |
| 40 | + yield value; |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + // if (h === 'Range') { |
| 45 | + // let lower = asFloat(col.op1); |
| 46 | + // if (lower === null) return; |
| 47 | + // let upper = asFloat(col.op2); |
| 48 | + // if (upper === null) { |
| 49 | + // upper = lower; |
| 50 | + // lower = 1; |
| 51 | + // } |
| 52 | + |
| 53 | + // if (!isFinite(lower) || !isFinite(upper)) return; |
| 54 | + |
| 55 | + // if (lower > upper) { |
| 56 | + // const step = asFloat(col.op3 ?? -1) ?? -1; |
| 57 | + // if (step >= 0) return; |
| 58 | + // for (let i = lower; i <= upper; i += step) yield ce.number(i); |
| 59 | + // return; |
| 60 | + // } |
| 61 | + |
| 62 | + // const step = asFloat(col.op3 ?? 1) ?? 1; |
| 63 | + // if (step <= 0) return; |
| 64 | + // for (let i = lower; i <= upper; i += step) yield ce.number(i); |
| 65 | + // return; |
| 66 | + // } |
| 67 | + |
| 68 | + // if (h === 'Linspace') { |
| 69 | + // let start = asFloat(col.op1); |
| 70 | + // if (start === null) return; |
| 71 | + // let stop = asFloat(col.op2); |
| 72 | + // if (stop === null) { |
| 73 | + // stop = start; |
| 74 | + // start = 0; |
| 75 | + // } |
| 76 | + // const num = asFloat(col.op3) ?? 50; |
| 77 | + // if (!Number.isInteger(num)) return; |
| 78 | + // if (num <= 0) return; |
| 79 | + |
| 80 | + // if (!isFinite(stop) || !isFinite(start)) return; |
| 81 | + |
| 82 | + // const step = (stop - start) / (num - 1); |
| 83 | + |
| 84 | + // for (let i = start; i <= stop; i += step) yield ce.number(i); |
| 85 | + // return; |
| 86 | + // } |
| 87 | + |
| 88 | + // // Sequence are automatically flattended |
| 89 | + // if (h === 'Sequence') { |
| 90 | + // for (const x of col.ops!) { |
| 91 | + // if (x.head === 'Sequence') yield* each(x.ops!); |
| 92 | + // else yield x; |
| 93 | + // } |
| 94 | + // return; |
| 95 | + // } |
| 96 | + |
| 97 | + // if ( |
| 98 | + // typeof h === 'string' && |
| 99 | + // /^(List|Set|Tuple|Single|Pair|Triple)$/.test(h) |
| 100 | + // ) { |
| 101 | + // for (const x of col.ops!) yield x; |
| 102 | + // return; |
| 103 | + // } |
| 104 | + |
| 105 | + yield col; |
| 106 | +} |
| 107 | + |
| 108 | +/** |
| 109 | + * Iterate over all the expressions in an expression tree with |
| 110 | + * the following form: |
| 111 | + * - `["Range"]`, `["Interval"]`, `["Linspace"]` expressions |
| 112 | + * - `["List"]` and `["Set"]` expressions |
| 113 | + * - `["Tuple"]`, `["Pair"]`, `["Pair"]`, `["Triple"]` expressions |
| 114 | + * - `["Sequence"]` expressions |
| 115 | + * |
| 116 | + * @param exclude a list of expression heads to exclude from the |
| 117 | + * recursive iteration. They are instead retured as is. |
| 118 | + * |
| 119 | + * |
| 120 | + */ |
| 121 | +export function* each( |
| 122 | + ops: BoxedExpression[], |
| 123 | + exclude?: string[] |
| 124 | +): Generator<BoxedExpression> { |
| 125 | + if (ops.length === 0) return; |
| 126 | + |
| 127 | + for (const op of ops) for (const val of iterable(op, exclude)) yield val; |
| 128 | +} |
| 129 | + |
| 130 | +/** |
| 131 | + * From an expression, create an iterator that can be used |
| 132 | + * to enumerate values. |
| 133 | + * |
| 134 | + * `expr` can be a collection, a function, an expression, a string. |
| 135 | + * |
| 136 | + * - ["Range", 5] |
| 137 | + * - ["List", 1, 2, 3] |
| 138 | + * - "'hello world'" |
| 139 | + * |
| 140 | + * |
| 141 | + */ |
| 142 | +export function iterator( |
| 143 | + expr: BoxedExpression |
| 144 | +): Iterator<BoxedExpression> | undefined { |
| 145 | + // Is it a function expresson with a definition that includes an iterator? |
| 146 | + // e.g. ["Range", 5] |
| 147 | + // Note that if there is an at() handler, there is always |
| 148 | + // at least a default iterator |
| 149 | + const def = expr.functionDefinition; |
| 150 | + if (def?.iterator) return def.iterator(expr); |
| 151 | + |
| 152 | + // |
| 153 | + // String iterator |
| 154 | + // |
| 155 | + const s = expr.string; |
| 156 | + if (s !== null) { |
| 157 | + if (s.length === 0) |
| 158 | + return { next: () => ({ done: true, value: undefined }) }; |
| 159 | + let i = 0; |
| 160 | + return { |
| 161 | + next: () => ({ |
| 162 | + value: expr.engine.string(s.charAt(i++)), |
| 163 | + done: i > s.length, |
| 164 | + }), |
| 165 | + }; |
| 166 | + } |
| 167 | + |
| 168 | + return undefined; |
| 169 | +} |
| 170 | + |
| 171 | +/** |
| 172 | + * indexable(expr) return a JS function with one argument. |
| 173 | + * |
| 174 | + * Evaluate expr. |
| 175 | + * If expr is indexable function (def with at handler), return handler. |
| 176 | + * Otherwise, call makeLambda, then return function that set scope |
| 177 | + * with one arg, then evaluate result of makeLambda. |
| 178 | + */ |
| 179 | + |
| 180 | +// export function indexable( |
| 181 | +// expr: BoxedExpression |
| 182 | +// ): ((index: number) => BoxedExpression | undefined) | undefined { |
| 183 | +// expr = expr.evaluate(); |
| 184 | + |
| 185 | +// // If the function expression is indexable (it has an at() handler) |
| 186 | +// // return the at() handler, bound to this expression. |
| 187 | +// if (expr.functionDefinition?.at) { |
| 188 | +// const at = expr.functionDefinition.at; |
| 189 | +// return (index) => at(expr, index); |
| 190 | +// } |
| 191 | + |
| 192 | +// // |
| 193 | +// // String at |
| 194 | +// // |
| 195 | +// const s = expr.string; |
| 196 | +// if (s !== null) { |
| 197 | +// return (index) => { |
| 198 | +// const c = s.charAt(index); |
| 199 | +// if (c === undefined) return expr.engine.Nothing; |
| 200 | +// return expr.engine.string(c); |
| 201 | +// }; |
| 202 | +// } |
| 203 | + |
| 204 | +// // Expressions that don't have an at() handler, have the |
| 205 | +// // argument applied to them. |
| 206 | +// const lambda = makeLambda(expr); |
| 207 | +// if (lambda) return (index) => lambda([expr.engine.number(index)]); |
| 208 | + |
| 209 | +// return undefined; |
| 210 | +// } |
0 commit comments