records in the store

master
Dustin Swan 7 hours ago
parent 70569dfe48
commit 6652c0f970
Signed by: dustinswan
GPG Key ID: 30D46587E2100467

@ -1,4 +1,4 @@
import type { Value, NativeFunction } from './types' import type { Value } from './types'
const measureCanvas = document.createElement('canvas'); const measureCanvas = document.createElement('canvas');
const measureCtx = measureCanvas.getContext('2d')!; const measureCtx = measureCanvas.getContext('2d')!;
@ -468,7 +468,7 @@ export const builtins: { [name: string]: Value } = {
name: 'debug', name: 'debug',
arity: 2, arity: 2,
fn: (label, value) => { fn: (label, value) => {
const l = expectString(label, 'debug'); expectString(label, 'debug');
return value; return value;
} }
} }

@ -202,17 +202,49 @@ export function evaluate(ast: AST, env: Env, source: string): Value {
} }
case 'rebind': { case 'rebind': {
if (ast.target.kind !== 'variable')
throw new Error('Rebind target must be a variable');
const name = ast.target.name;
const value = evaluate(ast.value, env, source); const value = evaluate(ast.value, env, source);
return { if (ast.target.kind === 'variable') {
kind: 'constructor', const name = ast.target.name;
name: 'Rebind', return {
args: [{ kind: 'string', value: name }, value] kind: 'constructor',
}; name: 'Rebind',
args: [{ kind: 'string', value: name }, value]
};
}
if (ast.target.kind === 'record-access') {
let current: AST = ast.target;
const path: string[] = [];
while (current.kind === 'record-access') {
path.unshift(current.field);
current = current.record;
}
if (current.kind !== 'variable')
throw RuntimeError('Rebind target must be a variable or field access', ast.line, ast.column, source);
const rootName = current.name;
const rootValue = env.get(rootName);
if (!rootValue)
throw RuntimeError(`Unknown variable: ${rootName}`, ast.line, ast.column, source);
// const newRoot = updatePath(rootValue, path, value, ast.line, ast.column, source);
return {
kind: 'constructor',
name: 'Rebind',
args: [
{ kind: 'string', value: rootName },
{ kind: 'list', elements: path.map(p => ({ kind: 'string', value: p })) },
value
]
};
}
throw RuntimeError('Rebind target must be a variable or field access', ast.line, ast.column, source);
} }
default: default:
@ -297,5 +329,4 @@ function matchPattern(value: Value, pattern: Pattern): Bindings | null {
return bindings; return bindings;
} }
} }
} }

@ -187,7 +187,7 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env:
}; };
try { try {
const callEnv = new Map(app.view.env); const callEnv = new Map(env);
callEnv.set(app.view.params[0], state); callEnv.set(app.view.params[0], state);
callEnv.set(app.view.params[1], viewport); callEnv.set(app.view.params[1], viewport);
const uiValue = evaluate(app.view.body, callEnv, source); const uiValue = evaluate(app.view.body, callEnv, source);
@ -223,13 +223,25 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env:
} }
if (event.kind === 'constructor' && event.name === 'Rebind') { if (event.kind === 'constructor' && event.name === 'Rebind') {
if (event.args.length === 2 && event.args[0].kind === 'string') { if (event.args[0].kind !== 'string') return;
const name = event.args[0].value; const name = event.args[0].value;
const value = event.args[1];
env.set(name, value); if (event.args.length === 2) {
rerender(); // Rebind "name" value
return; env.set(name, event.args[1]);
} else if (event.args.length === 3 && event.args[1].kind === 'list') {
// Rebind "name" ["path"]
const pathList = (event.args[1] as { elements: Value[] });
const path = pathList.elements.map((e: Value) => e.kind === 'string' ? e.value : '');
const currentValue = env.get(name);
if (currentValue) {
const newValue = updatePath(currentValue, path, event.args[2]);
env.set(name, newValue);
}
} }
rerender();
return;
} }
if (app.update.kind !== 'closure') if (app.update.kind !== 'closure')
@ -321,3 +333,19 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env:
rerender(); rerender();
} }
function updatePath(obj: Value, path: string[], value: Value): Value {
if (path.length === 0) return value;
if (obj.kind !== 'record')
throw new Error('Cannot access field on non-record');
const [field, ...rest] = path;
return {
kind: 'record',
fields: {
...obj.fields,
[field]: updatePath(obj.fields[field], rest, value)
}
};
}

@ -1,7 +1,9 @@
init = {}; init = {};
email = ""; testApp = {
password = ""; email = "",
password = ""
};
update = state event \ event update = state event \ event
| _ \ state; | _ \ state;
@ -19,7 +21,7 @@ view = state viewport \
initialFocus = True, initialFocus = True,
w = 300, w = 300,
h = 40, h = 40,
onChange = text \ email := text onChange = text \ testApp.email := text
}, },
textInput { textInput {
key = "password", key = "password",
@ -27,10 +29,10 @@ view = state viewport \
initialFocus = False, initialFocus = False,
w = 300, w = 300,
h = 40, h = 40,
onChange = text \ password := text onChange = text \ testApp.password := text
}, },
Text { content = "Username: " & email, x = 8, y = 16 }, Text { content = "Username: " & testApp.email, x = 8, y = 16 },
Text { content = "Password: " & password, x = 8, y = 16 } Text { content = "Password: " & testApp.password, x = 8, y = 16 }
] ]
} }
}; };

Loading…
Cancel
Save