Public
Edited
Sep 19, 2023
Importers
Insert cell
Insert cell
import {formatExpr} from '239920cfe4c2e12c'
Insert cell
Insert cell
function getGenericDeps(start, getDeps, getDefaultId) {
const accum = new Set();
accum.add(getDefaultId(start));
const res = [];
for (const [depName, depObj] of Object.entries(getDeps(start))) {
const depDeps = getGenericDeps(depObj, getDeps, getDefaultId);
for (const [depDepName, depDepObj] of depDeps) {
if (accum.has(depDepName)) continue;
res.push([depDepName, depDepObj]);
accum.add(depDepName);
}
}
res.push([getDefaultId(start), start]);
return res;
}
Insert cell
function getAllDeps(fun) {
return getGenericDeps(fun, fun => (fun.meta.imports || {}), fun => fun.meta.name);
}
Insert cell
glslPostfix = name => `\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {
fragColor = ${name}();
}`
Insert cell
jsPostfix = name => `\n\nreturn ${name}(fragCoord, iResolution, iTime);`
Insert cell
rotateMatrix = new Fun({
title: '2-dimensional rotation matrix',
args: {
a: types.float,
},
imports: {},
resType: types.mat2,
}, 'mat2(cos(a), -sin(a), sin(a), cos(a))')
Insert cell
rotatedVector = new Fun({
title: 'Vector, rotated by angle',
name: 'rotatedVector',
args: {
vector2: types.vec2,
a: types.float,
},
resType: types.vec2,
imports: {
rotateMatrix
},
}, 'vector2 * rotateMatrix(a)')
Insert cell
compileWithoutDeps(rotatedVector)
Insert cell
function compileWithoutDeps(fun, lang='glsl') {
return compileFun(fun, fun.meta.name, lang);
}
Insert cell
function compileWithDeps(fun, lang='glsl') {
return getAllDeps(fun).map(([depName, depFun]) => {
return compileFun(depFun, depName, lang);
}).join('\n\n') + (lang === 'glsl' ? glslPostfix : jsPostfix)(fun.meta.name);
}
Insert cell
function indent(str) {
return str.split('\n').slice(0, -1).map(line => ' ' + line).join('\n') + '\n';
}
Insert cell
function compileFun(fun, funName, lang) {
fun.meta.ctxTypes = getFunCtxTypes(fun);
if (lang === 'glsl') {
const resType = fun.meta.resType.name;
const args = Object.entries(fun.meta.args).map(([argName, argType]) => {
return argType.name + ' ' + argName;
}).join(', ');
const body = indent(compileSnippet(fun.body, lang, fun, true));
return `${resType} ${funName}(${args}) {\n${body}}`;
} else if (lang === 'js') {
const args = Object.entries(fun.meta.args).map(([argName]) => argName).join(', ');
const body = indent(compileSnippet(fun.body, lang, fun, true));
return `function ${funName}(${args}) {\n${body}}`;
}
}
Insert cell
function compileSnippet(node, lang, fun, withReturn=false) {
if (!Array.isArray(node)) {
throw new Error(`Expecting array, got ${node}`);
} else if (node[0] === 'assignments') {
let res = '';
node.slice(1).forEach(([name, stmt], num, arr) => {
if (num === arr.length - 1 && withReturn) {
res += `return ${compileSnippet(stmt, lang, fun, false)}`;
} else {
if (lang === 'glsl') {
res += `${fun.meta.ctxTypes[name].type.name} ${name} = ${compileSnippet(stmt, lang, fun, false)}`;
} else {
res += `let ${name} = ${compileSnippet(stmt, lang, fun, false)}`;
}
}
});
return res;
} else if (node[0] === 'fun') {
throw new Error('Not expecting "fun" node in compileSnippet');
} else {
return (withReturn ? 'return ' : '') + formatExpr(node) + ';\n';
}
}
Insert cell
function getFunCtxTypes(fun) {
const args = Object.entries(fun.meta.args).map(([argName, argType]) => new Typed(argType));
return getType(
['fun', fun],
getDefaultCtx(),
true,
).value(...args);
}

Insert cell
function getType(stmt, ctx, returnCtx=false) {
if (typeof ctx === 'undefined') {
return getType(stmt, getDefaultCtx());
} else if (!Array.isArray(stmt)) {
throw new Error('Expecting statement to be an array');
} else if (stmt[0] === 'call') {
const head = getType(stmt[1], ctx);
const args = stmt.slice(2).map(arg => getType(arg, ctx));
const argsTypes = args.map(arg => arg.type.name);
const argsSignature = '(' + argsTypes.join(', ') + ')';
if (!head instanceof Typed) throw new Error();
if (!args.every(x => x instanceof Typed)) throw new Error();
const allArgsHasValue = args.every(arg => arg.hasValue());
if (head.type === types.polymorphic) {
for (const entry of head.value) {
if (checkSignature(entry.args, args)) {
if (allArgsHasValue) {
return new Typed(entry.resType, entry.js(...args.map(arg => arg.value)));
} else {
return new Typed(entry.resType);
}
}
}
const funName = head.value[0].name;
throw new Error(`No signature matches ${argsSignature} for function ${funName}`);
} else if (head.type === types.function) {
if (allArgsHasValue) {
return head.value(...args);
} else {
return new Typed(head.value.obj.meta.resType);
}
} else {
throw new Error();
}
} else if (stmt[0] === 'fun') {
const resFun = (...args) => {
const ctx = getDefaultCtx();
Object.entries(stmt[1].meta.imports).forEach(([importAs, fun]) => {
ctx[importAs] = getType(['fun', fun]);
});
if (!checkSignature(stmt[1].meta.args, args)) {
const argsTypes = args.map(arg => arg.type.name);
const argsSignature = '(' + argsTypes.join(', ') + ')';
throw new Error(`Function ${stmt[1].meta.name} signature mismatch: got ${argsSignature}`);
}
Object.entries(stmt[1].meta.args).forEach(([argName, argType], argNum) => {
ctx[argName] = args[argNum];
});
const res = getType(stmt[1].body, ctx);
if (res.type === types.assignments) {
if (returnCtx) {
return res.value(ctx);
} else {
return Object.values(res.value(ctx)).at(-1);
}
} else {
return res;
}
};
resFun.obj = stmt[1];
return new Typed(types.function, resFun);
} else if (stmt[0] === 'name') {
return ctx[stmt[1]];
} else if (stmt[0] === 'const') {
const s = stmt[1];
if (/^[0-9]*\.[0-9]*$/g.test(s)) {
return new Typed(types.float, Number(s));
} else if (/^[0-9]+$/g.test(s)) {
return new Typed(types.int, Number(s));
} else if (['true', 'false'].includes(s)) {
return new Typed(types.bool, s === 'true' ? true : false);
} else {
throw new Error(`Unexpected constant: "${s}"`);
}
} else if (stmt[0] === 'assignments') {
return new Typed(types.assignments, ctx => {
const res = {...ctx};
stmt.slice(1).forEach(([name, expr]) => {
const curRes = getType(expr, res);
if (curRes.type === types.assignments) {
res = curRes(res);
} else {
res[name] = curRes;
}
});
return res;
});
} else {
throw new Error(`Unknown statement: "${stmt[0]}"`);
}
}
Insert cell
mainImg
Insert cell
compileWithDeps(mainImg, 'js')
Insert cell
shader({height: 100, iTime: true, visibility})`${compileWithDeps(mainImg, 'glsl')}`
Insert cell
import {shader} from "@mbostock/shader"
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