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) } f := os.open(output_gml_path, {.Write, .Trunc}) or_return writer := io.Writer(os.to_stream(f)) gml_write_enums(writer, enums) os.close(f) 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(writer: io.Writer, enums: [dynamic]GmlEnum) { io.write_string(writer, "/*\n" + TAB + "This file was generated using enum_reflection tool\n*/\n") io.write_string(writer, "enum " + ENUM_ENUM + " {\n") for e in enums { io.write_string(writer, TAB) io.write_string(writer, e.name) io.write_string(writer, ",\n") } io.write_string(writer, TAB + "_Count\n}\n\n") io.write_string(writer, ENUM_NAMES_GLOBAL + " = [\n") for e in enums { io.write_string(writer, TAB + "\"") io.write_string(writer, e.name) io.write_string(writer, "\",\n") } io.write_string(writer, "];\n\n") io.write_string(writer, ENUM_MEMBER_NAMES_GLOBAL + " = [\n") for e in enums { io.write_string(writer, TAB + "[") for entry in e.members { io.write_string(writer, "\"") io.write_string(writer, entry) io.write_string(writer, "\", ") } io.write_string(writer, "],\n") } io.write_string(writer, "];\n\n") io.write_string(writer, "function " + ENUM_GET_NAME_FUNCTION + "(index) {\n") io.write_string(writer, TAB + "return " + ENUM_NAMES_GLOBAL + "[index];\n") io.write_string(writer, "}\n\n") io.write_string(writer, "function " + ENUM_GET_MEMBER_NAME_FUNCTION + "(index, member) {\n") io.write_string(writer, TAB + "return " + ENUM_MEMBER_NAMES_GLOBAL + "[index][member];\n") io.write_string(writer, "}\n\n") io.write_string(writer, "function " + ENUM_GET_MEMBER_NAMES_FUNCTION + "(index) {\n") io.write_string(writer, TAB + "return " + ENUM_MEMBER_NAMES_GLOBAL + "[index];\n") io.write_string(writer, "}\n\n") io.write_string(writer, "function " + ENUM_GET_MEMBER_NAME_FULL_FUNCTION + "(index, member) {\n") io.write_string(writer, TAB + "return $\"{" + ENUM_NAMES_GLOBAL + "[index]}.{" + ENUM_MEMBER_NAMES_GLOBAL + "[index][member]}\";\n") io.write_string(writer, "}\n\n") }