No more Refs, no more store, every top level def goes in the store, to update store values use :=

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

@ -2,7 +2,6 @@ import type { Value } from './types';
// Literals and Variables
export type Literal = {
kind: 'literal'
value: Value
@ -160,6 +159,15 @@ export type Import = {
start: number
}
export type Rebind = {
kind: 'rebind'
target: AST
value: AST
line: number
column: number
start: number
}
export type AST =
| Literal
| Variable
@ -176,6 +184,7 @@ export type AST =
| Definition
| TypeDef
| Import
| Rebind
export function prettyPrint(ast: AST, indent = 0): string {
const i = ' '.repeat(indent);

@ -471,44 +471,5 @@ export const builtins: { [name: string]: Value } = {
const l = expectString(label, 'debug');
return value;
}
},
'store': {
kind: 'record',
fields: {
'ref': {
kind: 'native',
name: 'Store.ref',
arity: 1,
fn: (initialValue) => {
return { kind: 'ref', value: initialValue };
}
},
'get': {
kind: 'native',
name: 'Store.get',
arity: 1,
fn: (ref) => {
if (ref.kind !== 'ref')
throw new Error('get expects a Ref');
return ref.value;
}
},
'set': {
kind: 'native',
name: 'Store.set',
arity: 2,
fn: (ref, transformFn) => {
return {
kind: 'constructor',
name: 'Update',
args: [ref, transformFn]
};
}
}
}
}
}

@ -201,6 +201,20 @@ export function evaluate(ast: AST, env: Env, source: string): Value {
throw RuntimeError('Non-exhaustive pattern match', ast.line, ast.column, source);
}
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]
};
}
default:
throw RuntimeError('Syntax Error', ast.line, ast.column, source);
}

@ -25,6 +25,7 @@ export type Token = (
// Symbols
| { kind: 'colon' }
| { kind: 'colon-equals' }
| { kind: 'semicolon' }
| { kind: 'backslash' }
| { kind: 'pipe' }
@ -214,7 +215,15 @@ export function tokenize(source: string): Token[] {
}
break;
}
case ':': tokens.push({ kind: 'colon', line: startLine, column: startColumn, start }); break;
case ':': {
if (source[i + 1] === '=') {
tokens.push({ kind: 'colon-equals', line: startLine, column: startColumn, start });
advance();
} else {
tokens.push({ kind: 'colon', line: startLine, column: startColumn, start });
}
break;
}
case ';': tokens.push({ kind: 'semicolon', line: startLine, column: startColumn, start }); break;
case '\\': tokens.push({ kind: 'backslash', line: startLine, column: startColumn, start }); break;
case '~': tokens.push({ kind: 'tilde', line: startLine, column: startColumn, start }); break;

@ -45,7 +45,7 @@ try {
const update = appRecord.fields.update;
const view = appRecord.fields.view;
runApp({ init, update, view }, canvas, cgCode);
runApp({ init, update, view }, canvas, cgCode, env);
} catch(error) {
console.log('CAUGHT ERROR:', error);
console.log('Is CGError??', error instanceof CGError);

@ -152,6 +152,14 @@ export class Parser {
let expr = this.parseInfix();
// Rebind
if (this.current().kind == 'colon-equals') {
const token = this.current();
this.advance();
const value = this.parseExpression();
return { kind: 'rebind', target: expr, value, ...this.getPos(token) };
}
// Match
if (this.current().kind === 'pipe') {
return this.parseMatch(expr);

@ -3,6 +3,7 @@ import { valueToUI } from './valueToUI';
import { render, hitTest } from './ui';
import { evaluate } from './interpreter';
import { CGError } from './error';
import type { Env } from './env';
export type App = {
init: Value;
@ -10,7 +11,7 @@ export type App = {
view: Value; // State / UI
}
export function runApp(app: App, canvas: HTMLCanvasElement, source: string) {
export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env: Env) {
let state = app.init;
type ComponentInstance = {
@ -221,23 +222,11 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) {
}
}
if (event.kind === 'constructor' && event.name === 'Update') {
if (event.args.length === 2) {
const ref = event.args[0];
const transformFn = event.args[1];
if (ref.kind !== 'ref')
throw new Error('Update event expects a Ref')
if (transformFn.kind !== 'closure')
throw new Error('Update event expects a Ref')
const callEnv = new Map(transformFn.env);
callEnv.set(transformFn.params[0], ref.value);
const newValue = evaluate(transformFn.body, callEnv, source);
ref.value = newValue;
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;
}
@ -275,7 +264,7 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) {
if (hitResult) {
const { event, relativeX, relativeY } = hitResult;
if (event.kind === 'constructor' && (event.name === 'Focus' || event.name === 'Update')) {
if (event.kind === 'constructor' && (event.name === 'Focus' || event.name === 'Rebind')) {
handleEvent(event);
} else if (event.kind === 'constructor' && event.name === 'FocusAndClick') {
const eventWithCoords: Value = {

@ -1,14 +1,12 @@
init = {};
email = store.ref "";
password = store.ref "";
email = "";
password = "";
update = state event \ event
| _ \ state;
view = state viewport \
emailText = store.get email;
Positioned {
x = 30,
y = 30,
@ -21,7 +19,7 @@ view = state viewport \
initialFocus = True,
w = 300,
h = 40,
onChange = text \ store.set email (a \ text)
onChange = text \ email := text
},
textInput {
key = "password",
@ -29,10 +27,10 @@ view = state viewport \
initialFocus = False,
w = 300,
h = 40,
onChange = text \ store.set password (a \ text)
onChange = text \ password := text
},
Text { content = "Username: " & emailText, x = 8, y = 16 },
Text { content = "Password: " & store.get password, x = 8, y = 16 }
Text { content = "Username: " & email, x = 8, y = 16 },
Text { content = "Password: " & password, x = 8, y = 16 }
]
}
};

@ -46,10 +46,6 @@ export type NativeFunction = {
fn: (...args: Value[]) => Value
}
export type RefValue = {
kind: 'ref'
value: Value
}
export type UIValue =
| { kind: 'rect', w: number, h: number, color: string, radius?: number }
@ -64,4 +60,4 @@ export type UIValue =
| { kind: 'stack', children: UIValue[] }
| { kind: 'stateful', key: string, focusable: boolean, init: Value, update: Value, view: Value }
export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue | NativeFunction | RefValue;
export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue | NativeFunction;

Loading…
Cancel
Save