(index: number, expr: Json, source: string)
| 162 | } |
| 163 | |
| 164 | function normalizeRule(index: number, expr: Json, source: string): RubiRule { |
| 165 | const setDelayed = asCall(expr, 'SetDelayed'); |
| 166 | if (!setDelayed) throw new Error('rule is not a SetDelayed definition'); |
| 167 | const [, lhsCall, rhsExpr] = setDelayed; |
| 168 | |
| 169 | const int = asCall(lhsCall, 'Int'); |
| 170 | if (!int || int.length !== 3) throw new Error('LHS is not Int[…, x_Symbol]'); |
| 171 | const [, lhs, varPat] = int; |
| 172 | // The integration-variable slot is `x_Symbol` or plain `x_`. |
| 173 | const blank = asCall(varPat, 'Blank'); |
| 174 | if ( |
| 175 | !blank || |
| 176 | typeof blank[1] !== 'string' || |
| 177 | blank[1] === '' || |
| 178 | (blank[2] !== undefined && blank[2] !== 'Symbol') |
| 179 | ) |
| 180 | throw new Error('integration variable is not an x_/x_Symbol pattern'); |
| 181 | const variable = blank[1]; |
| 182 | |
| 183 | // Outer condition: rhs = Condition[body, cond] |
| 184 | let body = rhsExpr; |
| 185 | let condition: Json | null = null; |
| 186 | const outerCond = asCall(body, 'Condition'); |
| 187 | if (outerCond) { |
| 188 | body = outerCond[1]; |
| 189 | condition = outerCond[2]; |
| 190 | } |
| 191 | |
| 192 | // With/Module scope (possibly nested) with optional inner condition |
| 193 | const bindings: RubiRule['bindings'] = []; |
| 194 | let scoped: RubiRule['scoped'] = null; |
| 195 | let innerCondition: Json | null = null; |
| 196 | for (;;) { |
| 197 | const scope = asCall(body, 'With') ?? asCall(body, 'Module'); |
| 198 | if (!scope) break; |
| 199 | if (scope.length !== 3) |
| 200 | throw new Error(`${scope[0]} with ${scope.length - 1} arguments`); |
| 201 | scoped = scoped ?? ((scope[0] === 'With' ? 'with' : 'module') as const); |
| 202 | const locals = asCall(scope[1], 'List'); |
| 203 | if (!locals) throw new Error(`${scope[0]} locals are not a List`); |
| 204 | for (const local of locals.slice(1)) { |
| 205 | const set = asCall(local, 'Set'); |
| 206 | if (set && typeof set[1] === 'string') |
| 207 | bindings.push({ name: set[1], value: set[2] }); |
| 208 | else if (typeof local === 'string') |
| 209 | bindings.push({ name: local, value: null }); |
| 210 | else throw new Error('unsupported local binding form'); |
| 211 | } |
| 212 | body = scope[2]; |
| 213 | const inner = asCall(body, 'Condition'); |
| 214 | if (inner) { |
| 215 | body = inner[1]; |
| 216 | innerCondition = |
| 217 | innerCondition === null ? inner[2] : ['And', innerCondition, inner[2]]; |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | return { |
no test coverage detected