package main import "core:fmt" import "core:io" import "core:mem" import "core:os" import "core:strings" TAB :: "\t" ENUM_TAG :: "gml_pragma(\"enum_reflection\")" ENUM_ENUM :: "Enum" ENUM_NAMES_GLOBAL :: "global._ENUM_NAMES" ENUM_MEMBER_NAMES_GLOBAL :: "global._ENUM_MEMBER_NAMES" ENUM_GET_NAME_FUNCTION :: "enum_get_name" ENUM_GET_MEMBER_NAME_FUNCTION :: "enum_get_member_name" ENUM_GET_MEMBER_NAMES_FUNCTION :: "enum_get_member_names" ENUM_GET_MEMBER_NAME_FULL_FUNCTION :: "enum_get_member_name_full" print_help_message :: proc() { fmt.println("--- GameMaker enum reflection ---", flush = false) fmt.println("Usage: ") } main :: proc() { switch len(os.args) { case 0, 1, 2: print_help_message() case 3: err := program(os.args[1], os.args[2]) if err != nil { fmt.println("Error: ", err) } case: fmt.println("Error: Too many arguments") print_help_message() } } Error :: union { os.Error, mem.Allocator_Error, ProgramError, } ProgramError :: enum { ProjectDirNotDir, OutputGmlNotExists, ScriptsDirNotExists, ObjectsDirNotExists, } program :: proc(project_dir, output_gml_path: string) -> Error { if !os.is_directory(project_dir) { return ProgramError.ProjectDirNotDir } if !os.exists(output_gml_path) { return ProgramError.OutputGmlNotExists } _, output_gml_name := os.split_path(output_gml_path) scripts_dir := os.join_path({project_dir, "scripts"}, context.allocator) or_return if !os.exists(scripts_dir) { return ProgramError.ScriptsDirNotExists } objects_dir := os.join_path({project_dir, "objects"}, context.allocator) or_return if !os.exists(objects_dir) { return ProgramError.ObjectsDirNotExists } enums := make([dynamic]GmlEnum) walker := os.walker_create(scripts_dir) for info, ok := os.walker_walk(&walker); ok; info, ok = os.walker_walk(&walker) { if _, ext := os.split_filename(info.name); ext != "gml" { continue } if info.name == output_gml_name { continue } data := os.read_entire_file(info.fullpath, context.allocator) or_return gml := string(data) gml_extract_enums(gml, info.name, &enums) } walker = os.walker_create(objects_dir) for info, ok := os.walker_walk(&walker); ok; info, ok = os.walker_walk(&walker) { if _, ext := os.split_filename(info.name); ext != "gml" { continue } if info.name == output_gml_name { continue } data := os.read_entire_file(info.fullpath, context.allocator) or_return gml := string(data) gml_extract_enums(gml, info.name, &enums) } gml := strings.builder_make() gml_write_enums(&gml, enums) os.write_entire_file(output_gml_path, strings.to_string(gml)) or_return return nil } GmlEnum :: struct { name: string, members: []string, } gml_extract_enums :: proc(gml, filename: string, enums_out: ^[dynamic]GmlEnum) { ENUM_SEARCH :: "enum " pos: int for true { gml_prev_enum := gml[pos:] pos_tag_start := strings.index(gml_prev_enum, ENUM_TAG) if pos_tag_start == -1 { break } pos_tag_end := pos_tag_start + len(ENUM_TAG) pos += pos_tag_end gml_tag_end := gml_prev_enum[pos_tag_end:] pos_enum_start := strings.index(gml_tag_end, ENUM_SEARCH) if pos_enum_start == -1 { fmt.println("Found " + ENUM_TAG + " in", filename, "but no enum") break } if len(strings.trim(strings.trim_space(gml_tag_end[:pos_enum_start]), ";\n")) > 0 { fmt.println("Found", ENUM_TAG, "in", filename, "but separation between tag and enum was too great") break } gml_enum_name_start := gml_tag_end[pos_enum_start + len(ENUM_SEARCH):] pos_enum_curly_open := strings.index(gml_enum_name_start, "{") if pos_enum_curly_open == -1 { fmt.println("Missing enum opening bracket in", filename) break } enum_name := strings.trim_space(gml_enum_name_start[:pos_enum_curly_open]) pos_enum_curly_close := strings.index(gml_enum_name_start, "}") if pos_enum_curly_close == -1 { fmt.println("Missing enum closing bracket in", filename) } gml_enum_content := gml_enum_name_start[pos_enum_curly_open + 1:pos_enum_curly_close] if strings.index(gml_enum_content, "/") != -1 { fmt.println("Enum members of", enum_name, "in", filename, "contained disallowed character '/' (possibly comments). Skipping...") continue } if strings.index(gml_enum_content, "=") != -1 { fmt.println("Enum members of", enum_name, "in", filename, "contained disallowed character '='. Skipping...") continue } enum_names := strings.split(gml_enum_content, ",") for i in 0 ..< len(enum_names) { enum_names[i] = strings.trim_space(enum_names[i]) } if len(enum_names[len(enum_names) - 1]) == 0 { enum_names = enum_names[:len(enum_names) - 1] } append(enums_out, GmlEnum{name = enum_name, members = enum_names}) } } gml_write_enums :: proc(gml: &strings.Builder, enums: [dynamic]GmlEnum) { fmt.sbprint(&gml, "/*\n" + TAB + "This file was generated using enum_reflection tool\n*/\n", sep = "") fmt.sbprint(&gml, "enum " + ENUM_ENUM + " {\n", sep = "") for e in enums { fmt.sbprint(&gml, TAB, e.name, ",\n", sep = "") } fmt.sbprint(&gml, TAB + "_Count\n}\n\n", sep = "") fmt.sbprint(&gml, ENUM_NAMES_GLOBAL, " = [\n", sep = "") for e in enums { fmt.sbprint(&gml, TAB, "\"", e.name, "\",\n", sep = "") } fmt.sbprint(&gml, "];\n\n", sep = "") fmt.sbprint(&gml, ENUM_MEMBER_NAMES_GLOBAL, " = [\n", sep = "") for e in enums { fmt.sbprint(&gml, TAB, "[", sep = "") for entry in e.members { fmt.sbprint(&gml, "\"", entry, "\", ", sep = "") } fmt.sbprint(&gml, "],\n", sep = "") } fmt.sbprint(&gml, "];\n\n", sep = "") fmt.sbprint(&gml, "function ", ENUM_GET_NAME_FUNCTION, "(index) {return ", ENUM_NAMES_GLOBAL, "[index];}\n", sep = "") fmt.sbprint(&gml, "function ", ENUM_GET_MEMBER_NAME_FUNCTION, "(index, member) {return ", ENUM_MEMBER_NAMES_GLOBAL, "[index][member];}\n", sep = "") fmt.sbprint(&gml, "function ", ENUM_GET_MEMBER_NAMES_FUNCTION, "(index) {return ", ENUM_MEMBER_NAMES_GLOBAL, "[index];}", sep = "") fmt.sbprint(&gml, "function ", ENUM_GET_MEMBER_NAME_FULL_FUNCTION, "(index, member) {\n", sep = "") fmt.sbprint(&gml, TAB, "return $\"{", ENUM_NAMES_GLOBAL, "[index]}.{" + ENUM_MEMBER_NAMES_GLOBAL, "[index][member]}\";\n", sep = "") fmt.sbprint(&gml, "}\n\n", sep = "") }