From: Brendan Hansen Date: Sun, 13 Jun 2021 19:59:12 +0000 (-0500) Subject: small parser bugfix; working on textboxes in ui library X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=034a05bb89191e21bd5b95f48351b9d99d47763a;p=onyx.git small parser bugfix; working on textboxes in ui library --- diff --git a/modules/bmfont/position.onyx b/modules/bmfont/position.onyx index d598e796..75e22996 100644 --- a/modules/bmfont/position.onyx +++ b/modules/bmfont/position.onyx @@ -8,6 +8,8 @@ package bmfont tex_x, tex_y : f32; tex_w, tex_h : f32; + + x_advance : f32; } #private_file renderable_glyph: Renderable_Glyph; @@ -62,8 +64,7 @@ get_character_positions :: (font: ^BMFont, size: f32, text: str, x: f32, y: f32) glyph := rc.font->get_glyph(char); if glyph == null { - glyph = rc.font->get_glyph(255); - assert(glyph != null, "NO NULL GLYPH"); + continue; } renderable_glyph.pos_x = math.floor(rc.x + ~~glyph.xoffset * rc.size + .5); @@ -76,6 +77,7 @@ get_character_positions :: (font: ^BMFont, size: f32, text: str, x: f32, y: f32) renderable_glyph.tex_w = glyph.tex_w; renderable_glyph.tex_h = glyph.tex_h; + renderable_glyph.x_advance = ~~glyph.xadvance * rc.size; rc.x += ~~glyph.xadvance * rc.size; return ^renderable_glyph, true; diff --git a/modules/ui/components/textbox.onyx b/modules/ui/components/textbox.onyx index 6d1bf6c0..d6e4d75c 100644 --- a/modules/ui/components/textbox.onyx +++ b/modules/ui/components/textbox.onyx @@ -12,29 +12,117 @@ Textbox_Theme :: struct { border_color := gfx.Color4.{ 0.2, 0.2, 0.2 }; border_width := 6.0f; @InPixels + + cursor_color := gfx.Color4.{ 0, 0, 0 }; } default_textbox_theme := Textbox_Theme.{}; -textbox :: (use r: Rectangle, text: str, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool { +#private_file +Textbox_Editing_State :: struct { + hash: UI_Id = 0; + + cursor_position: i32 = 0; + + cursor_animation := 0.0f; + cursor_animation_speed := 0.02f; +} + +textbox_editing_state := Textbox_Editing_State.{}; + +textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool { result := false; hash := get_site_hash(site, increment); animation_state := map.get(^animation_states, hash); + border_width := theme.border_width; + width, height := Rectangle.dimensions(r); + + text := text_buffer.buffer; + text_width := bmfont.get_width(^font, text, theme.font_size); + text_height := bmfont.get_height(^font, text, theme.font_size); + + text_x := x0 + border_width; + text_y := y0 + ~~ font.common.baseline * theme.font_size + (height - text_height) / 2; + + if is_hot_item(hash) && !is_active_item(hash) { + if mouse_state.left_button_down && Rectangle.contains(r, mouse_state.x, mouse_state.y) { + set_active_item(hash); + textbox_editing_state.hash = hash; + } + } + + if is_active_item(hash) { + if mouse_state.left_button_just_down && !Rectangle.contains(r, mouse_state.x, mouse_state.y) { + set_active_item(0); + textbox_editing_state.hash = 0; + textbox_editing_state.cursor_position = 0; + } + } + if Rectangle.contains(r, mouse_state.x, mouse_state.y) { set_hot_item(hash); } + if textbox_editing_state.hash == hash { + // This is the actively edited textbox + move_towards(^textbox_editing_state.cursor_animation, 0.0f, textbox_editing_state.cursor_animation_speed); + if textbox_editing_state.cursor_animation <= 0.0f do textbox_editing_state.cursor_animation = 1.0f; + + if mouse_state.left_button_down && Rectangle.contains(r, mouse_state.x, mouse_state.y) { + textbox_editing_state.cursor_animation = 1.0f; + textbox_editing_state.cursor_position = get_cursor_position(text_buffer, text_x, text_y, theme.font_size, mouse_state.x, mouse_state.y); + } + + if keyboard_state.keys_down_this_frame > 0 { + for key_index: keyboard_state.keys_down_this_frame { + key := keyboard_state.keycodes_down_this_frame[key_index]; + + switch key.code { + case 0x25 do textbox_editing_state.cursor_position -= 1; + case 0x27 do textbox_editing_state.cursor_position += 1; + case 0x23 do textbox_editing_state.cursor_position = text_buffer.buffer.count; + case 0x24 do textbox_editing_state.cursor_position = 0; + + case 0x08 { + // Backspace + if text_buffer->delete(textbox_editing_state.cursor_position) { + textbox_editing_state.cursor_position -= 1; + } + } + + case 0x2E { + // Delete + text_buffer->delete(textbox_editing_state.cursor_position + 1); + } + + case #default { + shift_is_pressed := key.modifiers & .SHIFT; + index := key.code * 2; + if shift_is_pressed do index += 1; + + char := key_map[index]; + if char != #char "\0" { + if text_buffer->insert(textbox_editing_state.cursor_position, char) { + textbox_editing_state.cursor_position += 1; + } + } + } + } + + textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.buffer.count); + textbox_editing_state.cursor_animation = 1.0f; + } + } + } + if is_hot_item(hash) { move_towards(^animation_state.hover_time, 1.0f, 0.1f); @ThemeConfiguration } else { move_towards(^animation_state.hover_time, 0.0f, 0.1f); @ThemeConfiguration } - border_width := theme.border_width; - width, height := Rectangle.dimensions(r); - gfx.set_texture(); gfx.rect(.{ x0, y0 }, .{ width, height }, theme.border_color); @@ -42,13 +130,20 @@ textbox :: (use r: Rectangle, text: str, theme := ^default_textbox_theme, site : surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); gfx.rect(.{ x0 + border_width, y0 + border_width }, .{ width - border_width * 2, height - border_width * 2 }, surface_color); - text_width := bmfont.get_width(^font, text, theme.font_size); - text_height := bmfont.get_height(^font, text, theme.font_size); + draw_text_raw(text, text_x, text_y, theme.font_size, theme.text_color); - draw_text_raw(text, - x0 + border_width, - y0 + ~~ font.common.baseline * theme.font_size + (height - text_height) / 2, - theme.font_size, theme.text_color); + if textbox_editing_state.hash == hash { + cursor_x := get_cursor_location(text_buffer, text_x, text_y, theme.font_size, textbox_editing_state.cursor_position); + cursor_y := y0 + theme.border_width; + cursor_h := height - theme.border_width * 2; + + cursor_color := theme.cursor_color; + cursor_color.a = textbox_editing_state.cursor_animation; + + draw_rect( + .{ cursor_x, cursor_y, cursor_x + 4, cursor_y + cursor_h }, + color=cursor_color); @ThemeConfiguration + } move_towards(^animation_state.click_time, 0.0f, 0.08f); @ThemeConfiguration @@ -57,4 +152,323 @@ textbox :: (use r: Rectangle, text: str, theme := ^default_textbox_theme, site : } else { map.delete(^animation_states, hash); } -} \ No newline at end of file + + return result; +} + +#private_file +get_cursor_location :: (text_buffer: ^String_Buffer, text_x: f32, text_y: f32, text_size: f32, cursor_position: i32) -> f32 { + countdown := cursor_position + 1; + last_x : f32 = text_x; + last_w : f32; + + for glyph: bmfont.get_character_positions(^font, text_size, text_buffer.buffer, text_x, text_y) { + if countdown == 0 do return last_x; + + last_x = glyph.pos_x; + last_w = glyph.x_advance; + + countdown -= 1; + } + + if countdown == 0 do return last_x; + + return last_x + last_w; +} + +#private_file +get_cursor_position :: (text_buffer: ^String_Buffer, text_x: f32, text_y: f32, text_size: f32, mouse_x: f32, mouse_y: f32) -> i32 { + cursor_position := 0; + + last_x: f32 = text_x; + + for glyph: bmfont.get_character_positions(^font, text_size, text_buffer.buffer, text_x, text_y) { + cursor_position += 1; + if cursor_position == 1 do continue; + + @Incomplete // This is still very wrong but it is better than nothing + if mouse_x <= glyph.pos_x + glyph.pos_w / 2 && mouse_x >= last_x { + return cursor_position - 1; + } + + last_x = glyph.pos_x + glyph.pos_w / 2; + } + + return text_buffer.buffer.count; +} + +@Relocate // I think this should be part of the standard library? Or something similar to it? +String_Buffer :: struct { + @Todo // make 'use buffer : [] u8' work here + buffer : [] u8; + capacity : u32; + + insert :: (use sb: ^String_Buffer, position: i32, ch: u8) -> bool { + if position >= capacity do return false; + if buffer.count >= capacity do return false; + + while i := cast(i32) buffer.count; i > position { + defer i -= 1; + + buffer.data[i] = buffer.data[i - 1]; + } + + buffer.data[position] = ch; + buffer.count += 1; + return true; + } + + delete :: (use sb: ^String_Buffer, position: i32) -> bool { + if position > capacity do return false; + if buffer.count == 0 do return false; + if position == 0 do return false; + + while i := position - 1; i < cast(i32) buffer.count - 1{ + defer i += 1; + + buffer.data[i] = buffer.data[i + 1]; + } + + buffer.count -= 1; + return true; + } +} + + +@Relocate @Cleanup +// This keymap is very wrong in a lot of ways. It works for my standard US keyboard, but will break horribly +// for any other keyboard layout. I would like to use something else from the browser, but unsurprisingly the +// browser does not make this easy. Gotta love web "standards".... +key_map := u8.[ + // Keycode Normal Shift + /* 00 */ #char "\0", #char "\0", + /* 01 */ #char "\0", #char "\0", + /* 02 */ #char "\0", #char "\0", + /* 03 */ #char "\0", #char "\0", + /* 04 */ #char "\0", #char "\0", + /* 05 */ #char "\0", #char "\0", + /* 06 */ #char "\0", #char "\0", + /* 07 */ #char "\0", #char "\0", + /* 08 */ #char "\0", #char "\0", + /* 09 */ #char "\0", #char "\0", + /* 10 */ #char "\0", #char "\0", + /* 11 */ #char "\0", #char "\0", + /* 12 */ #char "\0", #char "\0", + /* 13 */ #char "\0", #char "\0", + /* 14 */ #char "\0", #char "\0", + /* 15 */ #char "\0", #char "\0", + /* 16 */ #char "\0", #char "\0", + /* 17 */ #char "\0", #char "\0", + /* 18 */ #char "\0", #char "\0", + /* 19 */ #char "\0", #char "\0", + /* 20 */ #char "\0", #char "\0", + /* 21 */ #char "\0", #char "\0", + /* 22 */ #char "\0", #char "\0", + /* 23 */ #char "\0", #char "\0", + /* 24 */ #char "\0", #char "\0", + /* 25 */ #char "\0", #char "\0", + /* 26 */ #char "\0", #char "\0", + /* 27 */ #char "\0", #char "\0", + /* 28 */ #char "\0", #char "\0", + /* 29 */ #char "\0", #char "\0", + /* 30 */ #char "\0", #char "\0", + /* 31 */ #char "\0", #char "\0", + /* 32 */ #char " ", #char " ", + /* 33 */ #char "\0", #char "\0", + /* 34 */ #char "\0", #char "\0", + /* 35 */ #char "\0", #char "\0", + /* 36 */ #char "\0", #char "\0", + /* 37 */ #char "\0", #char "\0", + /* 38 */ #char "\0", #char "\0", + /* 39 */ #char "\0", #char "\0", + /* 40 */ #char "\0", #char "\0", + /* 41 */ #char "\0", #char "\0", + /* 42 */ #char "\0", #char "\0", + /* 43 */ #char "\0", #char "\0", + /* 44 */ #char "\0", #char "\0", + /* 45 */ #char "\0", #char "\0", + /* 46 */ #char "\0", #char "\0", + /* 47 */ #char "\0", #char "\0", + /* 48 */ #char "0", #char ")", + /* 49 */ #char "1", #char "!", + /* 50 */ #char "2", #char "@", + /* 51 */ #char "3", #char "#", + /* 52 */ #char "4", #char "$", + /* 53 */ #char "5", #char "%", + /* 54 */ #char "6", #char "^", + /* 55 */ #char "7", #char "&", + /* 56 */ #char "8", #char "*", + /* 57 */ #char "9", #char "(", + /* 58 */ #char "\0", #char "\0", + /* 59 */ #char ";", #char ":", + /* 60 */ #char "\0", #char "\0", + /* 61 */ #char "=", #char "+", + /* 62 */ #char "\0", #char "\0", + /* 63 */ #char "\0", #char "\0", + /* 64 */ #char "\0", #char "\0", + /* 65 */ #char "a", #char "A", + /* 66 */ #char "b", #char "B", + /* 67 */ #char "c", #char "C", + /* 68 */ #char "d", #char "D", + /* 69 */ #char "e", #char "E", + /* 70 */ #char "f", #char "F", + /* 71 */ #char "g", #char "G", + /* 72 */ #char "h", #char "H", + /* 73 */ #char "i", #char "I", + /* 74 */ #char "j", #char "J", + /* 75 */ #char "k", #char "K", + /* 76 */ #char "l", #char "L", + /* 77 */ #char "m", #char "M", + /* 78 */ #char "n", #char "N", + /* 79 */ #char "o", #char "O", + /* 80 */ #char "p", #char "P", + /* 81 */ #char "q", #char "Q", + /* 82 */ #char "r", #char "R", + /* 83 */ #char "s", #char "S", + /* 84 */ #char "t", #char "T", + /* 85 */ #char "u", #char "U", + /* 86 */ #char "v", #char "V", + /* 87 */ #char "w", #char "W", + /* 88 */ #char "x", #char "X", + /* 89 */ #char "y", #char "Y", + /* 90 */ #char "z", #char "Z", + /* 91 */ #char "\0", #char "\0", + /* 92 */ #char "\0", #char "\0", + /* 93 */ #char "\0", #char "\0", + /* 94 */ #char "\0", #char "\0", + /* 95 */ #char "\0", #char "\0", + /* 96 */ #char "\0", #char "\0", + /* 97 */ #char "\0", #char "\0", + /* 98 */ #char "\0", #char "\0", + /* 99 */ #char "\0", #char "\0", + /* 100 */ #char "\0", #char "\0", + /* 101 */ #char "\0", #char "\0", + /* 102 */ #char "\0", #char "\0", + /* 103 */ #char "\0", #char "\0", + /* 104 */ #char "\0", #char "\0", + /* 105 */ #char "\0", #char "\0", + /* 106 */ #char "\0", #char "\0", + /* 107 */ #char "\0", #char "\0", + /* 108 */ #char "\0", #char "\0", + /* 109 */ #char "\0", #char "\0", + /* 110 */ #char "\0", #char "\0", + /* 111 */ #char "\0", #char "\0", + /* 112 */ #char "\0", #char "\0", + /* 113 */ #char "\0", #char "\0", + /* 114 */ #char "\0", #char "\0", + /* 115 */ #char "\0", #char "\0", + /* 116 */ #char "\0", #char "\0", + /* 117 */ #char "\0", #char "\0", + /* 118 */ #char "\0", #char "\0", + /* 119 */ #char "\0", #char "\0", + /* 120 */ #char "\0", #char "\0", + /* 121 */ #char "\0", #char "\0", + /* 122 */ #char "\0", #char "\0", + /* 123 */ #char "\0", #char "\0", + /* 124 */ #char "\0", #char "\0", + /* 125 */ #char "\0", #char "\0", + /* 126 */ #char "\0", #char "\0", + /* 127 */ #char "\0", #char "\0", + /* 128 */ #char "\0", #char "\0", + /* 129 */ #char "\0", #char "\0", + /* 130 */ #char "\0", #char "\0", + /* 131 */ #char "\0", #char "\0", + /* 132 */ #char "\0", #char "\0", + /* 133 */ #char "\0", #char "\0", + /* 134 */ #char "\0", #char "\0", + /* 135 */ #char "\0", #char "\0", + /* 136 */ #char "\0", #char "\0", + /* 137 */ #char "\0", #char "\0", + /* 138 */ #char "\0", #char "\0", + /* 139 */ #char "\0", #char "\0", + /* 140 */ #char "\0", #char "\0", + /* 141 */ #char "\0", #char "\0", + /* 142 */ #char "\0", #char "\0", + /* 143 */ #char "\0", #char "\0", + /* 144 */ #char "\0", #char "\0", + /* 145 */ #char "\0", #char "\0", + /* 146 */ #char "\0", #char "\0", + /* 147 */ #char "\0", #char "\0", + /* 148 */ #char "\0", #char "\0", + /* 149 */ #char "\0", #char "\0", + /* 150 */ #char "\0", #char "\0", + /* 151 */ #char "\0", #char "\0", + /* 152 */ #char "\0", #char "\0", + /* 153 */ #char "\0", #char "\0", + /* 154 */ #char "\0", #char "\0", + /* 155 */ #char "\0", #char "\0", + /* 156 */ #char "\0", #char "\0", + /* 157 */ #char "\0", #char "\0", + /* 158 */ #char "\0", #char "\0", + /* 159 */ #char "\0", #char "\0", + /* 160 */ #char "\0", #char "\0", + /* 161 */ #char "\0", #char "\0", + /* 162 */ #char "\0", #char "\0", + /* 163 */ #char "\0", #char "\0", + /* 164 */ #char "\0", #char "\0", + /* 165 */ #char "\0", #char "\0", + /* 166 */ #char "\0", #char "\0", + /* 167 */ #char "\0", #char "\0", + /* 168 */ #char "\0", #char "\0", + /* 169 */ #char "\0", #char "\0", + /* 170 */ #char "\0", #char "\0", + /* 171 */ #char "\0", #char "\0", + /* 172 */ #char "\0", #char "\0", + /* 173 */ #char "-", #char "_", + /* 174 */ #char "\0", #char "\0", + /* 175 */ #char "\0", #char "\0", + /* 176 */ #char "\0", #char "\0", + /* 177 */ #char "\0", #char "\0", + /* 178 */ #char "\0", #char "\0", + /* 179 */ #char "\0", #char "\0", + /* 180 */ #char "\0", #char "\0", + /* 181 */ #char "\0", #char "\0", + /* 182 */ #char "\0", #char "\0", + /* 183 */ #char "\0", #char "\0", + /* 184 */ #char "\0", #char "\0", + /* 185 */ #char "\0", #char "\0", + /* 186 */ #char ";", #char ":", + /* 187 */ #char "=", #char "+", + /* 188 */ #char ",", #char "<", + /* 189 */ #char "-", #char "_", + /* 190 */ #char ".", #char ">", + /* 191 */ #char "/", #char "?", + /* 192 */ #char "`", #char "~", + /* 193 */ #char "\0", #char "\0", + /* 194 */ #char "\0", #char "\0", + /* 195 */ #char "\0", #char "\0", + /* 196 */ #char "\0", #char "\0", + /* 197 */ #char "\0", #char "\0", + /* 198 */ #char "\0", #char "\0", + /* 199 */ #char "\0", #char "\0", + /* 200 */ #char "\0", #char "\0", + /* 201 */ #char "\0", #char "\0", + /* 202 */ #char "\0", #char "\0", + /* 203 */ #char "\0", #char "\0", + /* 204 */ #char "\0", #char "\0", + /* 205 */ #char "\0", #char "\0", + /* 206 */ #char "\0", #char "\0", + /* 207 */ #char "\0", #char "\0", + /* 208 */ #char "\0", #char "\0", + /* 209 */ #char "\0", #char "\0", + /* 210 */ #char "\0", #char "\0", + /* 211 */ #char "\0", #char "\0", + /* 212 */ #char "\0", #char "\0", + /* 213 */ #char "\0", #char "\0", + /* 214 */ #char "\0", #char "\0", + /* 215 */ #char "\0", #char "\0", + /* 216 */ #char "\0", #char "\0", + /* 217 */ #char "\0", #char "\0", + /* 218 */ #char "\0", #char "\0", + /* 219 */ #char "[", #char "{", + /* 220 */ #char "\\", #char "|", + /* 221 */ #char "]", #char "}", + /* 222 */ #char "'", #char "\"", + /* 223 */ #char "\0", #char "\0", + /* 224 */ #char "\0", #char "\0", + /* 225 */ #char "\0", #char "\0", + /* 226 */ #char "\0", #char "\0", + /* 227 */ #char "\0", #char "\0", + /* 228 */ #char "\0", #char "\0", + /* 229 */ #char "\0", #char "\0", +]; \ No newline at end of file diff --git a/modules/ui/ui.onyx b/modules/ui/ui.onyx index c7922100..ae027920 100644 --- a/modules/ui/ui.onyx +++ b/modules/ui/ui.onyx @@ -17,18 +17,42 @@ UI_Id :: #type u32 #private active_item : UI_Id = 0 #private hot_item_was_set := false -MouseState :: struct { - left_button_down := false; - left_button_just_up := false; +Mouse_State :: struct { + left_button_down := false; + left_button_just_down := false; + left_button_just_up := false; - right_button_down := false; - right_button_just_up := false; + right_button_down := false; + right_button_just_down := false; + right_button_just_up := false; x: f32 = 0; y: f32 = 0; } -mouse_state := MouseState.{}; +mouse_state := Mouse_State.{}; + +Keyboard_State :: struct { + Max_Keys_Per_Frame :: 4; + + Key_State :: struct { + code: u32 = 0; + modifiers: Modifiers = ~~0; + + Modifiers :: enum #flags { + CTRL; ALT; SHIFT; META; + } + } + + keycodes_down_this_frame : [Max_Keys_Per_Frame] Key_State; + keys_down_this_frame : u32; + + keycodes_up_this_frame : [Max_Keys_Per_Frame] Key_State; + keys_up_this_frame : u32; +} + +@Note // This assumes that this gets zero intialized. +keyboard_state: Keyboard_State; init_ui :: () { @@ -38,8 +62,16 @@ init_ui :: () { } clear_buttons :: () { - mouse_state.left_button_just_up = false; - mouse_state.right_button_just_up = false; + mouse_state.left_button_just_up = false; + mouse_state.left_button_just_down = false; + mouse_state.right_button_just_up = false; + mouse_state.right_button_just_down = false; + + for ^key: keyboard_state.keycodes_down_this_frame do *key = .{}; + keyboard_state.keys_down_this_frame = 0; + + for ^key: keyboard_state.keycodes_up_this_frame do *key = .{}; + keyboard_state.keys_up_this_frame = 0; if !hot_item_was_set do set_hot_item(0); hot_item_was_set = false; @@ -54,16 +86,22 @@ update_mouse_position :: (new_x: f32, new_y: f32) { mouse_state.y = new_y; } -#private_file ButtonKind :: enum { Left; Right; Middle; } +#private_file Mouse_Button_Kind :: enum { Left; Right; Middle; } -button_pressed :: (kind: ButtonKind) { +button_pressed :: (kind: Mouse_Button_Kind) { switch kind { - case .Left do mouse_state.left_button_down = true; - case .Right do mouse_state.right_button_down = true; + case .Left { + mouse_state.left_button_down = true; + mouse_state.left_button_just_down = true; + } + case .Right { + mouse_state.right_button_down = true; + mouse_state.right_button_just_down = true; + } } } -button_released :: (kind: ButtonKind) { +button_released :: (kind: Mouse_Button_Kind) { switch kind { case .Left { mouse_state.left_button_down = false; @@ -77,6 +115,23 @@ button_released :: (kind: ButtonKind) { } } +key_down :: (keycode: u32, modifiers: Keyboard_State.Key_State.Modifiers) { + keyboard_state.keycodes_down_this_frame[keyboard_state.keys_down_this_frame] = .{ + keycode, + modifiers + }; + + keyboard_state.keys_down_this_frame += 1; +} + +key_up :: (keycode: u32, modifiers: Keyboard_State.Key_State.Modifiers) { + keyboard_state.keycodes_up_this_frame[keyboard_state.keys_up_this_frame] = .{ + keycode, + modifiers + }; + + keyboard_state.keys_up_this_frame += 1; +} set_active_item :: (id: UI_Id) -> bool { active_item = id; diff --git a/src/onyxparser.c b/src/onyxparser.c index 00e38aaf..0616cbce 100644 --- a/src/onyxparser.c +++ b/src/onyxparser.c @@ -259,6 +259,8 @@ static b32 parse_possible_array_literal(OnyxParser* parser, AstTyped* left, AstT expect_token(parser, '.'); expect_token(parser, '['); while (!consume_token_if_next(parser, ']')) { + if (parser->hit_unexpected_token) return 1; + AstTyped* value = parse_expression(parser, 0); bh_arr_push(al->values, value);