//////////////////////////////////////////////////////////// //~ Value conversion utilities String CR_SanitizeString(Arena *arena, String str) { String result = Zi; result.text = ArenaNext(arena, u8); for (u64 idx = 0; idx < str.len; ++idx) { u8 c = str.text[idx]; String sanitized = Zi; switch (c) { default: { sanitized.text = &c; sanitized.len = 1; } break; case '\r': { // Ignore } break; case '\n': { sanitized = Lit("\\n"); } break; case '\\': { sanitized = Lit("\\"); } break; case '"': { sanitized = Lit("\\\""); } break; } result.len += PushString(arena, sanitized).len; } return result; } i64 CR_IntFromString(String str) { i64 result = 0; b32 ok = 1; b32 is_hex = 0; u64 pos = 0; if (StringBeginsWith(str, Lit("0x"))) { pos += 2; is_hex = 1; } // Eat sign i32 sign = 1; if (ok && !is_hex) { if (pos < str.len) { if (str.text[pos] == '-') { sign = -1; pos += 1; } else if (str.text[pos] == '+') { pos += 1; } } } // Parse number while (ok && pos < str.len) { u8 c = str.text[pos]; u8 digit = 0; if (c >= '0' && c <= '9') { digit = c - '0'; } else if (is_hex) { if (c >= 'a' && c <= 'f') { digit = 10 + c - 'a'; } else if (c >= 'A' && c <= 'F') { digit = 10 + c - 'A'; } else { ok = 0; } } else { ok = 0; } pos += 1; result += digit * PowU64(10 + is_hex * 6, str.len - pos); } if (ok) { result *= sign; } else { result = 0; } return result; } // TODO: Handle hexadecimal f64 CR_FloatFromString(String str) { f64 result = 0; b32 ok = 1; // Eat sign u64 whole_start_idx = 0; i32 sign = 1; if (ok && str.len > 0) { if (whole_start_idx < str.len) { u8 c = str.text[whole_start_idx]; if (c == '-') { sign = -1; whole_start_idx += 1; } else if (c == '+') { whole_start_idx += 1; } } } // Find decimal place u64 frac_start_idx = whole_start_idx; for (; ok && frac_start_idx < str.len; ++frac_start_idx) { u8 c = str.text[frac_start_idx]; if (c == '.') { break; } } // Parse whole part u64 whole_part = 0; for (u64 char_idx = whole_start_idx; ok && char_idx < frac_start_idx; ++char_idx) { u8 c = str.text[char_idx]; if (c >= '0' && c <= '9') { u8 digit = c - '0'; whole_part += digit * PowU64(10, frac_start_idx - (char_idx + 1)); } else { ok = 0; } } // Parse frac part u64 frac_part = 0; for (u64 char_idx = frac_start_idx + 1; ok && char_idx < str.len; ++char_idx) { u8 c = str.text[char_idx]; if (c >= '0' && c <= '9') { u8 digit = c - '0'; frac_part += digit * PowU64(10, str.len - (char_idx + 1)); } else { ok = 0; } } if (ok) { if (frac_part != 0) { result = ((f64)whole_part + ((f64)frac_part / PowU64(10, str.len - (frac_start_idx + 1)))) * sign; } else { result = (f64)whole_part * sign; } } else { result = 0; } return result; } b32 CR_BoolFromString(String str) { return MatchString(str, Lit("true")); } Vec4 CR_Vec4FromString(String str) { Vec4 result = Zi; b32 ok = 1; // if (StringBeginsWith(str, Lit("("))) // { // str.text += 1; // str.len -= 1; // } // if (StringEndsWith(str, Lit(")"))) // { // str.len -= 1; // } u64 pos = 0; u64 parts_found = 0; String part = Zi; part.text = str.text; if (StringBeginsWith(str, Lit("("))) { part.text += 1; pos += 1; } while (pos < str.len && parts_found < 4) { u8 c = str.text[pos]; u8 next_c = 0; if (c == ',' || c == ')') { part.len = (str.text + pos) - part.text; f64 part_float = CR_FloatFromString(part); result.v[parts_found] = part_float; parts_found += 1; part.text = str.text + pos + 2; pos += 2; } else { pos += 1; } } return result; } Vec3 CR_Vec3FromString(String str) { Vec4 v4 = CR_Vec4FromString(str); Vec3 result = Zi; result.x = v4.x; result.y = v4.y; result.z = v4.z; return result; } Vec2 CR_Vec2FromString(String str) { Vec4 v4 = CR_Vec4FromString(str); Vec2 result = Zi; result.x = v4.x; result.y = v4.y; return result; } //////////////////////////////////////////////////////////// //~ Parse CR_Item *CR_ItemFromString(Arena *arena, String str) { CR_Item *result = PushStruct(arena, CR_Item); TempArena scratch = BeginScratch(arena); Enum(Mode) { Mode_None, Mode_Text, Mode_SingleLineComment, Mode_MultiLineComment, }; Mode mode = Mode_None; Struct(ItemNode) { ItemNode *next; CR_Item *item; }; // TODO: Reuse these ItemNode *top_item = PushStruct(scratch.arena, ItemNode); top_item->item = result; CR_Item *cur_item = 0; i64 text_start = 0; b32 text_is_string = 0; b32 text_has_name = 0; i64 pos = 0; b32 is_escaped = 0; while (pos < (i64)str.len) { u8 c = str.text[pos]; u8 next_c = 0; if (pos + 1 < (i64)str.len) { next_c = str.text[pos + 1]; } b32 is_eol = c == '\n' || c == '\r' || c == 0; b32 is_whitespace = c == ' ' || c == '\t' || is_eol; b32 next_is_eol = next_c == '\n' || next_c == '\r' || next_c == 0; b32 next_is_whitespace = next_c == ' ' || next_c == '\t' || next_is_eol; switch (mode) { case Mode_None: { if (c == '/' && next_c == '/') { mode = Mode_SingleLineComment; pos += 2; } else if (c == '/' && next_c == '*') { mode = Mode_MultiLineComment; pos += 2; } else if (!is_whitespace) { mode = Mode_Text; text_is_string = 0; text_start = pos; } else { pos += 1; } } break; case Mode_Text: { i64 text_end = -1; b32 is_name = 0; if (is_escaped) { is_escaped = 0; } else { if (c == '\\') { is_escaped = 1; } else if (c == '\"') { if (!text_is_string && pos == text_start) { text_is_string = 1; text_start = pos + 1; } else { text_end = pos; } } else if (next_c == ':') { text_end = pos + 1; is_name = 1; } } if (!text_is_string && next_is_whitespace) { text_end = pos + 1; } if (text_end != -1) { is_escaped = 0; String src_text = Zi; if (text_end > text_start) { src_text.len = text_end - text_start; src_text.text = str.text + text_start; } if (!cur_item) { cur_item = PushStruct(arena, CR_Item); cur_item->parent = top_item->item; DllQueuePush(cur_item->parent->first, cur_item->parent->last, cur_item); cur_item->parent->count += 1; } // FIXME: Arrays always have an item even if empty if (MatchString(src_text, Lit("{"))) { ItemNode *n = PushStruct(scratch.arena, ItemNode); n->item = cur_item; SllStackPush(top_item, n); pos += 1; cur_item = 0; } else if (MatchString(src_text, Lit("}"))) { SllStackPop(top_item); pos += 1; cur_item = 0; } else { // FIXME: Resolve escape sequences String final_text = PushString(arena, src_text); if (is_name) { cur_item->name = final_text; } else { cur_item->value = final_text; cur_item = 0; } if (is_name) { pos += 1; } } mode = Mode_None; text_is_string = 0; } pos += 1; } break; case Mode_SingleLineComment: { if (is_eol) { mode = Mode_None; } pos += 1; } break; case Mode_MultiLineComment: { if (!is_escaped && c == '*' && next_c == '/') { mode = Mode_None; pos += 2; } else { pos += 1; } } break; } } EndScratch(scratch); return result; }