A 3D game engine from scratch.
at main 295 lines 8.5 kB view raw
1// (c) 2020 Vlad-Stefan Harbuz <vlad@vladh.net> 2 3#include "../src_external/pstr.h" 4#include "peony_parser.hpp" 5#include "logs.hpp" 6#include "intrinsics.hpp" 7 8 9bool 10peony_parser::parse_file(PeonyFile *pf, char const *path) 11{ 12 i32 idx_entry = -1; 13 Entry *entry = nullptr; 14 15 FILE *f = fopen(path, "r"); 16 if (!f) { 17 logs::error("Could not open file %s.", path); 18 return false; 19 } 20 defer { fclose(f); }; 21 22 char token[MAX_TOKEN_LENGTH]; 23 24 while (get_non_trivial_token(token, f)) { 25 if (token[0] == TOKEN_HEADER_START) { 26 idx_entry++; 27 entry = &pf->entries[idx_entry]; 28 parse_header(token, f); 29 pstr_copy(entry->name, MAX_TOKEN_LENGTH, token); 30 } else if (is_token_name(token)) { 31 if (!entry) { 32 logs::fatal("Tried to parse file, but encountered data before header."); 33 } 34 Prop *prop = &entry->props[entry->n_props]; 35 parse_property(prop, token, f); 36 entry->n_props++; 37 if (entry->n_props > MAX_N_ENTRY_PROPS) { 38 logs::fatal("Too many props in peony file"); 39 assert(false); 40 } 41 } else { 42 logs::info("Unhandled token: %s", token); 43 } 44 } 45 46 pf->n_entries = idx_entry + 1; 47 return true; 48} 49 50 51void 52peony_parser::print_value(PropValue *value) 53{ 54 if (value->type == PropValueType::unknown) { 55 logs::info("<unknown>"); 56 } else if (value->type == PropValueType::string) { 57 logs::info("%s", value->string_value); 58 } else if (value->type == PropValueType::boolean) { 59 logs::info("%d", value->boolean_value); 60 } else if (value->type == PropValueType::number) { 61 logs::info("%f", value->number_value); 62 } else if (value->type == PropValueType::vec2) { 63 logs::print_v2(&value->vec2_value); 64 } else if (value->type == PropValueType::vec3) { 65 logs::print_v3(&value->vec3_value); 66 } else if (value->type == PropValueType::vec4) { 67 logs::print_v4(&value->vec4_value); 68 } else { 69 logs::info("<invalid>"); 70 } 71} 72 73 74bool 75peony_parser::is_char_whitespace(const char target) 76{ 77 return target == TOKEN_NEWLINE || target == TOKEN_SPACE; 78} 79 80 81bool 82peony_parser::is_token_whitespace(const char *token) 83{ 84 return is_char_whitespace(token[0]); 85} 86 87 88bool 89peony_parser::is_char_allowed_in_name(const char target) 90{ 91 return isalpha(target) || isdigit(target) || target == '_' || target == '-' || 92 target == '/' || target == ':' || target == '.'; 93} 94 95 96bool 97peony_parser::is_token_name(const char *token) 98{ 99 range (0, pstr_len(token)) { 100 if (!is_char_allowed_in_name(token[idx])) { 101 return false; 102 } 103 } 104 return true; 105} 106 107 108bool 109peony_parser::is_char_token_boundary(char target) 110{ 111 return is_char_whitespace(target) || 112 target == TOKEN_HEADER_START || 113 target == TOKEN_ELEMENT_SEPARATOR || 114 target == TOKEN_TUPLE_START || 115 target == TOKEN_TUPLE_END || 116 target == TOKEN_ARRAY_START || 117 target == TOKEN_ARRAY_END || 118 target == TOKEN_OBJECT_START || 119 target == TOKEN_OBJECT_END; 120} 121 122 123bool 124peony_parser::get_token(char *token, FILE *f) 125{ 126 u32 idx_token = 0; 127 bool could_get_token = true; 128 129 while (true) { 130 char new_char = (char)fgetc(f); 131 132 *(token + idx_token) = new_char; 133 idx_token++; 134 135 if (is_char_token_boundary(new_char)) { 136 if (idx_token - 1 == 0) { 137 // Our first character is already a boundary. Radical! 138 } else { 139 // Return the boundary to the buffer so we can put it in its own token. 140 ungetc(new_char, f); 141 *(token + idx_token - 1) = '\0'; 142 } 143 break; 144 } 145 146 if (new_char == EOF) { 147 could_get_token = false; 148 break; 149 } 150 151 if (idx_token >= MAX_TOKEN_LENGTH) { 152 logs::error("Reached max token length for token %s.", token); 153 break; 154 } 155 } 156 157 *(token + idx_token) = '\0'; 158 159 return could_get_token; 160} 161 162 163bool 164peony_parser::get_non_trivial_token(char *token, FILE *f) 165{ 166 bool could_get_token; 167 do { 168 could_get_token = get_token(token, f); 169 if (token[0] == TOKEN_COMMENT_START) { 170 while ((could_get_token = get_token(token, f)) != false) { 171 if (token[0] == TOKEN_NEWLINE) { 172 break; 173 } 174 } 175 } 176 } while (is_token_whitespace(token) || token[0] == TOKEN_ELEMENT_SEPARATOR); 177 return could_get_token; 178} 179 180 181void 182peony_parser::parse_vec2(char *token, FILE *f, v2 *parsed_vector) 183{ 184 get_non_trivial_token(token, f); 185 assert(token[0] == TOKEN_TUPLE_START); 186 get_non_trivial_token(token, f); 187 (*parsed_vector).x = (f32)strtod(token, nullptr); 188 get_non_trivial_token(token, f); 189 (*parsed_vector).y = (f32)strtod(token, nullptr); 190 get_non_trivial_token(token, f); 191 assert(token[0] == TOKEN_TUPLE_END); 192} 193 194 195void 196peony_parser::parse_vec3(char *token, FILE *f, v3 *parsed_vector) 197{ 198 get_non_trivial_token(token, f); 199 assert(token[0] == TOKEN_TUPLE_START); 200 get_non_trivial_token(token, f); 201 (*parsed_vector).x = (f32)strtod(token, nullptr); 202 get_non_trivial_token(token, f); 203 (*parsed_vector).y = (f32)strtod(token, nullptr); 204 get_non_trivial_token(token, f); 205 (*parsed_vector).z = (f32)strtod(token, nullptr); 206 get_non_trivial_token(token, f); 207 assert(token[0] == TOKEN_TUPLE_END); 208} 209 210 211void 212peony_parser::parse_vec4(char *token, FILE *f, v4 *parsed_vector) 213{ 214 get_non_trivial_token(token, f); 215 assert(token[0] == TOKEN_TUPLE_START); 216 get_non_trivial_token(token, f); 217 (*parsed_vector).x = (f32)strtod(token, nullptr); 218 get_non_trivial_token(token, f); 219 (*parsed_vector).y = (f32)strtod(token, nullptr); 220 get_non_trivial_token(token, f); 221 (*parsed_vector).z = (f32)strtod(token, nullptr); 222 get_non_trivial_token(token, f); 223 (*parsed_vector).w = (f32)strtod(token, nullptr); 224 get_non_trivial_token(token, f); 225 assert(token[0] == TOKEN_TUPLE_END); 226} 227 228 229void 230peony_parser::get_value_from_token(char *token, FILE *f, PropValue *prop_value) 231{ 232 // NOTE: Type names the can start a value: vec2, vec3, vec4 233 if (pstr_eq(token, "vec2")) { 234 prop_value->type = PropValueType::vec2; 235 parse_vec2(token, f, &prop_value->vec2_value); 236 } else if (pstr_eq(token, "vec3")) { 237 prop_value->type = PropValueType::vec3; 238 parse_vec3(token, f, &prop_value->vec3_value); 239 } else if (pstr_eq(token, "vec4")) { 240 prop_value->type = PropValueType::vec4; 241 parse_vec4(token, f, &prop_value->vec4_value); 242 } else if (pstr_eq(token, "true")) { 243 prop_value->type = PropValueType::boolean; 244 prop_value->boolean_value = true; 245 } else if (pstr_eq(token, "false")) { 246 prop_value->type = PropValueType::boolean; 247 prop_value->boolean_value = false; 248 } else if (pstr_eq(token, "0.0") || strtod(token, nullptr) != 0.0f) { 249 // NOTE: `strtod()` returns 0.0 if parsing fails, so we need to check 250 // if our value actually was 0.0; 251 prop_value->type = PropValueType::number; 252 prop_value->number_value = (f32)strtod(token, nullptr); 253 } else { 254 prop_value->type = PropValueType::string; 255 pstr_copy(prop_value->string_value, MAX_TOKEN_LENGTH, token); 256 } 257} 258 259 260void 261peony_parser::parse_header(char *token, FILE *f) 262{ 263 get_non_trivial_token(token, f); 264 assert(is_token_name(token)); 265} 266 267 268void 269peony_parser::parse_property(Prop *prop, char *token, FILE *f) 270{ 271 pstr_copy(prop->name, MAX_TOKEN_LENGTH, token); 272 get_non_trivial_token(token, f); 273 assert(token[0] == TOKEN_EQUALS); 274 get_non_trivial_token(token, f); 275 276 assert(is_token_name(token) || token[0] == TOKEN_ARRAY_START); 277 278 if (is_token_name(token)) { 279 get_value_from_token(token, f, &prop->values[prop->n_values]); 280 prop->n_values++; 281 if (prop->n_values > MAX_N_ARRAY_VALUES) { 282 logs::fatal("Too many array values in peony file"); 283 assert(false); 284 } 285 } else if (token[0] == TOKEN_ARRAY_START) { 286 while (true) { 287 get_non_trivial_token(token, f); 288 if (token[0] == TOKEN_ARRAY_END) { 289 break; 290 } 291 get_value_from_token(token, f, &prop->values[prop->n_values]); 292 prop->n_values++; 293 } 294 } 295}