Initial commit

This commit is contained in:
Synthasmagoria 2026-05-12 14:54:23 +02:00
commit bdd292ce2a

210
main.odin Normal file
View file

@ -0,0 +1,210 @@
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: <program> <project dir> <output gml>")
}
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")
}