odin-libui

Odin bindings to libui
Log | Files | Refs | Submodules | README | LICENSE

parse.odin (8869B)


      1 package main
      2 
      3 import "core:bytes"
      4 import "core:fmt"
      5 import "core:strconv"
      6 import "core:strings"
      7 import "core:unicode"
      8 
      9 Struct :: struct {
     10 	name:   string,
     11 	fields: [dynamic]Struct_Field,
     12 }
     13 
     14 Struct_Field :: struct {
     15 	type:       string,
     16 	name:       string,
     17 	is_pointer: bool,
     18 	// Because we don't do generic expression parsing we must explicitly 
     19 	// allow for function pointers.
     20 	function:   Maybe(Function),
     21 }
     22 
     23 Function :: struct {
     24 	name:              string,
     25 	return_type:       string,
     26 	return_is_pointer: bool,
     27 	is_type:           bool,
     28 	args:              [dynamic]Function_Argument,
     29 }
     30 
     31 Function_Argument :: struct {
     32 	type:       string,
     33 	name:       string,
     34 	is_pointer: bool,
     35 	function:   Maybe(Function),
     36 }
     37 
     38 Enum :: struct {
     39 	name:     string,
     40 	type:     string,
     41 	variants: [dynamic]Enum_Variant,
     42 }
     43 
     44 Enum_Variant :: struct {
     45 	name:  string,
     46 	value: Maybe(uint),
     47 }
     48 
     49 Parser :: struct {
     50 	data:          []byte,
     51 	offset:        int,
     52 	line:          int,
     53 	column:        int,
     54 	enum_defs:     [dynamic]Enum,
     55 	struct_defs:   [dynamic]Struct,
     56 	function_defs: [dynamic]Function,
     57 }
     58 
     59 Parser_Proc :: #type proc(_: ^Parser)
     60 
     61 parser_parse_struct :: proc(p: ^Parser) {
     62 	struct_def: Struct
     63 	parser_consume(p, "struct")
     64 	parser_skip_space(p)
     65 	struct_def.name = parser_consume_identifier(p)
     66 	parser_skip_space(p)
     67 	if !parser_check(p, "{") do return // not all structs have bodies
     68 	parser_consume(p, "{")
     69 	for {
     70 		parser_skip_space(p)
     71 		if parser_check(p, "};") {
     72 			break
     73 		}
     74 		append(&struct_def.fields, parser_parse_struct_field(p))
     75 	}
     76 	parser_consume(p, "};")
     77 	append(&p.struct_defs, struct_def)
     78 }
     79 
     80 parser_check :: proc(p: ^Parser, text: string) -> bool {
     81 	return parser_peek(p, len(text)) == text
     82 }
     83 
     84 parser_consume :: proc(p: ^Parser, text: string) {
     85 	got := p.data[p.offset:p.offset + len(text)]
     86 	if !bytes.equal(got, transmute([]u8)text) {
     87 		panic(
     88 			fmt.tprintf(
     89 				"expected %q, got %q (offset=%d) (line=%d, col=%d) (context=%q)",
     90 				text,
     91 				got,
     92 				p.offset,
     93 				p.line + 1,
     94 				p.column,
     95 				p.data[p.offset - 10:p.offset + 10],
     96 			),
     97 		)
     98 	}
     99 	parser_skip_n(p, len(text))
    100 }
    101 
    102 parser_consume_identifier :: proc(p: ^Parser) -> string {
    103 	parser_skip_text(p, "const ")
    104 	parser_skip_text(p, "struct ")
    105 	start := p.offset
    106 	for {
    107 		if !is_ident(p.data[p.offset]) {
    108 			return cast(string)p.data[start:p.offset]
    109 		}
    110 		if !parser_advance(p) {
    111 			break
    112 		}
    113 	}
    114 	panic("EOF before identifier")
    115 }
    116 
    117 is_ident :: proc(b: byte) -> bool {
    118 	r := rune(b)
    119 	return unicode.is_letter(r) || unicode.is_digit(r) || b == '_'
    120 }
    121 
    122 parser_skip_space :: proc(p: ^Parser) -> bool {
    123 	start := p.offset
    124 	for {
    125 		if p.data[p.offset] == '/' {
    126 			parser_skip_comment(p)
    127 		}
    128 		if !unicode.is_space(rune(p.data[p.offset])) {
    129 			return p.offset != start
    130 		}
    131 		if !parser_advance(p) {
    132 			break
    133 		}
    134 	}
    135 	return false
    136 }
    137 
    138 parser_parse_struct_field :: proc(p: ^Parser) -> (field: Struct_Field) {
    139 	ident := parser_consume_identifier(p)
    140 	parser_skip_space(p)
    141 	if parser_check(p, "*") {
    142 		parser_consume(p, "*")
    143 		field.is_pointer = true
    144 	}
    145 	parser_skip_space(p)
    146 	if !parser_check(p, "(") {
    147 		field.name = parser_consume_identifier(p)
    148 	} else {
    149 		field.function = parser_parse_function_pointer(p)
    150 	}
    151 	parser_consume(p, ";")
    152 	// Special case to ensure that the return type is appropriately labeled. 
    153 	if field.function == nil {
    154 		field.type = ident
    155 	} else {
    156 		if f, ok := &field.function.?; ok {
    157 			f.return_type = ident
    158 		}
    159 	}
    160 	return
    161 }
    162 
    163 parser_parse_function_pointer :: proc(p: ^Parser) -> (f: Function) {
    164 	f.return_type = parser_consume_identifier(p)
    165 	parser_skip_space(p)
    166 	parser_consume(p, "(")
    167 	parser_skip_space(p)
    168 	parser_consume(p, "*")
    169 	parser_skip_space(p)
    170 	f.name = parser_consume_identifier(p)
    171 	parser_consume(p, ")")
    172 	parser_skip_space(p)
    173 	f.args = parser_parse_function_arguments(p)
    174 	return
    175 }
    176 
    177 parser_parse_function :: proc(p: ^Parser) {
    178 	f: Function
    179 	parser_consume(p, "_UI_EXTERN")
    180 	parser_skip_space(p)
    181 	f.return_type = parser_consume_identifier(p)
    182 	parser_skip_space(p)
    183 	if parser_check(p, "*") {
    184 		f.return_is_pointer = true
    185 		parser_consume(p, "*")
    186 	}
    187 	parser_skip_space(p)
    188 	f.name = parser_consume_identifier(p)
    189 	f.args = parser_parse_function_arguments(p)
    190 	parser_skip_space(p)
    191 	parser_consume(p, ";")
    192 	append(&p.function_defs, f)
    193 	return
    194 }
    195 
    196 parser_parse_function_arguments :: proc(p: ^Parser) -> (args: [dynamic]Function_Argument) {
    197 	parser_consume(p, "(")
    198 	for {
    199 		arg: Function_Argument
    200 		if parser_check(p, ")") {
    201 			break
    202 		}
    203 		parser_skip_space(p)
    204 		arg.type = parser_consume_identifier(p)
    205 		parser_skip_space(p)
    206 		if parser_check(p, "*") {
    207 			arg.is_pointer = true
    208 			parser_consume(p, "*")
    209 		}
    210 		parser_skip_space(p)
    211 		if parser_check(p, "(") {
    212 			fn := parser_parse_function_pointer(p)
    213 			fn.return_type = arg.type
    214 			arg.type = ""
    215 			arg.function = fn
    216 		} else {
    217 			arg.name = parser_consume_identifier(p)
    218 		}
    219 
    220 		if parser_check(p, ",") {
    221 			parser_consume(p, ",")
    222 		}
    223 		parser_skip_space(p)
    224 		append(&args, arg)
    225 	}
    226 	parser_consume(p, ")")
    227 	return
    228 }
    229 
    230 parser_skip_n :: proc(p: ^Parser, n: int = 1) {
    231 	for ii in 0 ..< n {
    232 		if !parser_advance(p) do return
    233 	}
    234 }
    235 
    236 parser_skip_text :: proc(p: ^Parser, text: string) {
    237 	if parser_check(p, text) {
    238 		parser_consume(p, text)
    239 	}
    240 }
    241 
    242 // TODO: might need to handle multiline preprocessor things
    243 parser_skip_preproccessor_stuff :: proc(p: ^Parser) -> bool {
    244 	if !parser_check(p, "#") {
    245 		return false
    246 	}
    247 	parser_skip_until(p, "\n")
    248 	return true
    249 }
    250 
    251 parser_parse_enum :: proc(p: ^Parser) {
    252 	enum_def: Enum
    253 	parser_consume(p, "_UI_ENUM")
    254 	parser_consume(p, "(")
    255 	enum_def.name = parser_consume_identifier(p)
    256 	parser_consume(p, ")")
    257 	parser_skip_space(p)
    258 	parser_consume(p, "{")
    259 	for {
    260 		variant: Enum_Variant
    261 		parser_skip_space(p)
    262 		if parser_check(p, "};") {
    263 			break
    264 		}
    265 		append(&enum_def.variants, parser_parse_enum_variant(p))
    266 	}
    267 	parser_consume(p, "};")
    268 	append(&p.enum_defs, enum_def)
    269 }
    270 
    271 parser_skip_comment :: proc(p: ^Parser) -> bool {
    272 	line_comment := parser_check(p, "//")
    273 	inline_comment := parser_check(p, "/*")
    274 
    275 	if line_comment {
    276 		parser_skip_until(p, "\n")
    277 		return true
    278 	}
    279 
    280 	if inline_comment {
    281 		parser_skip_until(p, "*/")
    282 		return true
    283 	}
    284 
    285 	return false
    286 }
    287 
    288 parser_skip_until :: proc(p: ^Parser, text: string) {
    289 	for {
    290 		if parser_check(p, text) {
    291 			defer parser_consume(p, text)
    292 			return
    293 		}
    294 		if !parser_advance(p) do return
    295 	}
    296 }
    297 
    298 parser_advance :: proc(p: ^Parser) -> bool {
    299 	if p.offset > len(p.data) - 1 {
    300 		return false
    301 	}
    302 	if p.data[p.offset] == '\n' {
    303 		p.line += 1
    304 		p.column = 0
    305 	}
    306 	p.column += 1
    307 	p.offset += 1
    308 	return p.offset < len(p.data)
    309 }
    310 
    311 parser_peek :: proc(p: ^Parser, n: int) -> string {
    312 	end := p.offset + n
    313 	if end > len(p.data) - 1 do return ""
    314 	return cast(string)p.data[p.offset:end]
    315 }
    316 
    317 bitwise_operators :: [?]string{"<<", ">>"}
    318 
    319 parser_parse_enum_variant :: proc(p: ^Parser) -> (variant: Enum_Variant) {
    320 	variant.name = parser_consume_identifier(p)
    321 	parser_skip_space(p)
    322 	if parser_check(p, "=") {
    323 		parser_consume(p, "=")
    324 		parser_skip_space(p)
    325 		n_1 := parser_parse_int(p)
    326 		parser_skip_space(p)
    327 		// Because some enums use bitshifting we have to account for that,
    328 		// but because we aren't doing proper expresion parsing it's total
    329 		// jank.
    330 		for op in bitwise_operators {
    331 			if !parser_check(p, op) do continue
    332 			parser_consume(p, op)
    333 			parser_skip_space(p)
    334 			n_2 := parser_parse_int(p)
    335 			switch op {
    336 			case "<<":
    337 				n_1 = n_1 << n_2
    338 			case ">>":
    339 				n_1 = n_1 << n_2
    340 			}
    341 		}
    342 		variant.value = n_1
    343 	}
    344 	parser_consume(p, ",")
    345 	return
    346 }
    347 
    348 parser_parse_int :: proc(p: ^Parser) -> uint {
    349 	start := p.offset
    350 	index: int
    351 	for unicode.is_digit(rune(p.data[p.offset + index])) {
    352 		index += 1
    353 	}
    354 	n, n_ok := strconv.parse_uint(cast(string)p.data[p.offset:p.offset + index])
    355 	if !n_ok {
    356 		panic("not a number")
    357 	}
    358 	parser_skip_n(p, index)
    359 	return n
    360 }
    361 
    362 // We are only handling typedef'd function pointers. 
    363 parser_parse_typedef :: proc(p: ^Parser) {
    364 	parser_consume(p, "typedef")
    365 	parser_skip_space(p)
    366 	if parser_check(p, "struct") {
    367 		parser_skip_until(p, ";")
    368 		return
    369 	}
    370 	function := parser_parse_function_pointer(p)
    371 	function.is_type = true
    372 	parser_skip_until(p, ";")
    373 	append(&p.function_defs, function)
    374 }
    375 
    376 keywords :: [?]string{"_UI_ENUM", "_UI_EXTERN", "typedef", "struct"}
    377 
    378 parser_parse :: proc(p: ^Parser, by: []byte) {
    379 	p.data = by
    380 
    381 	for p.offset < len(p.data) - 1 {
    382 		skipped_space := parser_skip_space(p)
    383 		skipped_comment := parser_skip_comment(p)
    384 		skipped_preprocessor := parser_skip_preproccessor_stuff(p)
    385 
    386 		start := p.offset
    387 
    388 
    389 		defer if !(skipped_space || skipped_comment || skipped_preprocessor) {
    390 			end := p.offset
    391 
    392 			if start == end {
    393 				parser_skip_n(p, 1)
    394 			}
    395 		}
    396 
    397 		for keyword in keywords {
    398 			if !parser_check(p, keyword) {
    399 				continue
    400 			}
    401 			switch keyword {
    402 			case "struct":
    403 				parser_parse_struct(p)
    404 			case "_UI_ENUM":
    405 				parser_parse_enum(p)
    406 			case "_UI_EXTERN":
    407 				parser_parse_function(p)
    408 			case "typedef":
    409 				parser_parse_typedef(p)
    410 			}
    411 		}
    412 	}
    413 }
    414