package codegen import rt "base:runtime" import "core:encoding/xml" import "core:fmt" import "core:strconv" import "core:strings" // ===================================================================================================================== // TYPES Parser :: struct { doc: ^xml.Document, i: int, parents: [dynamic; 16]int } Parser_Error :: Maybe(Parser_Error_Value) Parser_Error_Value :: struct { type: Parser_Error_Type, expected, got: string, } Parser_Error_Type :: enum { Unknown_Elem_Type, Ident_Mismatch, Too_Many_Values, Body_Type_Mismatch, Missing_Attribute, Invalid_Attribute_Format, Invalid_Attribute_Value, } // ===================================================================================================================== // LIFECYCLE parser_make :: proc(doc: ^xml.Document) -> (p: Parser) { p.doc = doc return } // ===================================================================================================================== // USAGE parser_iterate :: proc(p: ^Parser) -> (ident: string, cond: bool) { p.i += 1 elems := &p.doc.elements if p.i >= len(elems) { return } cond = elems[p.i].parent == _parser_get_parent(p) if !cond { p.i -= 1 } return elems[p.i].ident, cond } parser_enter :: proc(p: ^Parser) { append(&p.parents, p.i) } parser_exit :: proc(p: ^Parser) { unordered_remove(&p.parents, len(p.parents) - 1) } parser_ensure_ident :: proc(p: ^Parser, ident: string) -> Parser_Error { elem := _parser_get_elem(p) // TODO: "Got" ident is owned by the stack frame! We need to clone it. return elem.ident == ident ? nil : parser_make_error(p, .Ident_Mismatch, ident, elem.ident) } parser_get_ident :: proc(p: ^Parser) -> string { return _parser_get_elem(p).ident } parser_get_attr :: proc(p: ^Parser, key: string, $T: typeid) -> (val: T, err: Parser_Error) { ok: bool val, ok = parser_maybe_get_attr(p, key, T) or_return if !ok { err = parser_make_error(p, .Missing_Attribute, key, "") } return } parser_maybe_get_attr :: proc(p: ^Parser, key: string, $T: typeid) -> (val: T, found: bool, err: Parser_Error) { attr := _parser_find_attr(p, key) if attr == nil { return } found = true val = _parser_strconv_attr(p, attr, T) or_return return } parser_get_body :: proc(p: ^Parser, $T: typeid) -> (ret: T, err: Parser_Error) { elem := _parser_get_elem(p) if len(elem.value) == 0 { // Description with summary but without body. return } else if len(elem.value) > 1 { fmt.println(elem) err = parser_make_error(p, .Too_Many_Values, 1, len(elem.value)) return } val, ok := elem.value[0].(T) if !ok { err = parser_make_error(p, .Body_Type_Mismatch, typeid_of(T), typeid_of(type_of(elem.value[0]))) return } return val, nil } parser_make_error :: proc(p: ^Parser, type: Parser_Error_Type, expected: $E, got: $G) -> Parser_Error_Value { return { type = type, expected = fmt.aprint(expected), got = fmt.aprint(got), } } // ===================================================================================================================== // INTERNAL _parser_get_elem :: proc(p: ^Parser) -> ^xml.Element { return &p.doc.elements[p.i] } _parser_get_parent :: proc(p: ^Parser) -> u32 { if len(p.parents) == 0 { return 0 } return u32(p.parents[len(p.parents) - 1]) } _parser_find_attr :: proc(p: ^Parser, key: string) -> ^xml.Attribute { for &attr in _parser_get_elem(p).attribs { if attr.key == key { return &attr } } return nil } _parser_strconv_attr :: proc(p: ^Parser, attr: ^xml.Attribute, $T: typeid) -> (val: T, err: Parser_Error) { ok_to_err :: proc(p: ^Parser, retval: $R, ok: bool) -> (R, Parser_Error) { return retval, ok ? nil : parser_make_error(p, .Invalid_Attribute_Format, typeid_of(T), "") } when T == string { // TODO: Normalize whitespace and remove newlines. return attr.val, nil } else when T == bool { return ok_to_err(p, strconv.parse_bool(attr.val)) } else when T == int { return ok_to_err(p, strconv.parse_int(attr.val)) } else when T == Arg_Type { switch attr.val { case "int": return .Int, nil case "uint": return .Uint, nil case "fixed": return .Fixed, nil case "string": return .String, nil case "array": return .Array, nil case "fd": return .FD, nil case "new_id": return .New_ID, nil case "object": return .Object, nil } return T{}, parser_make_error(p, .Invalid_Attribute_Value, attr.val, "") } else when T == Arg_Enum { if !strings.contains(attr.val, ".") { return attr.val, nil } head, _, tail := strings.partition(attr.val, ".") return Arg_Ext_Enum{head, tail}, nil } }