Public
Edited
Sep 20, 2023
Importers
Insert cell
Insert cell
Insert cell
Insert cell
class Fun {
constructor(meta, body) {
this.meta = meta;
this.body = parseCode(body);
}
}
Insert cell
class Typed {
constructor(type, value) {
this.type = type;
this.value = value;
}
hasValue() {
return typeof this.value !== 'undefined';
}
check() {
if (!this.type.check(this.value)) {
throw new Error(`Invalid type: ${this.type.name}`);
}
}
}
Insert cell
function parseCode(code) {
if (code.constructor.name === 'Fun') {
return ['fun', code];
} else if (typeof code === 'string') {
if (['break', 'continue', 'return'].includes(code)) {
throw new Error('Break, continue and return are not supported');
// return [code];
}
return parseExpr(code);
} else if (Array.isArray(code)) {
if (code.length === 0) throw new Error('Empty array not expected');
if (['if', 'forInRange'].includes(code[0])) {
return ['call', ['name', code[0]], ...code.slice(1).map(parseCode)];
} else if (['break', 'continue', 'return'].includes(code[0])) {
throw new Error('Break, continue and return are not supported');
// return [code[0]];
} else {
throw new Error(`Unknown statement: "${code[0]}"`);
}
} else if (typeof code === 'object' && code && (code.constructor === ({}).constructor)) {
const stmts = Object.entries(code).map(([label, stmt]) => [label, parseCode(stmt)]);
return ['assignments', ...stmts];
} else {
throw new Error(`Unknown statement: ${code}`);
}
}
Insert cell
function getDefaultCtx() {
const res = Object.fromEntries(Object.entries(lib).map(([name, entry]) => [
name,
new Typed(types.polymorphic, entry),
]));
res['iTime'] = new Typed(types.float);
res['gl_FragCoord'] = new Typed(types.vec3);
res['iResolution'] = new Typed(types.vec3);
return res;
}
Insert cell
function checkSignature(funArgs, args) {
const argsTypes = args.map(arg => arg.type.name);
const signature = Object.values(funArgs).map(t => t.name);
if (argsTypes.length !== signature.length) return false;
for (let i=0; i<argsTypes.length; i++) {
if (argsTypes[i] !== signature[i]) {
return false;
}
}
return true;
}
Insert cell
function interpret(stmt, ctx) {
if (typeof ctx === 'undefined') {
return interpret(stmt, getDefaultCtx());
} else if (!Array.isArray(stmt)) {
throw new Error('Expecting statement to be an array');
} else if (stmt[0] === 'call') {
const head = interpret(stmt[1], ctx);
const args = stmt.slice(2).map(arg => interpret(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();
if (head.type === types.polymorphic) {
for (const entry of head.value) {
if (checkSignature(entry.args, args)) {
return new Typed(entry.resType, entry.js(...args.map(arg => arg.value)));
}
}
const funName = head.value[0].name;
throw new Error(`No signature matches ${argsSignature} for function ${funName}`);
} else if (head.type === types.function) {
return head.value(...args);
} else {
throw new Error();
}
} else if (stmt[0] === 'fun') {
return new Typed(types.function, (...args) => {
const ctx = getDefaultCtx();
Object.entries(stmt[1].meta.imports).forEach(([importAs, fun]) => {
ctx[importAs] = interpret(['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 = interpret(stmt[1].body, ctx);
if (res.type === types.assignments) {
return Object.values(res.value(ctx)).at(-1);
} else {
return res;
}
});
} 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 {
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 = interpret(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
rotate2d = new Fun({
name: 'rotate2d',
args: {a: types.float},
resType: types.mat2,
imports: {},
}, 'mat2(cos(a), -sin(a), sin(a), cos(a))')
Insert cell
mainImg = new Fun({
name: 'mainImg',
args: {
// fragCoord: types.vec2,
// iResolution: types.vec3,
// iTime: types.float,
},
resType: types.vec4,
imports: {
rotate2d,
},
}, {
size: '100.0',
p: '(gl_FragCoord.xy - iResolution.xy / 2.0) * rotate2d(iTime / 10.0)',
k: 'float((mod(p.x, size * 1.2) < size) == (mod(p.y, size * 1.2) < size))',
fragColor: 'vec4(vec3(k), 1.0)',
})
Insert cell
vec2 = (x, y) => ['call', ['name', 'vec2'], ['const', x], ['const', y]]
Insert cell
getColor = (x, y) => interpret(['call', ['fun', mainImg], vec2(x.toString(), y.toString()), vec2('640.0', '100.0'), ['const', '5.0']])
Insert cell
{
const ctx = DOM.context2d(640, 100);
for (let x=0; x<640; x+=2) {
for (let y=0; y<100; y+=2) {
break;
const [r1, g1, b1, a1] = getColor(x + Math.PI, y + Math.PI).value;
const [r, g, b, a] = [r1 * 255, g1 * 255, b1 * 255, a1];
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
ctx.fillRect(x, y, 1, 1);
yield ctx.canvas;
}
}
return ctx.canvas;
}
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