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
|
// Literals and Variables
|
||||||
|
|
||||||
|
|
||||||
export type Literal = {
|
export type Literal = {
|
||||||
kind: 'literal'
|
kind: 'literal'
|
||||||
value: Value
|
value: Value
|
||||||
|
|
@ -160,6 +159,15 @@ export type Import = {
|
||||||
start: number
|
start: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Rebind = {
|
||||||
|
kind: 'rebind'
|
||||||
|
target: AST
|
||||||
|
value: AST
|
||||||
|
line: number
|
||||||
|
column: number
|
||||||
|
start: number
|
||||||
|
}
|
||||||
|
|
||||||
export type AST =
|
export type AST =
|
||||||
| Literal
|
| Literal
|
||||||
| Variable
|
| Variable
|
||||||
|
|
@ -176,6 +184,7 @@ export type AST =
|
||||||
| Definition
|
| Definition
|
||||||
| TypeDef
|
| TypeDef
|
||||||
| Import
|
| Import
|
||||||
|
| Rebind
|
||||||
|
|
||||||
export function prettyPrint(ast: AST, indent = 0): string {
|
export function prettyPrint(ast: AST, indent = 0): string {
|
||||||
const i = ' '.repeat(indent);
|
const i = ' '.repeat(indent);
|
||||||
|
|
|
||||||
|
|
@ -471,44 +471,5 @@ export const builtins: { [name: string]: Value } = {
|
||||||
const l = expectString(label, 'debug');
|
const l = expectString(label, 'debug');
|
||||||
return value;
|
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);
|
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:
|
default:
|
||||||
throw RuntimeError('Syntax Error', ast.line, ast.column, source);
|
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
|
// Symbols
|
||||||
| { kind: 'colon' }
|
| { kind: 'colon' }
|
||||||
|
| { kind: 'colon-equals' }
|
||||||
| { kind: 'semicolon' }
|
| { kind: 'semicolon' }
|
||||||
| { kind: 'backslash' }
|
| { kind: 'backslash' }
|
||||||
| { kind: 'pipe' }
|
| { kind: 'pipe' }
|
||||||
|
|
@ -214,7 +215,15 @@ export function tokenize(source: string): Token[] {
|
||||||
}
|
}
|
||||||
break;
|
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: 'semicolon', line: startLine, column: startColumn, start }); break;
|
||||||
case '\\': tokens.push({ kind: 'backslash', 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;
|
case '~': tokens.push({ kind: 'tilde', line: startLine, column: startColumn, start }); break;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ try {
|
||||||
const update = appRecord.fields.update;
|
const update = appRecord.fields.update;
|
||||||
const view = appRecord.fields.view;
|
const view = appRecord.fields.view;
|
||||||
|
|
||||||
runApp({ init, update, view }, canvas, cgCode);
|
runApp({ init, update, view }, canvas, cgCode, env);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.log('CAUGHT ERROR:', error);
|
console.log('CAUGHT ERROR:', error);
|
||||||
console.log('Is CGError??', error instanceof CGError);
|
console.log('Is CGError??', error instanceof CGError);
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,14 @@ export class Parser {
|
||||||
|
|
||||||
let expr = this.parseInfix();
|
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
|
// Match
|
||||||
if (this.current().kind === 'pipe') {
|
if (this.current().kind === 'pipe') {
|
||||||
return this.parseMatch(expr);
|
return this.parseMatch(expr);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { valueToUI } from './valueToUI';
|
||||||
import { render, hitTest } from './ui';
|
import { render, hitTest } from './ui';
|
||||||
import { evaluate } from './interpreter';
|
import { evaluate } from './interpreter';
|
||||||
import { CGError } from './error';
|
import { CGError } from './error';
|
||||||
|
import type { Env } from './env';
|
||||||
|
|
||||||
export type App = {
|
export type App = {
|
||||||
init: Value;
|
init: Value;
|
||||||
|
|
@ -10,7 +11,7 @@ export type App = {
|
||||||
view: Value; // State / UI
|
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;
|
let state = app.init;
|
||||||
|
|
||||||
type ComponentInstance = {
|
type ComponentInstance = {
|
||||||
|
|
@ -221,23 +222,11 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.kind === 'constructor' && event.name === 'Update') {
|
if (event.kind === 'constructor' && event.name === 'Rebind') {
|
||||||
if (event.args.length === 2) {
|
if (event.args.length === 2 && event.args[0].kind === 'string') {
|
||||||
const ref = event.args[0];
|
const name = event.args[0].value;
|
||||||
const transformFn = event.args[1];
|
const value = event.args[1];
|
||||||
|
env.set(name, value);
|
||||||
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;
|
|
||||||
|
|
||||||
rerender();
|
rerender();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -275,7 +264,7 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) {
|
||||||
if (hitResult) {
|
if (hitResult) {
|
||||||
const { event, relativeX, relativeY } = 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);
|
handleEvent(event);
|
||||||
} else if (event.kind === 'constructor' && event.name === 'FocusAndClick') {
|
} else if (event.kind === 'constructor' && event.name === 'FocusAndClick') {
|
||||||
const eventWithCoords: Value = {
|
const eventWithCoords: Value = {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
init = {};
|
init = {};
|
||||||
|
|
||||||
email = store.ref "";
|
email = "";
|
||||||
password = store.ref "";
|
password = "";
|
||||||
|
|
||||||
update = state event \ event
|
update = state event \ event
|
||||||
| _ \ state;
|
| _ \ state;
|
||||||
|
|
||||||
view = state viewport \
|
view = state viewport \
|
||||||
emailText = store.get email;
|
|
||||||
|
|
||||||
Positioned {
|
Positioned {
|
||||||
x = 30,
|
x = 30,
|
||||||
y = 30,
|
y = 30,
|
||||||
|
|
@ -21,7 +19,7 @@ view = state viewport \
|
||||||
initialFocus = True,
|
initialFocus = True,
|
||||||
w = 300,
|
w = 300,
|
||||||
h = 40,
|
h = 40,
|
||||||
onChange = text \ store.set email (a \ text)
|
onChange = text \ email := text
|
||||||
},
|
},
|
||||||
textInput {
|
textInput {
|
||||||
key = "password",
|
key = "password",
|
||||||
|
|
@ -29,10 +27,10 @@ view = state viewport \
|
||||||
initialFocus = False,
|
initialFocus = False,
|
||||||
w = 300,
|
w = 300,
|
||||||
h = 40,
|
h = 40,
|
||||||
onChange = text \ store.set password (a \ text)
|
onChange = text \ password := text
|
||||||
},
|
},
|
||||||
Text { content = "Username: " & emailText, x = 8, y = 16 },
|
Text { content = "Username: " & email, x = 8, y = 16 },
|
||||||
Text { content = "Password: " & store.get password, x = 8, y = 16 }
|
Text { content = "Password: " & password, x = 8, y = 16 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,6 @@ export type NativeFunction = {
|
||||||
fn: (...args: Value[]) => Value
|
fn: (...args: Value[]) => Value
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RefValue = {
|
|
||||||
kind: 'ref'
|
|
||||||
value: Value
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UIValue =
|
export type UIValue =
|
||||||
| { kind: 'rect', w: number, h: number, color: string, radius?: number }
|
| { kind: 'rect', w: number, h: number, color: string, radius?: number }
|
||||||
|
|
@ -64,4 +60,4 @@ export type UIValue =
|
||||||
| { kind: 'stack', children: UIValue[] }
|
| { kind: 'stack', children: UIValue[] }
|
||||||
| { kind: 'stateful', key: string, focusable: boolean, init: Value, update: Value, view: Value }
|
| { 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