A 3D game engine from scratch.
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}