aboutsummaryrefslogtreecommitdiff
path: root/src/wmap_parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/wmap_parser.c')
-rw-r--r--src/wmap_parser.c687
1 files changed, 687 insertions, 0 deletions
diff --git a/src/wmap_parser.c b/src/wmap_parser.c
new file mode 100644
index 0000000..e0cf6d6
--- /dev/null
+++ b/src/wmap_parser.c
@@ -0,0 +1,687 @@
+#include "wmap_parser.h"
+
+static void wmap_lexer_init(wmap_lexer_t* lexer, const char* input) {
+ lexer->input = input;
+ lexer->pos = 0;
+ lexer->line = 1;
+ lexer->col = 1;
+ lexer->current_token = WMAP_TOKEN_EOF;
+ lexer->token_text[0] = '\0';
+ lexer->token_number = 0.0f;
+}
+
+static void wmap_lexer_skip_whitespace(wmap_lexer_t* lexer) {
+ while (lexer->input[lexer->pos] != '\0' &&
+ (lexer->input[lexer->pos] == ' ' || lexer->input[lexer->pos] == '\t')) {
+ lexer->pos++;
+ lexer->col++;
+ }
+}
+
+WMAP_68K_INLINE int wmap_lexer_is_identifier_char(char c) WMAP_68K_FAST_CALL {
+ return c != '-' && c != '+' && c != ',' && c != '[' && c != ']' &&
+ c != '(' && c != ')' && c != '\n' && c != '\r' && c != '\0' && c != ' ' && c != '\t';
+}
+
+static wmap_token_type_t wmap_lexer_next_token(wmap_lexer_t* lexer) {
+ char c;
+ int start, len, has_dot;
+
+ wmap_lexer_skip_whitespace(lexer);
+
+ if (lexer->input[lexer->pos] == '\0') {
+ return lexer->current_token = WMAP_TOKEN_EOF;
+ }
+
+ c = lexer->input[lexer->pos];
+
+ if (c == '\n') {
+ lexer->pos++;
+ lexer->line++;
+ lexer->col = 1;
+ return lexer->current_token = WMAP_TOKEN_NEWLINE;
+ }
+
+ if (c == '\r') {
+ lexer->pos++;
+ if (lexer->input[lexer->pos] == '\n') {
+ lexer->pos++;
+ }
+ lexer->line++;
+ lexer->col = 1;
+ return lexer->current_token = WMAP_TOKEN_NEWLINE;
+ }
+
+ if (c == '(') {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_LPAREN;
+ }
+
+ if (c == ')') {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_RPAREN;
+ }
+
+ if (c == '[') {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_LBRACKET;
+ }
+
+ if (c == ']') {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_RBRACKET;
+ }
+
+ if (c == ',') {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_COMMA;
+ }
+
+ if (c == '+') {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_PLUS;
+ }
+
+ if (c == '-') {
+ if (lexer->input[lexer->pos + 1] == '-') {
+ lexer->pos += 2;
+ lexer->col += 2;
+ return lexer->current_token = WMAP_TOKEN_DASH;
+ } else if (lexer->input[lexer->pos + 1] == '>') {
+ lexer->pos += 2;
+ lexer->col += 2;
+ return lexer->current_token = WMAP_TOKEN_ARROW;
+ } else {
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_MINUS;
+ }
+ }
+
+ if (isdigit(c) || c == '.') {
+ start = lexer->pos;
+ has_dot = 0;
+
+ if (c == '.') {
+ has_dot = 1;
+ lexer->pos++;
+ lexer->col++;
+ if (!isdigit(lexer->input[lexer->pos])) {
+ return lexer->current_token = WMAP_TOKEN_ERROR;
+ }
+ }
+
+ while (isdigit(lexer->input[lexer->pos])) {
+ lexer->pos++;
+ lexer->col++;
+ }
+
+ if (!has_dot && lexer->input[lexer->pos] == '.') {
+ has_dot = 1;
+ lexer->pos++;
+ lexer->col++;
+ while (isdigit(lexer->input[lexer->pos])) {
+ lexer->pos++;
+ lexer->col++;
+ }
+ }
+
+ len = lexer->pos - start;
+ if (len >= WMAP_MAX_NAME_LEN) {
+ return lexer->current_token = WMAP_TOKEN_ERROR;
+ }
+
+ strncpy(lexer->token_text, lexer->input + start, len);
+ lexer->token_text[len] = '\0';
+ lexer->token_number = (float)atof(lexer->token_text);
+
+ return lexer->current_token = WMAP_TOKEN_NUMBER;
+ }
+
+ if (wmap_lexer_is_identifier_char(c)) {
+ start = lexer->pos;
+
+ while (lexer->input[lexer->pos] != '\0' &&
+ wmap_lexer_is_identifier_char(lexer->input[lexer->pos])) {
+ lexer->pos++;
+ lexer->col++;
+ }
+
+ len = lexer->pos - start;
+ if (len >= WMAP_MAX_NAME_LEN) {
+ len = WMAP_MAX_NAME_LEN - 1;
+ }
+
+ strncpy(lexer->token_text, lexer->input + start, len);
+ lexer->token_text[len] = '\0';
+
+ return lexer->current_token = WMAP_TOKEN_IDENTIFIER;
+ }
+
+ lexer->pos++;
+ lexer->col++;
+ return lexer->current_token = WMAP_TOKEN_ERROR;
+}
+
+WMAP_68K_INLINE int wmap_lexer_is_keyword(const char* text, const char* keyword) WMAP_68K_FAST_CALL {
+ int i = 0;
+ while (text[i] && keyword[i]) {
+ char tc = text[i];
+ char kc = keyword[i];
+ if (tc >= 'A' && tc <= 'Z') tc += 32;
+ if (kc >= 'A' && kc <= 'Z') kc += 32;
+ if (tc != kc) {
+ return 0;
+ }
+ i++;
+ }
+ return text[i] == keyword[i];
+}
+
+static wmap_shape_t wmap_parse_shape(const char* text) {
+ if (wmap_lexer_is_keyword(text, "x")) return WMAP_SHAPE_X;
+ if (wmap_lexer_is_keyword(text, "square")) return WMAP_SHAPE_SQUARE;
+ if (wmap_lexer_is_keyword(text, "triangle")) return WMAP_SHAPE_TRIANGLE;
+ if (wmap_lexer_is_keyword(text, "circle")) return WMAP_SHAPE_CIRCLE;
+ return WMAP_SHAPE_NONE;
+}
+
+static wmap_stage_t wmap_parse_stage(const char* text) {
+ if (wmap_lexer_is_keyword(text, "i")) return WMAP_STAGE_I;
+ if (wmap_lexer_is_keyword(text, "ii")) return WMAP_STAGE_II;
+ if (wmap_lexer_is_keyword(text, "iii")) return WMAP_STAGE_III;
+ if (wmap_lexer_is_keyword(text, "iv")) return WMAP_STAGE_IV;
+ return 0;
+}
+
+static wmap_parser_t* wmap_parser_create(const char* input) {
+ wmap_parser_t* parser = (wmap_parser_t*)malloc(sizeof(wmap_parser_t));
+ if (!parser) return NULL;
+
+ parser->map = (wmap_map_t*)malloc(sizeof(wmap_map_t));
+ if (!parser->map) {
+ free(parser);
+ return NULL;
+ }
+
+ memset(parser->map, 0, sizeof(wmap_map_t));
+ wmap_lexer_init(&parser->lexer, input);
+ parser->error_count = 0;
+
+ wmap_lexer_next_token(&parser->lexer);
+
+ return parser;
+}
+
+static void wmap_parser_destroy(wmap_parser_t* parser) {
+ if (parser) {
+ if (parser->map) {
+ free(parser->map);
+ }
+ free(parser);
+ }
+}
+
+static void wmap_parser_skip_line(wmap_parser_t* parser) {
+ while (parser->lexer.current_token != WMAP_TOKEN_NEWLINE &&
+ parser->lexer.current_token != WMAP_TOKEN_EOF) {
+ wmap_lexer_next_token(&parser->lexer);
+ }
+ if (parser->lexer.current_token == WMAP_TOKEN_NEWLINE) {
+ wmap_lexer_next_token(&parser->lexer);
+ }
+}
+
+static int wmap_parser_parse_position(wmap_parser_t* parser, float* x, float* y) {
+ if (parser->lexer.current_token != WMAP_TOKEN_LPAREN) {
+ return 0;
+ }
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token != WMAP_TOKEN_NUMBER) {
+ return 0;
+ }
+ *x = parser->lexer.token_number;
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token != WMAP_TOKEN_COMMA) {
+ return 0;
+ }
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token != WMAP_TOKEN_NUMBER) {
+ return 0;
+ }
+ *y = parser->lexer.token_number;
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token != WMAP_TOKEN_RPAREN) {
+ return 0;
+ }
+ wmap_lexer_next_token(&parser->lexer);
+
+ return 1;
+}
+
+static int wmap_parser_parse_component(wmap_parser_t* parser) {
+ wmap_component_t* comp;
+
+ if (parser->map->component_count >= WMAP_MAX_COMPONENTS) {
+ return 0;
+ }
+
+ comp = &parser->map->components[parser->map->component_count];
+
+ if (parser->lexer.current_token != WMAP_TOKEN_IDENTIFIER) {
+ return 0;
+ }
+ strncpy(comp->name, parser->lexer.token_text, WMAP_MAX_NAME_LEN - 1);
+ comp->name[WMAP_MAX_NAME_LEN - 1] = '\0';
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (!wmap_parser_parse_position(parser, &comp->x, &comp->y)) {
+ return 0;
+ }
+
+ comp->shape = WMAP_SHAPE_NONE;
+ if (parser->lexer.current_token == WMAP_TOKEN_LBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (parser->lexer.current_token == WMAP_TOKEN_IDENTIFIER) {
+ comp->shape = wmap_parse_shape(parser->lexer.token_text);
+ wmap_lexer_next_token(&parser->lexer);
+ }
+ if (parser->lexer.current_token == WMAP_TOKEN_RBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+ }
+ }
+
+ parser->map->component_count++;
+ return 1;
+}
+
+static int wmap_parser_parse_dependency(wmap_parser_t* parser) {
+ wmap_dependency_t* dep;
+
+ if (parser->map->dependency_count >= WMAP_MAX_DEPENDENCIES) {
+ return 0;
+ }
+
+ dep = &parser->map->dependencies[parser->map->dependency_count];
+
+ if (parser->lexer.current_token != WMAP_TOKEN_IDENTIFIER) {
+ return 0;
+ }
+ strncpy(dep->from, parser->lexer.token_text, WMAP_MAX_NAME_LEN - 1);
+ dep->from[WMAP_MAX_NAME_LEN - 1] = '\0';
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token == WMAP_TOKEN_ARROW) {
+ dep->is_arrow = 1;
+ wmap_lexer_next_token(&parser->lexer);
+ } else if (parser->lexer.current_token == WMAP_TOKEN_DASH) {
+ dep->is_arrow = 0;
+ wmap_lexer_next_token(&parser->lexer);
+ } else {
+ return 0;
+ }
+
+ if (parser->lexer.current_token != WMAP_TOKEN_IDENTIFIER) {
+ return 0;
+ }
+ strncpy(dep->to, parser->lexer.token_text, WMAP_MAX_NAME_LEN - 1);
+ dep->to[WMAP_MAX_NAME_LEN - 1] = '\0';
+ wmap_lexer_next_token(&parser->lexer);
+
+ parser->map->dependency_count++;
+ return 1;
+}
+
+static int wmap_parser_parse_note(wmap_parser_t* parser) {
+ wmap_note_t* note;
+ int text_pos;
+
+ if (parser->map->note_count >= WMAP_MAX_NOTES) {
+ return 0;
+ }
+
+ note = &parser->map->notes[parser->map->note_count];
+
+ if (!wmap_parser_parse_position(parser, &note->x, &note->y)) {
+ return 0;
+ }
+
+ text_pos = 0;
+ while (parser->lexer.current_token != WMAP_TOKEN_NEWLINE &&
+ parser->lexer.current_token != WMAP_TOKEN_EOF &&
+ text_pos < WMAP_MAX_TEXT_LEN - 1) {
+
+ if (parser->lexer.current_token == WMAP_TOKEN_IDENTIFIER) {
+ int len = strlen(parser->lexer.token_text);
+ if (text_pos + len + 1 < WMAP_MAX_TEXT_LEN - 1) {
+ strncpy(note->text + text_pos, parser->lexer.token_text, len);
+ text_pos += len;
+ note->text[text_pos++] = ' ';
+ note->text[text_pos] = '\0';
+ }
+ }
+ wmap_lexer_next_token(&parser->lexer);
+ }
+
+ if (text_pos > 0) {
+ note->text[text_pos - 1] = '\0';
+ } else {
+ note->text[0] = '\0';
+ }
+
+ parser->map->note_count++;
+ return 1;
+}
+
+static int wmap_parser_parse_stage(wmap_parser_t* parser) {
+ wmap_stage_data_t* stage;
+
+ if (parser->map->stage_count >= WMAP_MAX_STAGES) {
+ return 0;
+ }
+
+ stage = &parser->map->stages[parser->map->stage_count];
+
+ if (parser->lexer.current_token != WMAP_TOKEN_IDENTIFIER) {
+ return 0;
+ }
+
+ stage->stage = wmap_parse_stage(parser->lexer.token_text);
+ if (stage->stage == 0) {
+ return 0;
+ }
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token != WMAP_TOKEN_RBRACKET) {
+ return 0;
+ }
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token != WMAP_TOKEN_NUMBER) {
+ return 0;
+ }
+ stage->value = parser->lexer.token_number;
+ wmap_lexer_next_token(&parser->lexer);
+
+ parser->map->stage_count++;
+ return 1;
+}
+
+static int wmap_parser_parse_group(wmap_parser_t* parser) {
+ wmap_group_t* group;
+
+ if (parser->map->group_count >= WMAP_MAX_GROUPS) {
+ return 0;
+ }
+
+ group = &parser->map->groups[parser->map->group_count];
+ group->member_count = 0;
+
+ while (parser->lexer.current_token == WMAP_TOKEN_IDENTIFIER &&
+ group->member_count < WMAP_MAX_GROUP_MEMBERS) {
+
+ strncpy(group->members[group->member_count], parser->lexer.token_text, WMAP_MAX_NAME_LEN - 1);
+ group->members[group->member_count][WMAP_MAX_NAME_LEN - 1] = '\0';
+ group->member_count++;
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token == WMAP_TOKEN_COMMA) {
+ wmap_lexer_next_token(&parser->lexer);
+ } else {
+ break;
+ }
+ }
+
+ if (group->member_count > 0) {
+ parser->map->group_count++;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int wmap_parser_parse_inertia(wmap_parser_t* parser) {
+ wmap_inertia_t* inertia;
+
+ if (parser->map->inertia_count >= WMAP_MAX_INERTIAS) {
+ return 0;
+ }
+
+ inertia = &parser->map->inertias[parser->map->inertia_count];
+
+ if (parser->lexer.current_token != WMAP_TOKEN_IDENTIFIER) {
+ return 0;
+ }
+ strncpy(inertia->name, parser->lexer.token_text, WMAP_MAX_NAME_LEN - 1);
+ inertia->name[WMAP_MAX_NAME_LEN - 1] = '\0';
+ wmap_lexer_next_token(&parser->lexer);
+
+ parser->map->inertia_count++;
+ return 1;
+}
+
+static int wmap_parser_parse_evolution(wmap_parser_t* parser) {
+ wmap_evolution_t* evolution;
+ int sign;
+
+ if (parser->map->evolution_count >= WMAP_MAX_EVOLUTIONS) {
+ return 0;
+ }
+
+ evolution = &parser->map->evolutions[parser->map->evolution_count];
+
+ if (parser->lexer.current_token != WMAP_TOKEN_IDENTIFIER) {
+ return 0;
+ }
+ strncpy(evolution->name, parser->lexer.token_text, WMAP_MAX_NAME_LEN - 1);
+ evolution->name[WMAP_MAX_NAME_LEN - 1] = '\0';
+ wmap_lexer_next_token(&parser->lexer);
+
+ sign = 1;
+ if (parser->lexer.current_token == WMAP_TOKEN_PLUS) {
+ sign = 1;
+ wmap_lexer_next_token(&parser->lexer);
+ } else if (parser->lexer.current_token == WMAP_TOKEN_MINUS) {
+ sign = -1;
+ wmap_lexer_next_token(&parser->lexer);
+ }
+
+ if (parser->lexer.current_token != WMAP_TOKEN_NUMBER) {
+ return 0;
+ }
+ evolution->change = sign * parser->lexer.token_number;
+ wmap_lexer_next_token(&parser->lexer);
+
+ parser->map->evolution_count++;
+ return 1;
+}
+
+static int wmap_parser_parse(wmap_parser_t* parser) {
+ while (parser->lexer.current_token != WMAP_TOKEN_EOF) {
+ if (parser->lexer.current_token == WMAP_TOKEN_NEWLINE) {
+ wmap_lexer_next_token(&parser->lexer);
+ continue;
+ }
+
+ if (parser->lexer.current_token == WMAP_TOKEN_LBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+
+ if (parser->lexer.current_token == WMAP_TOKEN_IDENTIFIER) {
+ if (wmap_lexer_is_keyword(parser->lexer.token_text, "note")) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (parser->lexer.current_token == WMAP_TOKEN_RBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (!wmap_parser_parse_note(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else if (wmap_lexer_is_keyword(parser->lexer.token_text, "group")) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (parser->lexer.current_token == WMAP_TOKEN_RBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (!wmap_parser_parse_group(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else if (wmap_lexer_is_keyword(parser->lexer.token_text, "inertia")) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (parser->lexer.current_token == WMAP_TOKEN_RBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (!wmap_parser_parse_inertia(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else if (wmap_lexer_is_keyword(parser->lexer.token_text, "evolution")) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (parser->lexer.current_token == WMAP_TOKEN_RBRACKET) {
+ wmap_lexer_next_token(&parser->lexer);
+ if (!wmap_parser_parse_evolution(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else {
+ if (!wmap_parser_parse_stage(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ }
+ } else {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else if (parser->lexer.current_token == WMAP_TOKEN_IDENTIFIER) {
+ wmap_lexer_t lookahead = parser->lexer;
+ int is_dependency = 0;
+
+ wmap_lexer_next_token(&lookahead);
+ while (lookahead.current_token != WMAP_TOKEN_NEWLINE &&
+ lookahead.current_token != WMAP_TOKEN_EOF) {
+ if (lookahead.current_token == WMAP_TOKEN_ARROW ||
+ lookahead.current_token == WMAP_TOKEN_DASH) {
+ is_dependency = 1;
+ break;
+ }
+ wmap_lexer_next_token(&lookahead);
+ }
+
+ if (is_dependency) {
+ if (!wmap_parser_parse_dependency(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ } else {
+ if (!wmap_parser_parse_component(parser)) {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ }
+ } else {
+ parser->error_count++;
+ wmap_parser_skip_line(parser);
+ }
+ }
+
+ return parser->error_count == 0;
+}
+
+wmap_map_t* wmap_parse_string(const char* input) {
+ wmap_parser_t* parser;
+ wmap_map_t* result;
+
+ if (!input) return NULL;
+
+ if (!wmap_validate_input(input, 1024 * 1024)) {
+ return NULL;
+ }
+
+ parser = wmap_parser_create(input);
+ if (!parser) return NULL;
+
+ wmap_parser_parse(parser);
+
+ result = parser->map;
+ parser->map = NULL;
+ wmap_parser_destroy(parser);
+
+ return result;
+}
+
+wmap_map_t* wmap_parse_file(const char* filename) {
+ FILE* file;
+ long size;
+ char* buffer;
+ wmap_map_t* result;
+
+ file = fopen(filename, "r");
+ if (!file) return NULL;
+
+ fseek(file, 0, SEEK_END);
+ size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ if (size < 0 || size > 10 * 1024 * 1024) {
+ fclose(file);
+ return NULL;
+ }
+
+ buffer = (char*)malloc(size + 1);
+ if (!buffer) {
+ fclose(file);
+ return NULL;
+ }
+
+ fread(buffer, 1, size, file);
+ buffer[size] = '\0';
+ fclose(file);
+
+ result = wmap_parse_string(buffer);
+ free(buffer);
+
+ return result;
+}
+
+void wmap_map_free(wmap_map_t* map) {
+ if (map) {
+ free(map);
+ }
+}
+
+int wmap_validate_input(const char* input, size_t max_size) {
+ size_t len;
+ if (!input) {
+ return 0;
+ }
+
+ len = strlen(input);
+ if (len > max_size) {
+ return 0;
+ }
+
+ return 1;
+}