#include "wmap_parser.h" /* Forward declarations for older compilers */ static int wmap_lexer_is_identifier_char(char c); static int wmap_lexer_is_keyword(const char* text, const char* keyword); 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++; } } static int wmap_lexer_is_identifier_char(char c) { 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((unsigned char)c) || c == '.') { start = lexer->pos; has_dot = 0; if (c == '.') { has_dot = 1; lexer->pos++; lexer->col++; if (!isdigit((unsigned char)lexer->input[lexer->pos])) { return lexer->current_token = WMAP_TOKEN_ERROR; } } while (isdigit((unsigned char)lexer->input[lexer->pos])) { lexer->pos++; lexer->col++; } if (!has_dot && lexer->input[lexer->pos] == '.') { has_dot = 1; lexer->pos++; lexer->col++; while (isdigit((unsigned char)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; } static int wmap_lexer_is_keyword(const char* text, const char* keyword) { 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, ¬e->x, ¬e->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; }