No more Refs, no more store, every top level def goes in the store, to update store values use :=
This commit is contained in:
parent
31ef279f16
commit
70569dfe48
9 changed files with 58 additions and 74 deletions
11
src/ast.ts
11
src/ast.ts
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
11
src/lexer.ts
11
src/lexer.ts
|
|
@ -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…
Add table
Add a link
Reference in a new issue