diff --git a/src/main.ts b/src/main.ts index 1904586..b6f2a16 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,47 +1,30 @@ -import { evaluate } from './interpreter' -import type { Env } from './env' -import { tokenize } from './lexer' -import { Parser } from './parser' -import { prettyPrint } from './ast'; +import type { UIValue } from './types'; +import { render } from './ui'; -function e(str: string) { - console.log(str); - const tokens = tokenize(str); - console.log(tokens); - const p = new Parser(tokens); - const ast = p.parse(); - console.log(ast); - console.log(prettyPrint(ast)); - const env: Env = new Map(); - console.log(evaluate(ast, env)); -} +const canvas = document.createElement('canvas'); +canvas.width = 800; +canvas.height = 600; +document.body.appendChild(canvas); -e('add1 = (x \\ x + 1); add1 3'); -e('sum = x y \\ x + y; sum 5 3'); -e('[1, 2, 3]'); -e('c = 5; { a = 3, b = c }'); -e('rec = { a = 3, b = 5 }; rec.a'); -e('rec = { a = 3, b = 5 }; rec{ a = 10 }'); -e('add1 = (x \\ x + 1); 3 > add1'); -e('[1, 2] & [3, 4]'); -e('"abc" & "def"'); -// e('n | 0 \\ 1 | _ \\ 99'); -// e('m | Some x \\ 1 | None \\ 0'); -// e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head'); -e('n = 5; n | 5 \\ "five" | _ \\ "other"'); -e('list = [1, 2, 3]; list | [x, y, z] \\ x + y + z'); -e('point = {x = 5, y = 10}; point | {x = px, y = py} \\ px + py'); +const ui: UIValue = { + kind: 'column', + gap: 10, + children: [ + { kind: 'text', content: "Hello CG World", x: 0, y: 20 }, + { kind: 'rect', w: 200, h: 50, color: 'blue' }, + { kind: 'text', content: "YESS", x: 0, y: 20 }, + { kind: 'row', gap: 13, children: + [ + { kind: 'text', content: "In a row", x: 0, y: 10 }, + { kind: 'clickable', event: "test", child: { + kind: 'padding', amount: 10, child: { + kind: 'rect', w: 80, h: 30, color: '#4a90e2' + } + } + } + ] + } + ] +}; -e(`factorial = n \\ n - | 0 \\ 1 - | n \\ n * factorial (n - 1); - - factorial 5`) - -e(`factorial = n \\ n - | 0 \\ 1 - | n \\ n * factorial (n - 1); - - factorial 5`) - -e('some5 = Some 5; some5'); +render(ui, canvas); diff --git a/src/tests.ts b/src/tests.ts new file mode 100644 index 0000000..769a287 --- /dev/null +++ b/src/tests.ts @@ -0,0 +1,52 @@ +import { evaluate } from './interpreter' +import type { Env } from './env' +import { tokenize } from './lexer' +import { Parser } from './parser' +import { prettyPrint } from './ast'; + +function e(str: string) { + console.log(str); + const tokens = tokenize(str); + console.log(tokens); + const p = new Parser(tokens); + const ast = p.parse(); + console.log(ast); + console.log(prettyPrint(ast)); + const env: Env = new Map(); + console.log(evaluate(ast, env)); +} + +e('add1 = (x \\ x + 1); add1 3'); +e('sum = x y \\ x + y; sum 5 3'); +e('[1, 2, 3]'); +e('c = 5; { a = 3, b = c }'); +e('rec = { a = 3, b = 5 }; rec.a'); +e('rec = { a = 3, b = 5 }; rec{ a = 10 }'); +e('add1 = (x \\ x + 1); 3 > add1'); +e('[1, 2] & [3, 4]'); +e('"abc" & "def"'); +e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head'); +e('n = 5; n | 5 \\ "five" | _ \\ "other"'); +e('list = [1, 2, 3]; list | [x, y, z] \\ x + y + z'); +e('point = {x = 5, y = 10}; point | {x = px, y = py} \\ px + py'); +e('some5 = Some 5; some5'); + +e(`factorial = n \\ n + | 0 \\ 1 + | n \\ n * factorial (n - 1); + + factorial 5`) + +e(` +factorial = n \\ n + | 0 \\ 1 + | n \\ n * factorial (n - 1); + +fib = n \\ n + | 0 \\ 0 + | 1 \\ 1 + | n \\ fib (n - 1) + fib (n - 2); + +[ fib 10, factorial 5 ]; +`); + diff --git a/src/types.ts b/src/types.ts index e22fabb..9d94181 100644 --- a/src/types.ts +++ b/src/types.ts @@ -39,4 +39,12 @@ export type ConstructorValue = { args: Value[] } -export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue; +export type UIValue = + | { kind: 'rect', w: number, h: number, color: string } + | { kind: 'text', content: string, x: number, y: number } + | { kind: 'row', children: UIValue[], gap: number } + | { kind: 'column', children: UIValue[], gap: number } + | { kind: 'clickable', child: UIValue, event: string } + | { kind: 'padding', child: UIValue, amount: number } + +export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue | UIValue; diff --git a/src/ui.ts b/src/ui.ts new file mode 100644 index 0000000..2d9a470 --- /dev/null +++ b/src/ui.ts @@ -0,0 +1,90 @@ +import type { UIValue } from './types'; + +export function render(ui: UIValue, canvas: HTMLCanvasElement) { + const ctx = canvas.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + renderUI(ui, ctx, 0, 0); + } +} + +function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: number) { + switch (ui.kind) { + case 'rect': + ctx.fillStyle = ui.color; + ctx.fillRect(x, y, ui.w, ui.h); + break; + + case 'text': + ctx.fillStyle = 'black'; + ctx.font = '16px monospace'; + ctx.fillText(ui.content, x + ui.x, y + ui.y); + break; + + case 'row': { + let offsetX = 0; + for (const child of ui.children) { + renderUI(child, ctx, x + offsetX, y); + offsetX += measure(child).width + ui.gap; + } + break; + } + + case 'column': { + let offsetY = 0; + for (const child of ui.children) { + renderUI(child, ctx, x, y + offsetY); + offsetY += measure(child).height + ui.gap; + } + break; + } + + case 'clickable': { + renderUI(ui.child, ctx, x, y); + break; + } + case 'padding': + renderUI(ui.child, ctx, x + ui.amount, y + ui.amount); + break; + } +} + +function measure(ui: UIValue): { width: number, height: number } { + switch (ui.kind) { + case 'rect': return { width: ui.w, height: ui.h }; + case 'text': return { width: ui.content.length * 10, height: 20 }; // TODO + case 'row': { + let totalWidth = 0; + let maxHeight = 0; + for (const child of ui.children) { + const size = measure(child); + totalWidth += size.width; + maxHeight = Math.max(maxHeight, size.height); + } + totalWidth += ui.gap * (ui.children.length - 1); + return { width: totalWidth, height: maxHeight }; + } + case 'column': { + let totalHeight = 0; + let maxWidth = 0; + for (const child of ui.children) { + const size = measure(child); + totalHeight += size.height; + maxWidth = Math.max(maxWidth, size.width); + } + totalHeight += ui.gap * (ui.children.length - 1); + return { width: maxWidth, height: totalHeight }; + } + case 'clickable': + return measure(ui.child); + case 'padding': { + const childSize = measure(ui.child); + return { + width: childSize.width + ui.amount * 2, + height: childSize.height + ui.amount * 2, + } + } + } + + return { width: 0, height: 0 }; +}