|
1 | 1 | import { createMap } from '../../utils/map.js' |
2 | | -import { isFunctionNode, isNode, isOperatorNode, isParenthesisNode, isSymbolNode } from '../../utils/is.js' |
| 2 | +import { isNode } from '../../utils/is.js' |
3 | 3 | import { factory } from '../../utils/factory.js' |
4 | 4 |
|
5 | 5 | const name = 'resolve' |
@@ -46,65 +46,94 @@ export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({ |
46 | 46 | * If there is a cyclic dependency among the variables in `scope`, |
47 | 47 | * resolution is impossible and a ReferenceError is thrown. |
48 | 48 | */ |
49 | | - function _resolve (node, scope, within = new Set()) { // note `within`: |
50 | | - // `within` is not documented, since it is for internal cycle |
51 | | - // detection only |
52 | | - if (!scope) { |
53 | | - return node |
54 | | - } |
55 | | - if (isSymbolNode(node)) { |
56 | | - if (within.has(node.name)) { |
57 | | - const variables = Array.from(within).join(', ') |
58 | | - throw new ReferenceError( |
59 | | - `recursive loop of variable definitions among {${variables}}` |
60 | | - ) |
| 49 | + |
| 50 | + // First we set up core implementations for different node types |
| 51 | + // Note the 'within' argument that they all take is not documented, as it is |
| 52 | + // used only for internal cycle detection |
| 53 | + const resolvers = { |
| 54 | + SymbolNode: typed.referToSelf(self => |
| 55 | + (symbol, scope, within = new Set()) => { |
| 56 | + // The key case for resolve; most other nodes we just recurse. |
| 57 | + if (!scope) return symbol |
| 58 | + if (within.has(symbol.name)) { |
| 59 | + const variables = Array.from(within).join(', ') |
| 60 | + throw new ReferenceError( |
| 61 | + `recursive loop of variable definitions among {${variables}}` |
| 62 | + ) |
| 63 | + } |
| 64 | + const value = scope.get(symbol.name) |
| 65 | + if (isNode(value)) { |
| 66 | + const nextWithin = new Set(within) |
| 67 | + nextWithin.add(symbol.name) |
| 68 | + return self(value, scope, nextWithin) |
| 69 | + } |
| 70 | + if (typeof value === 'number') { |
| 71 | + return parse(String(value)) // ?? is this just to get the currently |
| 72 | + // defined behavior for number literals, i.e. maybe numbers are |
| 73 | + // currently being coerced to BigNumber? |
| 74 | + } |
| 75 | + if (value !== undefined) { |
| 76 | + return new ConstantNode(value) |
| 77 | + } |
| 78 | + return symbol |
| 79 | + } |
| 80 | + ), |
| 81 | + OperatorNode: typed.referToSelf(self => |
| 82 | + (operator, scope, within = new Set()) => { |
| 83 | + const args = operator.args.map(arg => self(arg, scope, within)) |
| 84 | + // Has its own implementation because we don't recurse on the op also |
| 85 | + return new OperatorNode( |
| 86 | + operator.op, operator.fn, args, operator.implicit) |
61 | 87 | } |
62 | | - const value = scope.get(node.name) |
63 | | - if (isNode(value)) { |
64 | | - const nextWithin = new Set(within) |
65 | | - nextWithin.add(node.name) |
66 | | - return _resolve(value, scope, nextWithin) |
67 | | - } else if (typeof value === 'number') { |
68 | | - return parse(String(value)) |
69 | | - } else if (value !== undefined) { |
70 | | - return new ConstantNode(value) |
71 | | - } else { |
72 | | - return node |
| 88 | + ), |
| 89 | + FunctionNode: typed.referToSelf(self => |
| 90 | + (func, scope, within = new Set()) => { |
| 91 | + const args = func.args.map(arg => self(arg, scope, within)) |
| 92 | + // The only reason this has a separate implementation of its own |
| 93 | + // is that we don't resolve the func.name itself. But is that |
| 94 | + // really right? If the tree being resolved was the parse of |
| 95 | + // 'f(x,y)' and 'f' is defined in the scope, is it clear that we |
| 96 | + // don't want to replace the function symbol, too? Anyhow, leaving |
| 97 | + // the implementation as it was before the refactoring. |
| 98 | + return new FunctionNode(func.name, args) |
73 | 99 | } |
74 | | - } else if (isOperatorNode(node)) { |
75 | | - const args = node.args.map(function (arg) { |
76 | | - return _resolve(arg, scope, within) |
77 | | - }) |
78 | | - return new OperatorNode(node.op, node.fn, args, node.implicit) |
79 | | - } else if (isParenthesisNode(node)) { |
80 | | - return new ParenthesisNode(_resolve(node.content, scope, within)) |
81 | | - } else if (isFunctionNode(node)) { |
82 | | - const args = node.args.map(function (arg) { |
83 | | - return _resolve(arg, scope, within) |
84 | | - }) |
85 | | - return new FunctionNode(node.name, args) |
86 | | - } |
| 100 | + ), |
| 101 | + Node: typed.referToSelf(self => |
| 102 | + (node, scope, within = new Set()) => { |
| 103 | + // The generic case: just recurse |
| 104 | + return node.map(child => self(child, scope, within)) |
| 105 | + } |
| 106 | + ) |
| 107 | + } |
87 | 108 |
|
88 | | - // Otherwise just recursively resolve any children (might also work |
89 | | - // for some of the above special cases) |
90 | | - return node.map(child => _resolve(child, scope, within)) |
| 109 | + // Now expand with all the possible argument types: |
| 110 | + const nodeTypes = Object.keys(resolvers) |
| 111 | + const scopeType = ', Map | null | undefined' |
| 112 | + const objType = ', Object' |
| 113 | + const withinType = ', Set' |
| 114 | + for (const nodeType of nodeTypes) { |
| 115 | + resolvers[nodeType + scopeType] = resolvers[nodeType] |
| 116 | + resolvers[nodeType + scopeType + withinType] = resolvers[nodeType] |
| 117 | + resolvers[nodeType + objType] = typed.referToSelf(self => |
| 118 | + (node, objScope) => self(node, createMap(objScope)) |
| 119 | + ) |
| 120 | + // Don't need to do nodeType + objType + withinType since we only get |
| 121 | + // an obj instead of a Map scope in the outermost call, which has no |
| 122 | + // "within" argument. |
91 | 123 | } |
92 | 124 |
|
93 | | - return typed('resolve', { |
94 | | - Node: _resolve, |
95 | | - 'Node, Map | null | undefined': _resolve, |
96 | | - 'Node, Object': (n, scope) => _resolve(n, createMap(scope)), |
97 | | - // For arrays and matrices, we map `self` rather than `_resolve` |
98 | | - // because resolve is fairly expensive anyway, and this way |
99 | | - // we get nice error messages if one entry in the array has wrong type. |
| 125 | + // Now add the array and matrix types: |
| 126 | + Object.assign(resolvers, { |
100 | 127 | 'Array | Matrix': typed.referToSelf(self => A => A.map(n => self(n))), |
101 | 128 | 'Array | Matrix, null | undefined': typed.referToSelf( |
102 | 129 | self => A => A.map(n => self(n))), |
| 130 | + 'Array | Matrix, Map': typed.referToSelf( |
| 131 | + self => (A, scope) => A.map(n => self(n, scope))), |
103 | 132 | 'Array, Object': typed.referTo( |
104 | 133 | 'Array,Map', selfAM => (A, scope) => selfAM(A, createMap(scope))), |
105 | 134 | 'Matrix, Object': typed.referTo( |
106 | | - 'Matrix,Map', selfMM => (A, scope) => selfMM(A, createMap(scope))), |
107 | | - 'Array | Matrix, Map': typed.referToSelf( |
108 | | - self => (A, scope) => A.map(n => self(n, scope))) |
| 135 | + 'Matrix,Map', selfMM => (A, scope) => selfMM(A, createMap(scope))) |
109 | 136 | }) |
| 137 | + |
| 138 | + return typed('resolve', resolvers) |
110 | 139 | }) |
0 commit comments