records in the store
This commit is contained in:
parent
70569dfe48
commit
6652c0f970
4 changed files with 86 additions and 25 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import type { Value, NativeFunction } from './types'
|
||||
import type { Value } from './types'
|
||||
|
||||
const measureCanvas = document.createElement('canvas');
|
||||
const measureCtx = measureCanvas.getContext('2d')!;
|
||||
|
|
@ -468,7 +468,7 @@ export const builtins: { [name: string]: Value } = {
|
|||
name: 'debug',
|
||||
arity: 2,
|
||||
fn: (label, value) => {
|
||||
const l = expectString(label, 'debug');
|
||||
expectString(label, 'debug');
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,17 +202,49 @@ export function evaluate(ast: AST, env: Env, source: string): Value {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: 'Rebind',
|
||||
args: [{ kind: 'string', value: name }, value]
|
||||
};
|
||||
if (ast.target.kind === 'variable') {
|
||||
const name = ast.target.name;
|
||||
return {
|
||||
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:
|
||||
|
|
@ -297,5 +329,4 @@ function matchPattern(value: Value, pattern: Pattern): Bindings | null {
|
|||
return bindings;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env:
|
|||
};
|
||||
|
||||
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[1], viewport);
|
||||
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.args.length === 2 && event.args[0].kind === 'string') {
|
||||
const name = event.args[0].value;
|
||||
const value = event.args[1];
|
||||
env.set(name, value);
|
||||
rerender();
|
||||
return;
|
||||
if (event.args[0].kind !== 'string') return;
|
||||
const name = event.args[0].value;
|
||||
|
||||
if (event.args.length === 2) {
|
||||
// Rebind "name" value
|
||||
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')
|
||||
|
|
@ -321,3 +333,19 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env:
|
|||
|
||||
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 = {};
|
||||
|
||||
email = "";
|
||||
password = "";
|
||||
testApp = {
|
||||
email = "",
|
||||
password = ""
|
||||
};
|
||||
|
||||
update = state event \ event
|
||||
| _ \ state;
|
||||
|
|
@ -19,7 +21,7 @@ view = state viewport \
|
|||
initialFocus = True,
|
||||
w = 300,
|
||||
h = 40,
|
||||
onChange = text \ email := text
|
||||
onChange = text \ testApp.email := text
|
||||
},
|
||||
textInput {
|
||||
key = "password",
|
||||
|
|
@ -27,10 +29,10 @@ view = state viewport \
|
|||
initialFocus = False,
|
||||
w = 300,
|
||||
h = 40,
|
||||
onChange = text \ password := text
|
||||
onChange = text \ testApp.password := text
|
||||
},
|
||||
Text { content = "Username: " & email, x = 8, y = 16 },
|
||||
Text { content = "Password: " & password, x = 8, y = 16 }
|
||||
Text { content = "Username: " & testApp.email, x = 8, y = 16 },
|
||||
Text { content = "Password: " & testApp.password, x = 8, y = 16 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue