Public
Edited
Sep 20, 2023
Importers
Insert cell
Insert cell
Insert cell
function extractExpressionRoot(str) {
const parseRes = acorn.parse(str);
if (parseRes.type !== 'Program') throw new Error();
if (parseRes.body.length !== 1) {
throw new Error('Expecting single expression');
}
if (parseRes.body[0].type !== 'ExpressionStatement') {
throw new Error(`Expecting expression, not ${parseRes.body[0].type}`);
}
return parseRes.body[0].expression;
}
Insert cell
function visiter(node) {
if (node.type === 'CallExpression') {
return ['call', visiter(node.callee), ...node.arguments.map(arg => visiter(arg))];
} else if (node.type === 'Identifier') {
return ['name', node.name];
} else if (node.type === 'Literal') {
return ['const', node.raw];
} else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
return ['call', ['name', node.operator], visiter(node.left), visiter(node.right)];
} else if (node.type === 'ConditionalExpression') {
return [
'call',
['name', '?:'],
visiter(node.test),
visiter(node.consequent),
visiter(node.alternate),
];
} else if (node.type === 'UnaryExpression' || node.type === 'UpdateExpression') {
const op = node.prefix ? node.operator + '@' : '@' + node.operator;
return ['call', ['name', op], visiter(node.argument)];
} else if (node.type === 'MemberExpression') {
if (node.property.type === 'Identifier') {
return ['call', ['name', '.' + node.property.name], visiter(node.object)];
} else if (node.property.type === 'Literal') {
return ['call', ['name', '[]'], visiter(node.property)];
} else {
throw new Error(`Unexpected MemberExpression property type: "${node.property.type}"`);
}
throw new Error(`Member property is not identifier`);
} else {
throw new Error(`Unexpected node type: "${node.type}"`);
}
}
Insert cell
function toString(node) {
if (node[0] === '') {
return '[' + toString(node[1]) + ']';
} else if (node[0] === 'call') {
const fun = toString(node[1]);
const args = node.slice(2).map(arg => toString(arg));
return fun + '(' + args.join(', ') + ')';
} else if (node[0] === 'name') {
return node[1];
} else if (node[0] === 'const') {
return node[1];
} else {
throw new Error(`Unexpected node type: "${node[0]}"`)
}
}
Insert cell
function parseExpr(str) {
return visiter(extractExpressionRoot(str));
}
Insert cell
toString(parseExpr('float((mod(p.x, size * 1.2) < size) == (mod(p.y, size * 1.2) < size))'))
Insert cell
acorn = require('acorn')
Insert cell
Insert cell
operatorPriorities = ({
'': 1,
'[]': 2,
'.': 2,
'@++': 2,
'@--': 2,
'++@': 3,
'--@': 3,
'+@': 3,
'-@': 3,
'!@': 3,
'*': 4,
'/': 4,
// '%': 4,
'+': 5,
'-': 5,
// '<<': 6,
// '>>': 6,
'<': 7,
'>': 7,
'<=': 7,
'>=': 7,
'==': 8,
'!=': 8,
// '&': 9,
// '^': 10,
// '|': 11,
'&&': 12,
'^^': 13,
'||': 14,
'?:': 15,
'=': 16,
'+=': 16,
'-=': 16,
'*=': 16,
'/=': 16,
',': 17,
})
Insert cell
function getPriority(node) {
if (node[0] === 'call') {
const args = node.slice(2);
if (node[1][0] === 'name') {
const name = node[1][1];
if (/[a-z]/gi.test(name[0])) {
return -1;
} else if (name[0] === '.') {
return operatorPriorities['.'];
} else if (name in operatorPriorities) {
return operatorPriorities[name];
} else {
throw new Error(`Unsupported operator: ${name}`);
}
}
}
return -1;
}
Insert cell
function insertBraces(node) {
if (node[0] === 'call') {
const priority = getPriority(node);
const head = insertBraces(node[1]);
const args = node.slice(2).map(arg => insertBraces(arg));
return ['call', head, ...args.map(arg => {
const argPriority = getPriority(arg);
if (priority !== -1 && argPriority !== -1 && argPriority > priority) {
return ['', arg];
} else {
return arg;
}
})];
} else {
return node;
}
}
Insert cell
Insert cell
function toCode(node, getName) {
if (node[0] === '') {
return '(' + toCode(node[1], getName) + ')';
} else if (node[0] === 'call') {
const fun = toCode(node[1], getName);
const args = node.slice(2).map(arg => toCode(arg, getName));
const defaultRes = fun + '(' + args.join(', ') + ')';
if (node[1][0] === 'name') {
const name = node[1][1];
if (/[a-z]/gi.test(name[0])) {
return defaultRes;
} else if (args.length === 1) {
if (name[0] === '.') {
return args[0] + getName(name);
} else {
return getName(name).replace('@', args[0]);
}
} else if (name === '?:') {
return args[0] + ' ? ' + args[1] + ' : ' + args[2];
} else {
return args.join(' ' + getName(name) + ' ');
}
} else if (node[1][0] == 'call') {
return defaultRes;
} else if (node[1][0] === 'const') {
throw new Error(`Not expecting const at call head`);
} else {
throw new Error(`Unexpected node type: "${node[1][0]}"`)
}
} else if (node[0] === 'name') {
return getName(node[1]);
} else if (node[0] === 'const') {
return node[1];
} else {
throw new Error(`Unexpected node type: "${node[0]}"`)
}
}
Insert cell
function formatExpr(expr, getName=name => name) {
return toCode(insertBraces(expr), getName);
}
Insert cell
formatExpr(parseExpr('float((mod(p.x, size * 1.2) < size) == (mod(p.y, size * 1.2) < size))'))
Insert cell
formatExpr(parseExpr('true ? 1.0 : 0.0'))
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