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 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…
Add table
Add a link
Reference in a new issue