Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/expression/node/FunctionNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({
}
}

/**
* TODO
* @param {*} scope
* @param {*} within
* @returns
*/
resolve (scope, within = new Set()) {
return new FunctionNode(this.name, this.args.map((a) => a.resolve(scope, within)))
}

/**
* Execute a callback for each of the child nodes of this node
* @param {function(child: Node, path: string, parent: Node)} callback
Expand Down
7 changes: 7 additions & 0 deletions src/expression/node/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export const createNode = /* #__PURE__ */ factory(name, dependencies, ({ mathWit
}
}

/**
* TODO
*/
resolve (scope, within = new Set()) {
return this.map(child => child.resolve(scope, within))
}

/**
* Compile a node into a JavaScript function.
* This basically pre-calculates as much as possible and only leaves open
Expand Down
10 changes: 10 additions & 0 deletions src/expression/node/OperatorNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({
}
}

/**
* TODO
* @param {*} scope
* @param {*} within
* @returns
*/
resolve (scope, within = new Set()) {
return new OperatorNode(this.op, this.fn, this.args.map(a => a.resolve(scope, within)), this.implicit)
}

/**
* Execute a callback for each of the child nodes of this node
* @param {function(child: Node, path: string, parent: Node)} callback
Expand Down
10 changes: 10 additions & 0 deletions src/expression/node/ParenthesisNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ export const createParenthesisNode = /* #__PURE__ */ factory(name, dependencies,
return this.content._compile(math, argNames)
}

/**
* TODO
* @param {*} scope
* @param {*} within
* @returns
*/
resolve (scope, within = new Set()) {
return new ParenthesisNode(this.content.resolve(scope, within))
}

/**
* Get the content of the current Node.
* @return {Node} content
Expand Down
35 changes: 33 additions & 2 deletions src/expression/node/SymbolNode.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { escape } from '../../utils/string.js'
import { getSafeProperty } from '../../utils/customs.js'
import { isNode } from '../../utils/is.js'
import { factory } from '../../utils/factory.js'
import { toSymbol } from '../../utils/latex.js'

const name = 'SymbolNode'
const dependencies = [
'math',
'?Unit',
'Node'
'Node',
'ConstantNode'
]

export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ math, Unit, Node }) => {
export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ math, Unit, Node, ConstantNode, parse }) => {
/**
* Check whether some name is a valueless unit like "inch".
* @param {string} name
Expand Down Expand Up @@ -41,6 +43,35 @@ export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ m
get type () { return 'SymbolNode' }
get isSymbolNode () { return true }

/**
* TODO
* @param {*} scope
* @param {*} within
* @returns
*/
resolve (scope, within = new Set()) {
if (within.has(this.name)) {
const variables = Array.from(within).join(', ')
throw new ReferenceError(
`recursive loop of variable definitions among {${variables}}`
)
}

const value = scope.get(this.name)

if (isNode(value)) {
const nextWithin = new Set(within)
nextWithin.add(this.name)
return value.resolve(scope, nextWithin)
} else if (typeof value === 'number') {
return math.parse(String(value))
} else if (value !== undefined) {
return new ConstantNode(value)
} else {
return this
}
}

/**
* Compile a node into a JavaScript function.
* This basically pre-calculates as much as possible and only leaves open
Expand Down
51 changes: 3 additions & 48 deletions src/function/algebra/resolve.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import { createMap } from '../../utils/map.js'
import { isFunctionNode, isNode, isOperatorNode, isParenthesisNode, isSymbolNode } from '../../utils/is.js'
import { factory } from '../../utils/factory.js'

const name = 'resolve'
const dependencies = [
'typed',
'parse',
'ConstantNode',
'FunctionNode',
'OperatorNode',
'ParenthesisNode'
'typed'
]

export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({
typed,
parse,
ConstantNode,
FunctionNode,
OperatorNode,
ParenthesisNode
typed
}) => {
/**
* resolve(expr, scope) replaces variable nodes with their scoped values
Expand Down Expand Up @@ -52,42 +41,8 @@ export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({
if (!scope) {
return node
}
if (isSymbolNode(node)) {
if (within.has(node.name)) {
const variables = Array.from(within).join(', ')
throw new ReferenceError(
`recursive loop of variable definitions among {${variables}}`
)
}
const value = scope.get(node.name)
if (isNode(value)) {
const nextWithin = new Set(within)
nextWithin.add(node.name)
return _resolve(value, scope, nextWithin)
} else if (typeof value === 'number') {
return parse(String(value))
} else if (value !== undefined) {
return new ConstantNode(value)
} else {
return node
}
} else if (isOperatorNode(node)) {
const args = node.args.map(function (arg) {
return _resolve(arg, scope, within)
})
return new OperatorNode(node.op, node.fn, args, node.implicit)
} else if (isParenthesisNode(node)) {
return new ParenthesisNode(_resolve(node.content, scope, within))
} else if (isFunctionNode(node)) {
const args = node.args.map(function (arg) {
return _resolve(arg, scope, within)
})
return new FunctionNode(node.name, args)
}

// Otherwise just recursively resolve any children (might also work
// for some of the above special cases)
return node.map(child => _resolve(child, scope, within))
return node.resolve(scope, within)
}

return typed('resolve', {
Expand Down
4 changes: 4 additions & 0 deletions test/unit-tests/expression/node/OperatorNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ describe('OperatorNode', function () {
assert.strictEqual(add23.compile().evaluate(), 5)
})

it('should resolve an OperatorNode', function () {
// TODO
})

it('should test whether a unary or binary operator', function () {
const n1 = new OperatorNode('-', 'unaryMinus', [two])
assert.strictEqual(n1.isUnary(), true)
Expand Down
4 changes: 4 additions & 0 deletions test/unit-tests/expression/node/ParenthesisNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ describe('ParenthesisNode', function () {
assert.strictEqual(n.compile().evaluate.toString(), a.compile().evaluate.toString())
})

it('should resolve a ParenthesisNode', function () {
// TODO
})

it('should filter a ParenthesisNode', function () {
const a = new ConstantNode(1)
const n = new ParenthesisNode(a)
Expand Down
4 changes: 4 additions & 0 deletions test/unit-tests/expression/node/SymbolNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ describe('SymbolNode', function () {
assert.strictEqual(expr2.evaluate(scope2), math.sqrt)
})

it('should resolve a SymbolNode', function () {
// TODO
})

it('should filter a SymbolNode', function () {
const n = new SymbolNode('x')
assert.deepStrictEqual(n.filter(function (node) { return node instanceof SymbolNode }), [n])
Expand Down