diff --git a/src/main.ts b/src/main.ts index a0a5bac..ca9e423 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,9 +13,7 @@ import stdlibCode from './stdlib.cg?raw'; import designTokensCode from './design-tokens.cg?raw'; import uiComponentsCode from './ui-components.cg?raw'; -import textInputCode from './textinput-test.cg?raw'; -// import testCode from './test.cg?raw'; -// import counterApp from './counter.cg?raw'; +import osCode from './os.cg?raw'; const canvas = document.createElement('canvas') as HTMLCanvasElement; document.body.appendChild(canvas); @@ -28,7 +26,7 @@ if (clearButton) { const cgCode = stdlibCode + '\n' + designTokensCode + '\n' + uiComponentsCode + '\n' + - textInputCode + '\n' + osCode + '\n' try { const tokens = tokenize(cgCode); @@ -41,7 +39,8 @@ try { const persisted = loadStore(); for (const def of definitions) { - const body = persisted?.[def.name]?.body ?? def.body; + // const body = persisted?.[def.name]?.body ?? def.body; + const body = def.body; const deps = startTracking(def.name); const value = evaluate(body, env, cgCode); diff --git a/src/os.cg b/src/os.cg new file mode 100644 index 0000000..3c2d8c6 --- /dev/null +++ b/src/os.cg @@ -0,0 +1,41 @@ +osState = { + query = "", + debug = "DEBUG", + selectedPaletteIndex = 0 +}; + +init = { + test = "" +}; + +update = state event \ event + | _ \ state; + +view = state viewport \ + Positioned { + x = 30, + y = 30, + child = Column { + gap = 10, + children = [ + textInput { + key = "query", + initialValue = osState.query, + initialFocus = True, + w = 300, + h = 40, + onChange = text \ osState.query := text, + onKeyDown = key \ key + | ArrowUp \ osState.selectedPaletteIndex := max 0 (osState.selectedPaletteIndex + 1) + | ArrowDown \ osState.selectedPaletteIndex := osState.selectedPaletteIndex - 1 + }, + Text { content = osState.debug, x = 8, y = 16 }, + Column { + gap = 10, + children = map (t \ Text { content = t, x = 8, y = 16 }) (storeSearch osState.query) + } + ] + } + }; + +os = { init = init, update = update, view = view } diff --git a/src/parser.ts b/src/parser.ts index 9b9ec8c..caad6d2 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -140,25 +140,7 @@ export class Parser { } private parseExpression(): AST { - // Lambda - if (this.isLambdaStart()) { - return this.parseLambda(); - } - - // Let - if ((this.current().kind === 'ident' || this.current().kind === 'underscore') && this.peek().kind === 'equals') { - return this.parseLet(); - } - - 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) }; - } + let expr = this.parseExpressionNoMatch(); // Match if (this.current().kind === 'pipe') { @@ -180,9 +162,18 @@ export class Parser { return this.parseLet(); } - return 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) }; + } + return expr; + } private parseMatch(expr: AST): AST { const token = this.current(); @@ -241,7 +232,10 @@ export class Parser { let spreadName: string | null = null; while (this.current().kind !== 'close-bracket') { - if (!first) this.expect('comma'); + if (!first) { + this.expect('comma'); + if (this.current().kind === 'close-bracket') break; // trailing commas + } first = false; // Spread @@ -271,7 +265,10 @@ export class Parser { let first = true; while (this.current().kind !== 'close-brace') { - if (!first) this.expect('comma'); + if (!first) { + this.expect('comma'); + if (this.current().kind === 'close-brace') break; // trailing commas + } first = false; const keyToken = this.expect('ident'); @@ -409,6 +406,7 @@ export class Parser { while (this.current().kind !== 'close-brace') { if (!first) { this.expect('comma'); + if (this.current().kind === 'close-brace') break; // trailing commas } first = false; @@ -454,6 +452,7 @@ export class Parser { while (this.current().kind !== 'close-bracket') { if (!first) { this.expect('comma'); + if (this.current().kind === 'close-bracket') break; // trailing commas } first = false; @@ -481,6 +480,7 @@ export class Parser { while (this.current().kind !== 'close-brace') { if (!first) { this.expect('comma'); + if (this.current().kind === 'close-brace') break; // trailing commas } first = false; diff --git a/src/ui-components.cg b/src/ui-components.cg index 052b5f3..a088ff9 100644 --- a/src/ui-components.cg +++ b/src/ui-components.cg @@ -64,46 +64,41 @@ textInput = config \ Stateful { # update : State \ Event \ State update = state event \ event - | ArrowLeft \ ( + | ArrowLeft \ newCursorPos = max 0 (state.cursorPos - 1); newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; - _ = debug "ArrowLeft" []; - { state = newState, emit = [] } - ) + { state = newState, emit = [config.onKeyDown] } - | ArrowRight \ ( + | ArrowRight \ newCursorPos = min (len state.text) (state.cursorPos + 1); newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; - { state = newState, emit = [] } - ) + { state = newState, emit = [config.onKeyDown] } - | Backspace \ ( + | Backspace \ newText = deleteChar state.text state.cursorPos; newCursorPos = max 0 (state.cursorPos - 1); newScroll = calcScrollOffset newText newCursorPos state.scrollOffset 284; newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll }; - { state = newState, emit = [config.onChange newText] } - ) + { state = newState, emit = [config.onChange newText, config.onKeyDown] } | Char c \ newText = insertChar state.text state.cursorPos c; newCursorPos = state.cursorPos + 1; newScroll = calcScrollOffset newText newCursorPos state.scrollOffset 284; newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll }; - { state = newState, emit = [config.onChange newText] } + { state = newState, emit = [config.onChange newText, config.onKeyDown] } - | Clicked coords \ ( + | Clicked coords \ newCursorPos = findCursorPos state.text coords.x state.scrollOffset 8; newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; { state = newState, emit = [] } - ) | Focused \ { state = state.{ focused = True }, emit = [] } | Blurred \ { state = state.{ focused = False }, emit = [] } - | _ \ { state = state, emit = [] }, + | event \ { state = state, emit = [config.onKeyDown event] }, view = state \ textBeforeCursor = slice state.text 0 state.cursorPos; diff --git a/src/ui.ts b/src/ui.ts index 7413784..4b50e15 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -8,19 +8,7 @@ type ClickRegion = { event: Value; }; -type TextInputRegion = { - x: number; - y: number; - width: number; - height: number; - inputConstructor: Value; - submitConstructor: Value; -} - let clickRegions: ClickRegion[] = []; -let textInputRegions: TextInputRegion[] = []; -let focusedInput: Value | null = null; -let focusedInputSubmit: Value | null = null; export function render(ui: UIValue, canvas: HTMLCanvasElement) { const ctx = canvas.getContext('2d'); @@ -32,7 +20,6 @@ export function render(ui: UIValue, canvas: HTMLCanvasElement) { ctx.setTransform(dpr, 0, 0, dpr, 0, 0); clickRegions = []; - textInputRegions = []; renderUI(ui, ctx, 0, 0); } } @@ -209,60 +196,3 @@ export function hitTest(x: number, y: number): { event: Value, relativeX: number } return null; } - -let currentInputValue: string = ''; - -export function hitTestTextInput(x: number, y: number): boolean { - for (const region of textInputRegions) { - if (x >= region.x && x < region.x + region.width && - y >= region.y && y < region.y + region.height) { - focusedInput = region.inputConstructor; - focusedInputSubmit = region.submitConstructor; - return true; - } - } - - focusedInput = null; - focusedInputSubmit = null; - return false; -} - -export function getFocusedInput(): Value | null { - return focusedInput; -} - -export function handleKeyboard(key: string): Value | null { - if (!focusedInput) return null; - - if (key === 'Enter') { - if (!focusedInputSubmit) return null; - return { - kind: 'constructor', - name: (focusedInputSubmit as any).name, - args: [{ kind: 'string', value: currentInputValue }] - }; - } - - if (key === 'Backspace') { - const newValue = currentInputValue.slice(0, -1); - currentInputValue = newValue; - return { - kind: 'constructor', - name: (focusedInput as any).name, - args: [{ kind: 'string', value: newValue }] - }; - } - - // Character - if (key.length === 1) { - const newValue = currentInputValue + key; - currentInputValue = newValue; - return { - kind: 'constructor', - name: (focusedInput as any).name, - args: [{ kind: 'string', value: newValue }] - }; - } - - return null; -}