Unlisted
Edited
May 13, 2023
Importers
2 stars
Insert cell
Insert cell
viewof fn = eqn`f(theta, alpha) = sin(theta + alpha)`
Insert cell
fn(0.5, 0.7)
Insert cell
viewof f = eqn.block`f(theta) = max(1/2, theta, sqrt(1 - (2/3)^2)) * 4 * (1 + 2)`
Insert cell
f(0.5)
Insert cell
eqn.block`1 * 2 * 3 + 4*x + x * 4 + 2 + 4 * (1 + 2) + 4 * (3 * 4) + 4 * 3**4`
Insert cell
eqn.block`u = cos(theta), v = sin(theta)`
Insert cell
eqn.block`[u, v] = [cos(theta), sin(theta)]`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
input = "m * (1 - m * sin(x / 2)**2)"
Insert cell
ast = parse(input)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
serializeToString(parse("1 * -2"))
Insert cell
serializeToString(parse("-5 + -2 * 1"))
Insert cell
serializeToString(parse("1 + 2 + 3 + 4 * 5 * 6 + 2 * (3 + sn(4))"))
Insert cell
parse("3 * u^3")
Insert cell
Insert cell
Insert cell
Insert cell
function createFunctionFactory(functionLibrary) {
return function serializeToFunction(node) {
const vars = new Set();
const outputs = new Map();
const orderedArgs = [];
const arrayOutputs = [];

function serialize(node, parentPrec = -1) {
const parts = [];

const thisPrec = getNodePrecedence(node);
const wrap = parentPrec > thisPrec;

if (wrap) parts.push("(");

switch (node.type) {
case "UnaryExpression":
parts.push(node.operator);
parts.push(serialize(node.argument, thisPrec));
break;
case "BinaryExpression":
if (node.operator === "^") parts.push("(");
parts.push(serialize(node.left, thisPrec));
if (node.operator === "^") {
parts.push("**");
} else {
parts.push(node.operator);
}
parts.push(serialize(node.right, thisPrec));
if (node.operator === "^") parts.push(")");
break;
case "Literal":
parts.push(node.raw);
break;
case "Identifier":
vars.add(node.name);
parts.push(`args.${node.name}`);
break;
case "CallExpression":
const func = functionLibrary.functions[node.callee.name];
if (!func) {
throw new Error(`Unknown function '${node.callee.name}'`);
}
const funcMeta = functionLibrary.metadata[node.callee.name];
if (funcMeta) {
if (typeof funcMeta.arity === "number") {
if (node.arguments.length !== funcMeta.arity) {
throw new Error(
`Expected ${funcMeta.arity} arguments for function '${node.callee.name}' but got ${node.arguments.length}`
);
}
} else if (Array.isArray(funcMeta.arity)) {
if (
node.arguments.length < funcMeta.arity[0] ||
node.arguments.length > funcMeta.arity[1]
) {
throw new Error(
`Expected between ${funcMeta.arity[0]} and ${funcMeta.arity[1]} arguments for function '${node.callee.name}' but got ${node.arguments.length}`
);
}
}
}

parts.push(
`lib.${node.callee.name}(${node.arguments
.map(serialize)
.join(", ")})`
);
break;
case "AssignmentExpression":
if (node.left.type === "Identifier") {
outputs.set(node.left.name, serialize(node.right, thisPrec));
} else if (node.left.type === "ArrayExpression") {
if (node.right.type === "ArrayExpression") {
for (const element of node.right.elements) {
arrayOutputs.push(serialize(element));
}
} else {
throw new Error(
`ArrayExpression assignment lvalue must be accompanied by ArrayExpression rvalue but got ${node.left.type}.`
);
}
} else if (node.left.type === "CallExpression") {
if (orderedArgs.length) {
throw new Error(
`Only one lvalue CallExpression may be present in an equation. Try removing the arguments from your lvalues.`
);
}
outputs.set(node.left.callee.name, serialize(node.right, thisPrec));
if (node.left.callee.type !== "Identifier") {
throw new Error(
`Name of lvalue CallExpression must be an Identifier but got ${node.left.callee.type}.`
);
}
for (let i = 0; i < node.left.arguments.length; i++) {
const arg = node.left.arguments[i];
if (arg.type !== "Identifier") {
throw new Error(
`Arguments of lvalue CallExpression must have type Identifier, but argument ${
i + 1
} has type ${arg.type}`
);
}
orderedArgs.push(arg.name);
}
} else {
throw new Error(
`Expected Identifier or ArrayExpression for assignment lvalue but got ${node.left.type}.`
);
}
break;
case "Compound":
for (const part of node.body) {
parts.push(serialize(part, thisPrec));
}
break;
case "ArrayExpression":
parts.push(
`[${node.elements
.map((node) => serialize(node, thisPrec))
.join(",")}]`
);
break;

default:
throw new Error(
`Internal error in createFunction: Unhandled node: ${JSON.stringify(
node,
null,
2
)}`
);
}

if (wrap) parts.push(")");

return parts.join(" ");
}
if (outputs.length > 1) orderedArgs.length = 0;

let str = serialize(node, vars);

const hasArgs = orderedArgs.length;
const singleOutput = outputs.size === 1;

// Major hack! Remove `args.` from args if the argument order is specified
// by providing a lvalue CallExpression
function fixArgs(str) {
return hasArgs ? str.replace(/\bargs\./g, "") : str;
}

// prettier-ignore
const code = `return function (${
hasArgs ? orderedArgs.join(", ") : "args"
}) {
${
hasArgs
? ""
: Array.from(vars)
.map(
(v) =>
` if (typeof args['${v}'] !== 'number') throw new Error("Expected numeric value for ${v} but got " + (typeof args['${v}']));`
)
.join("\n")
}
${
outputs.size > 1
? ` const out = {};
${[...outputs].map(([id, fn]) => ` out['${id}'] = ${fixArgs(fn)};`).join("\n")}
return out;`
: arrayOutputs.length
? ` return [
${arrayOutputs.map((fn) => ` ${fixArgs(fn)},`).join("\n")}
];`
: ` return ${fixArgs(
singleOutput ? [...outputs.values()][0] : str
)};`
}
}`;
console.log(code);
return new Function("lib", code)(functionLibrary.functions);
};
}
Insert cell
createFunction = createFunctionFactory(mathBuiltins)
Insert cell
createFunction(parse("asdf(x)"))
Insert cell
sin1 = createFunction(parse("sin(1)")).toString()
Insert cell
eqn`f(x, y ) = x + y`.value.toString()
Insert cell
Insert cell
tex`${serializeToTex(parse("4 * 3"))}`
Insert cell
tex`${serializeToTex(parse("2 * (3 * 4)"))}`
Insert cell
tex`${serializeToTex(parse("2 * 3 ** 4"))}`
Insert cell
tex`${serializeToTex(parse("3 * u ** 3"))}`
Insert cell
parse(`-(u^3)`)
Insert cell
createFunction(parse(`-(u^3)`))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
eqn = eqnFactory(mathBuiltins)
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more