├── .gitignore ├── .gitmodules ├── README.md ├── build.bat ├── build └── build.odin ├── build_test.bat ├── copy_to_shared.bat ├── mani ├── logging.odin ├── lua.odin └── mani.odin ├── mani_config.json ├── manigen ├── array_gen.odin ├── code_writer │ └── code_writer.odin ├── generator.odin ├── main.odin ├── parser.odin ├── proc_gen.odin └── struct_gen.odin ├── manigen2 ├── attriparse │ └── parser.odin ├── codegen │ └── parser.odin └── main.odin ├── media └── intellisense.gif ├── ols.json ├── run.bat ├── run_test.bat ├── ship.bat ├── test.bat └── test ├── lua542.dll ├── main.odin ├── main2.odin ├── subpackage └── subpackage.odin └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # But descend into directories 4 | !*/ 5 | 6 | !.gitignore 7 | !build.bat 8 | !run.bat 9 | !ship.bat 10 | !ols.json 11 | !.vscode 12 | 13 | !foreign/** 14 | !test/** 15 | 16 | !manigen/** 17 | !mani/** 18 | !media/** 19 | 20 | !manigen2/** 21 | !build/** 22 | 23 | !odin-build 24 | !odin-lua 25 | 26 | # Mani generations 27 | *.mani.odin 28 | *.lsp.lua 29 | 30 | !copy_to_shared.bat 31 | 32 | !build_test.bat 33 | !run_test.bat 34 | !test.bat 35 | 36 | !mani_config.json 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "odin-build"] 2 | path = odin-build 3 | url = https://github.com/DragosPopse/odin-build 4 | [submodule "odin-lua"] 5 | path = odin-lua 6 | url = https://github.com/DragosPopse/odin-lua 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mani 2 | Mani is an odin-to-lua exporter that generates Lua C API bindings and LSP code completion for your odin source files. 3 | 4 | ```odin 5 | package main 6 | 7 | import "core:fmt" 8 | import "shared:lua" 9 | import "shared:luaL" 10 | import "shared:mani" 11 | 12 | @(LuaExport = { 13 | Name = "my_nice_fn", 14 | }) 15 | test :: proc(var: int) -> (result1: int, result2: int) { 16 | fmt.printf("Lua: Var is %d\n", var) 17 | result1 = 60 + var 18 | result2 = 40 + var 19 | return 20 | } 21 | 22 | main :: proc() { 23 | L := luaL.newstate() 24 | mani.export_all(L, mani.global_state) 25 | } 26 | ``` 27 | ```lua 28 | -- lua code 29 | res1, res2 = my_nice_fn(3) 30 | print("Results from Call: " .. res1 .. " " .. res2) 31 | ``` 32 | 33 | ## Overview 34 | 35 | ### LSP Code Completion 36 | - Mani generates intellisense data for the lua language server. 37 | ![](media/intellisense.gif) 38 | 39 | ### Procedures Exporting 40 | ```odin 41 | @(LuaExport = { 42 | Name = "print_object", // optional 43 | }) 44 | half_object_print :: proc(using v: HalfObject) { 45 | fmt.printf("My value is %d, and my hidden is %d\n", value, hidden) 46 | } 47 | ``` 48 | - Procedures support all primitive types (string, cstring, ints, floats) as results and parameters 49 | - Pointers are only supported with exported structs/arrays 50 | - Multiple return values are supported 51 | 52 | ### Structs Exporting 53 | - Code completion is automatically configured to work for the new struct type declared 54 | - You must specify the userdata `Type` in `LuaExport` to either be `Full`, `Light` or both of them 55 | - Mani converts automatically between light and full userdata, depending on the context. If the user needs a value, it will be a value. 56 | - The term light userdata in mani's context is not fully accurate. Light userdatas cannot have metatables. What mani does is create a full userdata which is a wrapper around a single pointer. This allows to modify odin from within lua, while still allowing a metatable. 57 | - Passing a struct by value or pointer in odin doesn't have any correlation with the `Type` supported. It will *just work* 58 | ```odin 59 | @(LuaExport = { 60 | Name = "half_object", // optional lua name 61 | Type = { // Light or Full userdata. You must have at least one of these 62 | Full, // The struct can be allocated in lua fully 63 | Light // A wrapper to a pointer, usually from odin. You can modify odin from lua with this 64 | }, 65 | 66 | // Optional field access 67 | Fields = { 68 | value = "val", 69 | }, 70 | 71 | // Methods/metamethods assume that the first parameter of the proc is the object itself. 72 | // The object can either be passed by value or by pointer 73 | // All methods/metamethods require to be @LuaExport'ed 74 | Methods = { 75 | half_object_print = "print", 76 | mod_object = "mod", 77 | }, 78 | Metamethods = { 79 | __tostring = half_object_tostring, 80 | }, 81 | }) 82 | HalfObject :: struct { 83 | value: int, 84 | hidden: int, // Not in fields, therefore private 85 | } 86 | 87 | 88 | @(LuaExport) 89 | make_object :: proc(v: int) -> (result: HalfObject) { 90 | return { 91 | value = v, 92 | hidden = v + 1, 93 | } 94 | } 95 | 96 | @(LuaExport) 97 | mod_object :: proc(o: ^HalfObject, v: int) { 98 | o.value = v 99 | } 100 | 101 | @(LuaExport) 102 | half_object_tostring :: proc(using v: HalfObject) -> string { 103 | // Lua will allocate a string when you push it, so this can be on the temp allocator 104 | return fmt.tprintf("HalfObject {{%d, %d}}", value, hidden) 105 | } 106 | ``` 107 | 108 | 109 | ### Arrays Exporting 110 | - Mani supports arrays up to 4 elements 111 | - Swizzling support in lua!!! With intellisense!!!! 112 | - Methods/metamethods support 113 | - Because you can convert make conversions between different sizes of arrays via swizzling, mani requires to specify the conversions via `SwizzleTypes`, while also exporting every type defined in there. This is a bit of a headache, and I'm hoping to find a better solution to this 114 | - Distinct types not yet supported, because distinct and swizzling works rather weird in odin aswell 115 | ```odin 116 | @(LuaExport = { 117 | Name = "vec2f64", 118 | Type = {Light, Full}, 119 | SwizzleTypes = {Vec3, Vec4}, // For type conversions when swizzling (local my_vec3 = v.xyy) 120 | Fields = xyrg, // Needs to be divizible by len(array). It will treat x, r as arr[0] and y,g as arr[1] 121 | Metamethods = { 122 | __tostring = vec2_tostring, 123 | }, 124 | }) 125 | Vec2 :: [2]int 126 | 127 | // We will need to declare all our swizzles 128 | 129 | @(LuaExport = { 130 | Name = "vec3f64", 131 | Type = {Light, Full}, 132 | SwizzleTypes = {Vec2, Vec4}, // A Vec3 can construct a Vec2 and a Vec4 133 | Fields = xyzrgb, 134 | Metamethods = { 135 | __tostring = vec3_tostring, 136 | }, 137 | }) 138 | Vec3 :: [3]int 139 | 140 | 141 | @(LuaExport = { 142 | Name = "vec4f64", 143 | Type = {Light, Full}, 144 | SwizzleTypes = {Vec2, Vec3}, 145 | Fields = xyzwrgba, 146 | Methods = { 147 | vec4_tostring = "stringify", 148 | }, 149 | Metamethods = { 150 | __tostring = vec4_tostring, 151 | }, 152 | }) 153 | Vec4 :: [4]int 154 | 155 | 156 | @(LuaExport) 157 | make_vec4 :: proc(x: int, y: int, z: int, w: int) -> Vec4 { 158 | return {x, y, z, w} 159 | } 160 | 161 | @(LuaExport) 162 | vec4_tostring :: proc(v: Vec4) -> string { 163 | return fmt.tprintf("{{%d, %d, %d, %d}}", v.x, v.y, v.z, v.w) 164 | } 165 | 166 | // ... and so on 167 | ``` 168 | ```lua 169 | -- lua code 170 | local v = make_vec3(2, 3, 4) 171 | local v2 = v.xz -- v2 is of type Vec2 172 | v2.xy = v.zz 173 | ``` 174 | 175 | ### Calling Lua from Odin 176 | - `LuaImport` can be used to link a global function to odin. 177 | - The wrapper code generated is available under the name `_mani_`. It accepts the same parameters and return values 178 | - Calling the wrapper procedure will call the lua function. 179 | - `GlobalSymbol` is optional. If not specified, the wrapper code will just generate the Lua C API calls required to call any function on the top of the lua stack, with the parameters and results provided. This will require you to be familiar with how the C API works, so use with caution. 180 | ```odin 181 | @(LuaImport = { 182 | GlobalSymbol = "Update", 183 | }) 184 | update :: proc(dt: f64) -> (int, int) { 185 | val1, val2 := _mani_update(dt) 186 | // do some for processing 187 | return val1, val2 188 | } 189 | ``` 190 | ```lua 191 | -- lua 192 | function Update(dt) 193 | return 1, 2 194 | end 195 | ``` 196 | 197 | ## Getting Started 198 | - Download [odin-lua](https://github.com/DragosPopse/odin-lua) 199 | - For lua code completion, download [lua-language-server](https://github.com/sumneko/lua-language-server) 200 | - Place the contents of the shared directory in `%ODIN_ROOT%/shared` folder or a collection of your choice. If `ODIN_ROOT` is configured, you can just call `copy_to_shared.bat` 201 | - Build `generator` directory `odin build generator -out:build/mani.exe` 202 | - Create a `.json` file with the configuration of the generator. See [the example config](mani_config.json) for all the available options 203 | - The output executable accepts a json as parameter `mani config.json`. You can specify `--show-timings` for benchmarking the performance of the generation. 204 | - The output of `mani` is a source file `*.(config.odin_ext)` and a LSP file `*(config.lua_ext)` for each package encountered in the directory and it's subdirectories. 205 | - When initializing the lua state, call `mani.export_all` in order to hook all the code generated to your lua state 206 | ```odin 207 | import lua "shared:lua" 208 | import luaL "shared:luaL" 209 | import mani "shared:mani" 210 | main :: proc() { 211 | L := luaL.newstate() 212 | mani.export_all(L, mani.global_state) 213 | } 214 | ``` 215 | - Build your source with the `-ignore-unkown-attributes` flag 216 | 217 | ## Configuration 218 | - See [the example config](mani_config.json) for all the available options 219 | - `dir` refers to the odin source folder you want to generate for. `meta_dir` refers to the lua directory you want to put the LSP generation. 220 | - In order to prevent mani for looking to generate code for the already generated files, provide a longer extension for the `odin_ext` and `lua_ext`. For example: `.generated.odin`/`.lsp.lua` 221 | - `types` accepts a dictionary of arrays. Each key will represent a lua type, while the array value will be a list of odin types to be converted to lua. This is used by the code completion generation. The types exported by your application will already be correctly configured, so you only need to worry about primitives. Because the parser doesn't do any package resolution, `c.int` and `int` both need to be there and will be treated as distinct types. Aliasing any one of them in your code means that you need to add the alias in your configuration aswell. 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if not exist build mkdir build 4 | if not exist build\mani mkdir build\mani 5 | 6 | odin.exe build generator -show-timings -debug -out:build\mani\mani.exe 7 | REM ..\vendor\odin\odin.exe build src -target:js_wasm32 -target-features:+bulk-memory,+atomics -strict-style -show-timings -debug -out:build\web\game.wasm 8 | -------------------------------------------------------------------------------- /build/build.odin: -------------------------------------------------------------------------------- 1 | package build_mani 2 | 3 | import "core:fmt" 4 | import "../odin-build/build" 5 | import "core:os" 6 | import "core:strings" 7 | import "core:path/filepath" 8 | import "core:c/libc" 9 | 10 | Conf_Type :: enum { 11 | Debug, 12 | Release, 13 | Fast, 14 | Test, 15 | } 16 | 17 | Target :: struct { 18 | name: string, 19 | platform: build.Platform, 20 | conf: Conf_Type, 21 | } 22 | Project :: build.Project(Target) 23 | 24 | CURRENT_PLATFORM :: build.Platform{ODIN_OS, ODIN_ARCH} 25 | 26 | 27 | copy_dll :: proc(config: build.Config) -> int { 28 | out_dir := filepath.dir(config.out, context.temp_allocator) 29 | 30 | cmd := fmt.tprintf("xcopy /y /i \"odin-lua\\bin\\windows\\lua542.dll\" \"%s\\lua542.dll\"", ODIN_ROOT, out_dir) 31 | return build.syscall(cmd, true) 32 | } 33 | 34 | copy_assets :: proc(config: build.Config) -> int { 35 | 36 | return 0 37 | } 38 | 39 | add_targets :: proc(project: ^Project) { 40 | build.add_target(project, Target{"deb", CURRENT_PLATFORM, .Debug}) 41 | build.add_target(project, Target{"rel", CURRENT_PLATFORM, .Release}) 42 | build.add_target(project, Target{"fast", CURRENT_PLATFORM, .Fast}) 43 | build.add_target(project, Target{"test", CURRENT_PLATFORM, .Test}) 44 | } 45 | 46 | 47 | configure_target :: proc(project: Project, target: Target) -> (config: build.Config) { 48 | config = build.config_make() 49 | 50 | config.platform = target.platform 51 | config.name = target.name 52 | exe_ext, exe_name := "", "" 53 | 54 | if target.platform.os == .Windows { 55 | exe_ext = ".exe" 56 | } 57 | if target.conf == .Test { 58 | exe_name = "test" 59 | config.src = "./test" 60 | } else { 61 | exe_name = "manigen" 62 | config.src = "./manigen2" 63 | } 64 | 65 | switch target.conf { 66 | case .Debug: { 67 | config.flags += {.Debug} 68 | config.optimization = .None 69 | } 70 | 71 | case .Release: { 72 | config.optimization = .Speed 73 | } 74 | 75 | case .Fast: { 76 | config.optimization = .Speed 77 | config.flags += {.Disable_Assert, .No_Bounds_Check} 78 | } 79 | 80 | case .Test: { 81 | if target.platform.os == .Windows { 82 | build.add_post_build_command(&config, "copy-dll", copy_dll) 83 | } 84 | config.optimization = .None 85 | config.flags += {.Debug, .Ignore_Unknown_Attributes} 86 | 87 | } 88 | } 89 | 90 | config.out = fmt.aprintf("out/%s/%s%s", target.name, exe_name, exe_ext) 91 | return 92 | } 93 | 94 | 95 | main :: proc() { 96 | project: build.Project(Target) 97 | project.configure_target_proc = configure_target 98 | options := build.build_options_make_from_args(os.args[1:]) 99 | add_targets(&project) 100 | build.build_project(project, options) 101 | } -------------------------------------------------------------------------------- /build_test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | xcopy test\lua542.dll build\test\lua542.dll /Y 4 | odin build test -debug -ignore-unknown-attributes -out:build\test\test.exe -------------------------------------------------------------------------------- /copy_to_shared.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | xcopy shared\mani %ODIN_ROOT%\shared\mani /R /Y -------------------------------------------------------------------------------- /mani/logging.odin: -------------------------------------------------------------------------------- 1 | package mani 2 | import "core:odin/tokenizer" 3 | import "core:odin/ast" 4 | import "core:runtime" 5 | import "core:log" 6 | import "core:os" 7 | import "core:strings" 8 | import "core:fmt" 9 | 10 | @(private = "file") 11 | LevelHeaders := [?]string{ 12 | 0..<10 = "[MANIGEN_DEBUG] -- ", 13 | 10..<20 = "[MANIGEN_INFO ] -- ", 14 | 20..<30 = "[MANIGEN_WARN ] -- ", 15 | 30..<40 = "[MANIGEN_ERROR] -- ", 16 | 40..<50 = "[MANIGEN_FATAL] -- ", 17 | } 18 | 19 | GeneratorLoggerData :: struct { 20 | node: ^ast.Node, 21 | token: string, 22 | } 23 | 24 | GeneratorLoggerDebugOptions :: log.Options { 25 | .Terminal_Color, 26 | .Level, 27 | .Short_File_Path, 28 | .Line, 29 | .Procedure, 30 | } 31 | 32 | GeneratorLoggerReleaseOptions :: log.Options { 33 | .Terminal_Color, 34 | .Level, 35 | } 36 | 37 | create_generator_logger :: proc(lowest := log.Level.Debug) -> log.Logger { 38 | data := new(GeneratorLoggerData) 39 | opts := GeneratorLoggerDebugOptions when ODIN_DEBUG else GeneratorLoggerReleaseOptions 40 | return log.Logger { 41 | generator_logger_proc, 42 | data, 43 | lowest, 44 | opts, 45 | } 46 | } 47 | 48 | destroy_generator_logger :: proc(logger: log.Logger) { 49 | free(logger.data) 50 | } 51 | 52 | @(deferred_out = temp_logger_token_exit) 53 | temp_logger_token :: proc(logger_data: rawptr, node: ^ast.Node, token: string) -> ^GeneratorLoggerData { 54 | data := cast(^GeneratorLoggerData)logger_data 55 | data.node = node 56 | data.token = token 57 | return data 58 | } 59 | 60 | temp_logger_token_exit :: proc(data: ^GeneratorLoggerData) { 61 | data.node = nil 62 | } 63 | 64 | generator_logger_proc :: proc(logger_data: rawptr, level: log.Level, text: string, options: log.Options, location := #caller_location) { 65 | h := os.stdout if level < log.Level.Error else os.stderr 66 | data := cast(^GeneratorLoggerData)logger_data 67 | 68 | 69 | backing: [1024]byte 70 | buf := strings.builder_from_bytes(backing[:]) 71 | 72 | do_generator_header(options, level, data, &buf) 73 | log.do_location_header(options, &buf, location) 74 | 75 | fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) 76 | } 77 | 78 | @(private = "file") 79 | do_generator_header :: proc(opts: log.Options, level: log.Level, data: ^GeneratorLoggerData, str: ^strings.Builder) { 80 | 81 | RESET :: "\x1b[0m" 82 | RED :: "\x1b[31m" 83 | YELLOW :: "\x1b[33m" 84 | DARK_GREY :: "\x1b[90m" 85 | 86 | col := RESET 87 | switch level { 88 | case .Debug: col = DARK_GREY 89 | case .Info: col = RESET 90 | case .Warning: col = YELLOW 91 | case .Error, .Fatal: col = RED 92 | } 93 | 94 | if .Level in opts { 95 | if .Terminal_Color in opts { 96 | fmt.sbprint(str, col) 97 | } 98 | fmt.sbprint(str, LevelHeaders[level]) 99 | if .Terminal_Color in opts { 100 | fmt.sbprint(str, RESET) 101 | } 102 | } 103 | 104 | if data.node != nil { 105 | fmt.sbprintf(str, "[%s(%d): `%s`]", data.node.pos.file, data.node.pos.line, data.token) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /mani/lua.odin: -------------------------------------------------------------------------------- 1 | package mani 2 | 3 | // This "shared" thing not really nice. Maybe make odin-lua a submodule of mani 4 | // That way we can have "mani:mani" "mani:lua" "mani:luaL" 5 | import lua "shared:lua" 6 | import luaL "shared:luaL" 7 | 8 | import intr "core:intrinsics" 9 | import refl "core:reflect" 10 | import rt "core:runtime" 11 | import "core:fmt" 12 | import "core:strings" 13 | import "core:c" 14 | import "core:mem" 15 | 16 | assert_contextless :: proc "contextless" (condition: bool, message := "", loc := #caller_location) { 17 | if !condition { 18 | @(cold) 19 | internal :: proc "contextless"(message: string, loc: rt.Source_Code_Location) { 20 | rt.print_caller_location(loc) 21 | rt.print_string(" ") 22 | rt.print_string("runtime assertion") 23 | if len(message) > 0 { 24 | rt.print_string(": ") 25 | rt.print_string(message) 26 | } 27 | rt.print_byte('\n') 28 | } 29 | internal(message, loc) 30 | } 31 | } 32 | 33 | push_value :: proc "contextless"(L: ^lua.State, val: $T) { 34 | //#assert(!intr.type_is_pointer(T), "Pointers are not supported in push_value") 35 | when intr.type_is_integer(T) { 36 | lua.pushinteger(L, cast(lua.Integer)val) // Note(Dragos): Should this be casted implicitly? I think not 37 | } else when intr.type_is_float(T) { 38 | lua.pushnumber(L, cast(lua.Number)val) 39 | } else when intr.type_is_boolean(T) { 40 | lua.pushboolean(L, cast(c.bool)val) 41 | } else when T == cstring { 42 | lua.pushcstring(L, val) 43 | } else when T == string { 44 | lua.pushstring(L, val) 45 | } else when intr.type_is_proc(T) { 46 | lua.pushcfunction(L, val) 47 | } else when intr.type_is_struct(T) || intr.type_is_pointer(T) || intr.type_is_array(T) { 48 | metatableStr, found := global_state.udata_metatable_mapping[T] 49 | assert_contextless(found, "Metatable not found for type. Did you mark it with @(LuaExport)?") 50 | udata := transmute(^T)lua.newuserdata(L, size_of(T)) 51 | udata^ = val 52 | luaL.getmetatable(L, metatableStr) 53 | lua.setmetatable(L, -2) 54 | } else { 55 | #assert(false, "mani.push_value: Type not supported") 56 | } 57 | } 58 | 59 | to_value :: proc "contextless"(L: ^lua.State, #any_int stack_pos: int, val: ^$T) { 60 | when intr.type_is_pointer(type_of(val^)) { 61 | Base :: type_of(val^^) 62 | Ptr :: type_of(val^) 63 | } else { 64 | Base :: type_of(val^) 65 | Ptr :: type_of(val) 66 | } 67 | #assert(!intr.type_is_pointer(Base), "Pointer to pointer not allowed in to_value") 68 | 69 | when intr.type_is_integer(Base) { 70 | val^ = cast(Base)luaL.checkinteger(L, cast(i32)stack_pos) 71 | } else when intr.type_is_float(Base) { 72 | val^ = cast(Base)luaL.checknumber(L, cast(i32)stack_pos) 73 | } else when intr.type_is_boolean(Base) { 74 | val^ = cast(Base)luaL.checkboolean(L, cast(i32)stack_pos) 75 | } else when Base == cstring { 76 | str := luaL.checkstring(L, cast(i32)stack_pos) 77 | raw := transmute(mem.Raw_String)str 78 | val^ = cstring(raw.data) 79 | } else when Base == string { 80 | val^ = luaL.checkstring(L, cast(i32)stack_pos) 81 | } else { 82 | fmeta, hasFulldata := global_state.udata_metatable_mapping[Base] 83 | lmeta, hasLightdata := global_state.udata_metatable_mapping[Ptr] 84 | assert_contextless(hasFulldata || hasLightdata, "Metatable not found for type") 85 | 86 | rawdata: rawptr 87 | 88 | fdata := cast(Ptr)luaL.testudata(L, cast(i32)stack_pos, fmeta) if hasFulldata else nil 89 | ldata := cast(^Ptr)luaL.testudata(L, cast(i32)stack_pos, lmeta) if hasLightdata else nil 90 | when intr.type_is_pointer(type_of(val^)) { 91 | if fdata != nil { 92 | val^ = fdata 93 | } else { 94 | val^ = ldata^ 95 | } 96 | } else { 97 | if fdata != nil { 98 | val^ = fdata^ 99 | } else { 100 | val^ = ldata^^ 101 | } 102 | } 103 | } 104 | } 105 | 106 | 107 | set_global :: proc(L: ^lua.State, name: string, val: $T) { 108 | push_value(L, val) 109 | cname := strings.clone_to_cstring(name, context.temp_allocator) 110 | lua.setglobal(L, cname) 111 | } 112 | 113 | -------------------------------------------------------------------------------- /mani/mani.odin: -------------------------------------------------------------------------------- 1 | package mani 2 | 3 | import lua "shared:lua" 4 | import luaL "shared:luaL" 5 | import strings "core:strings" 6 | import "core:c" 7 | import "core:fmt" 8 | import "core:runtime" 9 | 10 | LuaName :: distinct string 11 | OdinName :: distinct string 12 | ManiName :: distinct string 13 | 14 | MetatableData :: struct { 15 | name: cstring, 16 | odin_type: typeid, 17 | index: lua.CFunction, 18 | newindex: lua.CFunction, 19 | methods: map[cstring]lua.CFunction, 20 | } 21 | 22 | LuaExport :: struct { 23 | pkg: string, 24 | lua_name: LuaName, 25 | odin_name: OdinName, 26 | } 27 | 28 | 29 | ProcExport :: struct { 30 | using base: LuaExport, 31 | mani_name: ManiName, 32 | lua_proc: lua.CFunction, 33 | } 34 | 35 | // Note(Dragos): Test performance 36 | FieldSetProc :: #type proc(L: ^lua.State, s: rawptr, field: string) 37 | FieldGetProc :: #type proc(L: ^lua.State, s: rawptr, field: string) 38 | 39 | 40 | StructFieldExport :: struct { 41 | lua_name: LuaName, 42 | odin_name: OdinName, 43 | type: typeid, 44 | } 45 | 46 | StructExport :: struct { 47 | using base: LuaExport, 48 | type: typeid, 49 | //fields: map[LuaName]StructFieldExport, // Not needed for now 50 | light_meta: Maybe(MetatableData), 51 | full_meta: Maybe(MetatableData), 52 | methods: map[LuaName]lua.CFunction, 53 | } 54 | 55 | // TODO(Add lua state in here aswell) (then we can have a single init function instead of export_all) 56 | State :: struct { 57 | lua_state: ^lua.State, 58 | procs: map[OdinName]ProcExport, // Key: odin_name 59 | structs: map[typeid]StructExport, // Key: type 60 | udata_metatable_mapping: map[typeid]cstring, // Key: odin type; Value: lua name 61 | } 62 | 63 | global_state := State { 64 | procs = make(map[OdinName]ProcExport), 65 | structs = make(map[typeid]StructExport), 66 | udata_metatable_mapping = make(map[typeid]cstring), 67 | } 68 | 69 | default_context: proc "contextless" () -> runtime.Context = nil 70 | 71 | add_function :: proc(v: ProcExport) { 72 | using global_state 73 | procs[v.odin_name] = v 74 | } 75 | 76 | add_struct :: proc(s: StructExport) { 77 | using global_state 78 | structs[s.type] = s 79 | if light, ok := s.light_meta.?; ok { 80 | udata_metatable_mapping[light.odin_type] = light.name 81 | } 82 | 83 | if full, ok := s.full_meta.?; ok { 84 | udata_metatable_mapping[full.odin_type] = full.name 85 | } 86 | } 87 | 88 | 89 | init :: proc(L: ^lua.State, using state: ^State) { 90 | lua_state = L 91 | if default_context == nil { 92 | default_context = runtime.default_context 93 | } 94 | for key, val in structs { 95 | using val 96 | if light, ok := light_meta.?; ok { 97 | assert(light.index != nil && light.newindex != nil) 98 | luaL.newmetatable(L, light.name) 99 | lua.pushcfunction(L, light.index) 100 | lua.setfield(L, -2, "__index") 101 | lua.pushcfunction(L, light.newindex) 102 | lua.setfield(L, -2, "__newindex") 103 | 104 | 105 | if light.methods != nil { 106 | for name, method in light.methods { 107 | lua.pushcfunction(L, method) 108 | lua.setfield(L, -2, name) 109 | } 110 | } 111 | 112 | lua.pop(L, 1) 113 | } 114 | if full, ok := full_meta.?; ok { 115 | assert(full.index != nil && full.newindex != nil) 116 | luaL.newmetatable(L, full.name) 117 | lua.pushcfunction(L, full.index) 118 | lua.setfield(L, -2, "__index") 119 | lua.pushcfunction(L, full.newindex) 120 | lua.setfield(L, -2, "__newindex") 121 | 122 | if full.methods != nil { 123 | for name, method in full.methods { 124 | lua.pushcfunction(L, method) 125 | lua.setfield(L, -2, name) 126 | } 127 | } 128 | 129 | lua.pop(L, 1) 130 | } 131 | } 132 | for key, val in procs { 133 | lua.pushcfunction(L, val.lua_proc) 134 | cstr := strings.clone_to_cstring(cast(string)val.lua_name, context.temp_allocator) 135 | lua.setglobal(L, cstr) 136 | } 137 | 138 | 139 | } -------------------------------------------------------------------------------- /mani_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dir": "test", 3 | "meta_dir": "test", 4 | "odin_ext": ".mani.odin", 5 | "lua_ext": ".lsp.lua", 6 | "types": { 7 | "integer": [ 8 | "c.int", "int", "i32", "i64" 9 | ], 10 | 11 | "number": [ 12 | "c.float", "f32", "f64", "f16" 13 | ], 14 | 15 | "string": [ 16 | "string", "cstring" 17 | ], 18 | 19 | "boolean": [ 20 | "bool", "c.bool" 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /manigen/array_gen.odin: -------------------------------------------------------------------------------- 1 | package mani_generator 2 | 3 | import "core:fmt" 4 | import "core:strings" 5 | import codew "code_writer" 6 | 7 | write_lua_array_index :: proc(sb: ^strings.Builder, exports: FileExports, arr: ArrayExport) { 8 | using strings, fmt 9 | writer := codew.writer_make(sb) 10 | exportAttrib := arr.attribs["LuaExport"].(Attributes) 11 | luaName := exportAttrib["Name"].(String) or_else arr.name 12 | 13 | udataTypeAttrib := exportAttrib["Type"].(Attributes) 14 | allowLight := "Light" in udataTypeAttrib 15 | allowFull := "Full" in udataTypeAttrib 16 | UdataType :: enum { 17 | Light = 0, 18 | Full, 19 | } 20 | udataType := [UdataType]bool { 21 | .Light = allowLight, 22 | .Full = allowFull, 23 | } 24 | 25 | swizzleTypes := exportAttrib["SwizzleTypes"].(Attributes) 26 | allowedFields := cast(string)exportAttrib["Fields"].(Identifier) 27 | hasMethods := "Methods" in exportAttrib 28 | //0: Arr2 29 | //1: Arr3 30 | //2: Arr4 31 | arrayTypes: [3]ArrayExport 32 | for typename in swizzleTypes { 33 | type := exports.symbols[typename].(ArrayExport) 34 | arrayTypes[type.len - 2] = type 35 | } 36 | arrayTypes[arr.len - 2] = arr 37 | 38 | if allowFull { 39 | 40 | if hasMethods { 41 | sbprintf(sb, 42 | ` 43 | _mani_index_{0:s} :: proc "c" (L: ^lua.State) -> c.int {{ 44 | 45 | context = mani.default_context() 46 | udata: {0:s} 47 | mani.to_value(L, 1, &udata) 48 | // Note(Dragos): It should also accept indices 49 | key := lua.tostring(L, 2) 50 | if method, found := _mani_methods_{0:s}[key]; found {{ 51 | mani.push_value(L, method) 52 | return 1 53 | }} 54 | assert(len(key) <= 4, "Vectors can only be swizzled up to 4 elements") 55 | 56 | result: {1:s} // Highest possible array type 57 | for r, i in key {{ 58 | if idx := strings.index_rune("{3:s}", r); idx != -1 {{ 59 | arrIdx := idx %% {2:i} 60 | result[i] = udata[arrIdx] 61 | }} 62 | }} 63 | 64 | switch len(key) {{ 65 | case 1: {{ 66 | mani.push_value(L, result.x) 67 | }} 68 | 69 | case 2: {{ 70 | mani.push_value(L, result.xy) 71 | }} 72 | 73 | case 3: {{ 74 | mani.push_value(L, result.xyz) 75 | }} 76 | 77 | case 4: {{ 78 | mani.push_value(L, result) 79 | }} 80 | 81 | case: {{ 82 | lua.pushnil(L) 83 | }} 84 | }} 85 | 86 | return 1 87 | }} 88 | `, arr.name, arrayTypes[2].name, arr.len, allowedFields) 89 | } else { 90 | sbprintf(sb, 91 | ` 92 | _mani_index_{0:s} :: proc "c" (L: ^lua.State) -> c.int {{ 93 | 94 | context = mani.default_context() 95 | udata: {0:s} 96 | mani.to_value(L, 1, &udata) 97 | // Note(Dragos): It should also accept indices 98 | key := lua.tostring(L, 2) 99 | 100 | assert(len(key) <= 4, "Vectors can only be swizzled up to 4 elements") 101 | 102 | result: {1:s} // Highest possible array type 103 | for r, i in key {{ 104 | if idx := strings.index_rune("{3:s}", r); idx != -1 {{ 105 | arrIdx := idx %% {2:i} 106 | result[i] = udata[arrIdx] 107 | }} 108 | }} 109 | 110 | switch len(key) {{ 111 | case 1: {{ 112 | mani.push_value(L, result.x) 113 | }} 114 | 115 | case 2: {{ 116 | mani.push_value(L, result.xy) 117 | }} 118 | 119 | case 3: {{ 120 | mani.push_value(L, result.xyz) 121 | }} 122 | 123 | case 4: {{ 124 | mani.push_value(L, result) 125 | }} 126 | 127 | case: {{ 128 | lua.pushnil(L) 129 | }} 130 | }} 131 | 132 | return 1 133 | }} 134 | `, arr.name, arrayTypes[2].name, arr.len, allowedFields) 135 | } 136 | 137 | } 138 | 139 | if allowLight { 140 | if hasMethods { 141 | sbprintf(sb, 142 | ` 143 | _mani_index_{0:s}_ref :: proc "c" (L: ^lua.State) -> c.int {{ 144 | 145 | context = mani.default_context() 146 | udata: ^{0:s} 147 | mani.to_value(L, 1, &udata) 148 | // Note(Dragos): It should also accept indices 149 | key := lua.tostring(L, 2) 150 | if method, found := _mani_methods_{0:s}[key]; found {{ 151 | mani.push_value(L, method) 152 | return 1 153 | }} 154 | assert(len(key) <= 4, "Vectors can only be swizzled up to 4 elements") 155 | 156 | result: {1:s} // Highest possible array type 157 | for r, i in key {{ 158 | if idx := strings.index_rune("{3:s}", r); idx != -1 {{ 159 | arrIdx := idx %% {2:i} 160 | result[i] = udata[arrIdx] 161 | }} 162 | }} 163 | 164 | switch len(key) {{ 165 | case 1: {{ 166 | mani.push_value(L, result.x) 167 | }} 168 | 169 | case 2: {{ 170 | mani.push_value(L, result.xy) 171 | }} 172 | 173 | case 3: {{ 174 | mani.push_value(L, result.xyz) 175 | }} 176 | 177 | case 4: {{ 178 | mani.push_value(L, result) 179 | }} 180 | 181 | case: {{ 182 | lua.pushnil(L) 183 | }} 184 | }} 185 | 186 | return 1 187 | }} 188 | `, arr.name, arrayTypes[2].name, arr.len, allowedFields) 189 | } else { 190 | sbprintf(sb, 191 | ` 192 | _mani_index_{0:s}_ref :: proc "c" (L: ^lua.State) -> c.int {{ 193 | 194 | context = mani.default_context() 195 | udata: ^{0:s} 196 | mani.to_value(L, 1, &udata) 197 | // Note(Dragos): It should also accept indices 198 | key := lua.tostring(L, 2) 199 | 200 | assert(len(key) <= 4, "Vectors can only be swizzled up to 4 elements") 201 | 202 | result: {1:s} // Highest possible array type 203 | for r, i in key {{ 204 | if idx := strings.index_rune("{3:s}", r); idx != -1 {{ 205 | arrIdx := idx %% {2:i} 206 | result[i] = udata[arrIdx] 207 | }} 208 | }} 209 | 210 | switch len(key) {{ 211 | case 1: {{ 212 | mani.push_value(L, result.x) 213 | }} 214 | 215 | case 2: {{ 216 | mani.push_value(L, result.xy) 217 | }} 218 | 219 | case 3: {{ 220 | mani.push_value(L, result.xyz) 221 | }} 222 | 223 | case 4: {{ 224 | mani.push_value(L, result) 225 | }} 226 | 227 | case: {{ 228 | lua.pushnil(L) 229 | }} 230 | }} 231 | 232 | return 1 233 | }} 234 | `, arr.name, arrayTypes[2].name, arr.len, allowedFields) 235 | } 236 | } 237 | 238 | } 239 | 240 | write_lua_array_newindex :: proc(sb: ^strings.Builder, exports: FileExports, arr: ArrayExport) { 241 | using strings, fmt 242 | 243 | exportAttrib := arr.attribs["LuaExport"].(Attributes) 244 | luaName := exportAttrib["Name"].(String) or_else arr.name 245 | udataType := exportAttrib["Type"].(Attributes) 246 | allowLight := "Light" in udataType 247 | allowFull := "Full" in udataType 248 | swizzleTypes := exportAttrib["SwizzleTypes"].(Attributes) 249 | allowedFields := cast(string)exportAttrib["Fields"].(Identifier) 250 | 251 | //0: Arr2 252 | //1: Arr3 253 | //2: Arr4 254 | arrayTypes: [3]ArrayExport 255 | for typename in swizzleTypes { 256 | type := exports.symbols[typename].(ArrayExport) 257 | arrayTypes[type.len - 2] = type 258 | } 259 | arrayTypes[arr.len - 2] = arr 260 | 261 | if allowFull { 262 | sbprintf(sb, 263 | ` 264 | _mani_newindex_{0:s} :: proc "c" (L: ^lua.State) -> c.int {{ 265 | 266 | context = mani.default_context() 267 | udata: {0:s} 268 | mani.to_value(L, 1, &udata) 269 | // Note(Dragos): It should also accept indices 270 | key := lua.tostring(L, 2) 271 | assert(len(key) <= {1:d}, "Cannot assign more indices than the vector takes") 272 | 273 | switch len(key) {{ 274 | case 1: {{ 275 | val: {2:s} 276 | mani.to_value(L, 3, &val.x) 277 | 278 | if idx := strings.index_byte("{5:s}", key[0]); idx != -1 {{ 279 | arrIdx := idx %% {1:d} 280 | udata[arrIdx] = val.x 281 | }} 282 | } 283 | 284 | case 2: {{ 285 | val: {2:s} 286 | mani.to_value(L, 3, &val) 287 | for r, i in key {{ 288 | if idx := strings.index_rune("{5:s}", r); idx != -1 {{ 289 | arrIdx := idx %% {1:d} 290 | udata[arrIdx] = val[i] 291 | }} 292 | }} 293 | }} 294 | 295 | case 3: {{ 296 | val: {3:s} 297 | mani.to_value(L, 3, &val) 298 | for r, i in key {{ 299 | if idx := strings.index_rune("{5:s}", r); idx != -1 {{ 300 | arrIdx := idx %% {1:d} 301 | udata[arrIdx] = val[i] 302 | }} 303 | }} 304 | }} 305 | 306 | case 4: {{ 307 | val: {4:s} 308 | mani.to_value(L, 3, &val) 309 | for r, i in key {{ 310 | if idx := strings.index_rune("{5:s}", r); idx != -1 {{ 311 | arrIdx := idx %% {1:d} 312 | udata[arrIdx] = val[i] 313 | }} 314 | }} 315 | }} 316 | }} 317 | 318 | 319 | return 0 320 | }} 321 | `, arr.name, arr.len, arrayTypes[0].name, arrayTypes[1].name, arrayTypes[2].name, allowedFields) 322 | } 323 | 324 | if allowLight { 325 | sbprintf(sb, 326 | ` 327 | _mani_newindex_{0:s}_ref :: proc "c" (L: ^lua.State) -> c.int {{ 328 | 329 | context = mani.default_context() 330 | udata: ^{0:s} 331 | mani.to_value(L, 1, &udata) 332 | // Note(Dragos): It should also accept indices 333 | key := lua.tostring(L, 2) 334 | assert(len(key) <= {1:d}, "Cannot assign more indices than the vector takes") 335 | 336 | switch len(key) {{ 337 | case 1: {{ 338 | val: {2:s} 339 | mani.to_value(L, 3, &val.x) 340 | if idx := strings.index_byte("{5:s}", key[0]); idx != -1 {{ 341 | arrIdx := idx %% {1:d} 342 | udata[arrIdx] = val.x 343 | }} 344 | } 345 | 346 | case 2: {{ 347 | val: {2:s} 348 | mani.to_value(L, 3, &val) 349 | for r, i in key {{ 350 | if idx := strings.index_rune("{5:s}", r); idx != -1 {{ 351 | arrIdx := idx %% {1:d} 352 | udata[arrIdx] = val[i] 353 | }} 354 | }} 355 | }} 356 | 357 | case 3: {{ 358 | val: {3:s} 359 | mani.to_value(L, 3, &val) 360 | for r, i in key {{ 361 | if idx := strings.index_rune("{5:s}", r); idx != -1 {{ 362 | arrIdx := idx %% {1:d} 363 | udata[arrIdx] = val[i] 364 | }} 365 | }} 366 | }} 367 | 368 | case 4: {{ 369 | val: {4:s} 370 | mani.to_value(L, 3, &val) 371 | for r, i in key {{ 372 | if idx := strings.index_rune("{5:s}", r); idx != -1 {{ 373 | arrIdx := idx %% {1:d} 374 | udata[arrIdx] = val[i] 375 | }} 376 | }} 377 | }} 378 | }} 379 | 380 | 381 | return 0 382 | }} 383 | `, arr.name, arr.len, arrayTypes[0].name, arrayTypes[1].name, arrayTypes[2].name, allowedFields) 384 | } 385 | 386 | } 387 | 388 | 389 | write_lua_array_init :: proc(sb: ^strings.Builder, exports: FileExports, arr: ArrayExport) { 390 | using strings, fmt 391 | 392 | exportAttribs := arr.attribs[LUAEXPORT_STR].(Attributes) 393 | udataType := exportAttribs["Type"].(Attributes) 394 | allowLight := "Light" in udataType 395 | allowFull := "Full" in udataType 396 | 397 | luaName := exportAttribs["Name"].(String) or_else arr.name 398 | 399 | 400 | write_string(sb, "@(init)\n") 401 | write_string(sb, "_mani_init_") 402 | write_string(sb, arr.name) 403 | write_string(sb, " :: proc() {\n ") 404 | 405 | write_string(sb, "expStruct: mani.StructExport") 406 | write_string(sb, "\n ") 407 | write_string(sb, "expStruct.pkg = ") 408 | write_rune(sb, '"') 409 | write_string(sb, exports.symbols_package) 410 | write_rune(sb, '"') 411 | write_string(sb, "\n ") 412 | 413 | write_string(sb, "expStruct.odin_name = ") 414 | write_rune(sb, '"') 415 | write_string(sb, arr.name) 416 | write_rune(sb, '"') 417 | write_string(sb, "\n ") 418 | 419 | write_string(sb, "expStruct.lua_name = ") 420 | write_rune(sb, '"') 421 | write_string(sb, luaName) 422 | write_rune(sb, '"') 423 | write_string(sb, "\n ") 424 | 425 | write_string(sb, "expStruct.type = ") 426 | write_string(sb, arr.name) 427 | write_string(sb, "\n ") 428 | 429 | if methodsAttrib, found := exportAttribs["Methods"]; found { 430 | write_string(sb, "expStruct.methods = make(map[mani.LuaName]lua.CFunction)") 431 | write_string(sb, "\n ") 432 | methods := methodsAttrib.(Attributes) 433 | for odinName, attribVal in methods { 434 | luaName: string 435 | if name, ok := attribVal.(String); ok { 436 | luaName = name 437 | } else { 438 | luaName = odinName 439 | } 440 | fullName := strings.concatenate({"_mani_", odinName}, context.temp_allocator) 441 | write_string(sb, "expStruct.methods[") 442 | write_rune(sb, '"') 443 | write_string(sb, luaName) 444 | write_rune(sb, '"') 445 | write_string(sb, "] = ") 446 | write_string(sb, fullName) 447 | write_string(sb, "\n ") 448 | } 449 | 450 | } 451 | 452 | if allowLight { 453 | write_string(sb, "\n ") 454 | write_string(sb, "refMeta: mani.MetatableData\n ") 455 | write_string(sb, "refMeta.name = ") 456 | write_rune(sb, '"') 457 | write_string(sb, arr.name) 458 | write_string(sb, "_ref") 459 | write_rune(sb, '"') 460 | write_string(sb, "\n ") 461 | 462 | write_string(sb, "refMeta.odin_type = ") 463 | write_rune(sb, '^') 464 | write_string(sb, arr.name) 465 | write_string(sb, "\n ") 466 | 467 | write_string(sb, "refMeta.index = ") 468 | write_string(sb, "_mani_index_") 469 | write_string(sb, arr.name) 470 | write_string(sb, "_ref") 471 | write_string(sb, "\n ") 472 | 473 | write_string(sb, "refMeta.newindex = ") 474 | write_string(sb, "_mani_newindex_") 475 | write_string(sb, arr.name) 476 | write_string(sb, "_ref") 477 | write_string(sb, "\n ") 478 | 479 | if metaAttrib, found := exportAttribs["Metamethods"]; found { 480 | write_string(sb, "refMeta.methods = make(map[cstring]lua.CFunction)") 481 | write_string(sb, "\n ") 482 | methods := metaAttrib.(Attributes) 483 | for name, val in methods { 484 | odinProc := val.(Identifier) 485 | fmt.sbprintf(sb, "refMeta.methods[\"%s\"] = _mani_%s", name, cast(String)odinProc) 486 | write_string(sb, "\n ") 487 | } 488 | } 489 | 490 | 491 | write_string(sb, "expStruct.light_meta = refMeta") 492 | write_string(sb, "\n ") 493 | } 494 | 495 | if allowFull { 496 | write_string(sb, "\n ") 497 | write_string(sb, "copyMeta: mani.MetatableData\n ") 498 | write_string(sb, "copyMeta.name = ") 499 | write_rune(sb, '"') 500 | write_string(sb, arr.name) 501 | write_rune(sb, '"') 502 | write_string(sb, "\n ") 503 | 504 | write_string(sb, "copyMeta.odin_type = ") 505 | write_string(sb, arr.name) 506 | write_string(sb, "\n ") 507 | 508 | write_string(sb, "copyMeta.index = ") 509 | write_string(sb, "_mani_index_") 510 | write_string(sb, arr.name) 511 | write_string(sb, "\n ") 512 | 513 | write_string(sb, "copyMeta.newindex = ") 514 | write_string(sb, "_mani_newindex_") 515 | write_string(sb, arr.name) 516 | write_string(sb, "\n ") 517 | 518 | if metaAttrib, found := exportAttribs["Metamethods"]; found { 519 | write_string(sb, "copyMeta.methods = make(map[cstring]lua.CFunction)") 520 | write_string(sb, "\n ") 521 | methods := metaAttrib.(Attributes) 522 | for name, val in methods { 523 | odinProc := val.(Identifier) 524 | fmt.sbprintf(sb, "copyMeta.methods[\"%s\"] = _mani_%s", name, cast(String)odinProc) 525 | write_string(sb, "\n ") 526 | } 527 | } 528 | 529 | write_string(sb, "expStruct.full_meta = copyMeta") 530 | write_string(sb, "\n ") 531 | write_string(sb, "\n ") 532 | 533 | } 534 | 535 | 536 | 537 | write_string(sb, "mani.add_struct(expStruct)") 538 | write_string(sb, "\n}\n\n") 539 | } 540 | 541 | int_pow :: proc(x: int, exp: uint) -> (result: int) { 542 | result = x if exp > 0 else 1 543 | for in 0.. (result: OdinWriter) { 14 | using result 15 | sb = builder 16 | curr_indent = 0 17 | indentation = indent 18 | return 19 | } 20 | 21 | writer_destroy :: proc(w: ^OdinWriter) { 22 | strings.builder_destroy(w.sb) 23 | } 24 | 25 | indent :: proc(using w: ^OdinWriter) { 26 | using strings 27 | for in 0.. ^OdinWriter { 55 | using strings, fmt 56 | indent(w) 57 | curr_indent += 1 58 | sbprintf(sb, "%s %s %s {{", identifier, decl_token, type) 59 | 60 | next_line(w) 61 | return w 62 | } 63 | 64 | @(deferred_out = end_block) 65 | begin_if :: proc(using w: ^OdinWriter, cond: string) -> ^OdinWriter{ 66 | using fmt 67 | indent(w) 68 | curr_indent += 1 69 | sbprintf(sb, "if %s {{", cond) 70 | next_line(w) 71 | 72 | return w 73 | } 74 | 75 | @(deferred_out = end_block) 76 | begin_else_if :: proc(using w: ^OdinWriter, cond: string) -> ^OdinWriter { 77 | using fmt 78 | curr_indent -= 1 79 | indent(w) 80 | next_line(w) 81 | sbprintf(sb, "}} else %s {{", cond) 82 | curr_indent += 1 83 | next_line(w) 84 | return w 85 | } 86 | 87 | @(deferred_out = end_block) 88 | begin_else :: proc(using w: ^OdinWriter) -> ^OdinWriter { 89 | using strings 90 | curr_indent -= 1 91 | next_line(w) 92 | write_string(sb, "} else {") 93 | curr_indent += 1 94 | next_line(w) 95 | return w 96 | } 97 | 98 | @(deferred_out = end_block) 99 | begin_for :: proc(using w: ^OdinWriter, cond: string) -> ^OdinWriter { 100 | using fmt 101 | indent(w) 102 | curr_indent += 1 103 | sbprintf(sb, "for %s {{", cond) 104 | next_line(w) 105 | return w 106 | } 107 | 108 | @(deferred_out = end_block) 109 | begin_switch :: proc(using w: ^OdinWriter, stmt: string) -> ^OdinWriter{ 110 | using fmt 111 | indent(w) 112 | curr_indent += 1 113 | sbprintf(sb, "switch %s {{", stmt) 114 | next_line(w) 115 | return w 116 | } 117 | 118 | @(deferred_out = end_block) 119 | begin_case :: proc(using w: ^OdinWriter, cond: string) -> ^OdinWriter{ 120 | using fmt 121 | indent(w) 122 | curr_indent += 1 123 | sbprintf(sb, "case %s: {{", cond) 124 | next_line(w) 125 | return w 126 | } -------------------------------------------------------------------------------- /manigen/generator.odin: -------------------------------------------------------------------------------- 1 | package mani_generator 2 | 3 | import strings "core:strings" 4 | import fmt "core:fmt" 5 | import filepath "core:path/filepath" 6 | import os "core:os" 7 | import json "core:encoding/json" 8 | 9 | DEFAULT_PROC_ATTRIBUTES := Attributes { 10 | 11 | } 12 | 13 | // Note(Dragos): This should change 14 | DEFAULT_STRUCT_ATTRIBUTES := Attributes { 15 | "Type" = Attributes { 16 | "Full" = nil, 17 | "Light" = nil, 18 | }, 19 | } 20 | 21 | GeneratorConfig :: struct { 22 | input_directory: string, 23 | meta_directory: string, 24 | show_timings: bool, 25 | files: map[string]PackageFile, 26 | lua_types: map[string]string, // Key: odin type 27 | 28 | odin_ext: string, 29 | lua_ext: string, 30 | } 31 | 32 | 33 | PackageFile :: struct { 34 | builder: strings.Builder, 35 | filename: string, 36 | imports: map[string]FileImport, // Key: import package name; Value: import text 37 | 38 | // Lua LSP metadata 39 | lua_filename: string, 40 | lua_builder: strings.Builder, 41 | } 42 | 43 | package_file_make :: proc(path: string, luaPath: string) -> PackageFile { 44 | return PackageFile { 45 | builder = strings.builder_make(), 46 | filename = path, 47 | imports = make(map[string]FileImport), 48 | 49 | lua_builder = strings.builder_make(), 50 | lua_filename = luaPath, 51 | } 52 | } 53 | 54 | create_config_from_args :: proc() -> (result: GeneratorConfig) { 55 | result = GeneratorConfig{} 56 | for arg in os.args { 57 | if arg[0] == '-' { 58 | pair := strings.split(arg, ":", context.temp_allocator) 59 | switch pair[0] { 60 | case "-show-timings": { 61 | result.show_timings = true 62 | } 63 | } 64 | } else { 65 | result.input_directory = arg 66 | config_from_json(&result, arg) 67 | } 68 | } 69 | return 70 | } 71 | 72 | config_from_json :: proc(config: ^GeneratorConfig, file: string) { 73 | data, ok := os.read_entire_file(file, context.temp_allocator) 74 | if !ok { 75 | fmt.printf("Failed to read config file\n") 76 | return 77 | } 78 | str := strings.clone_from_bytes(data, context.temp_allocator) 79 | obj, err := json.parse_string(data = str, allocator = context.temp_allocator) 80 | if err != .None { 81 | return 82 | } 83 | 84 | root := obj.(json.Object) 85 | config.input_directory = strings.clone(root["dir"].(json.String)) 86 | config.meta_directory = strings.clone(root["meta_dir"].(json.String)) 87 | config.odin_ext = strings.clone(root["odin_ext"].(json.String) or_else "manigen.odin") 88 | config.lua_ext = strings.clone(root["lua_ext"].(json.String) or_else "lsp.lua") 89 | config.lua_types = make(map[string]string) 90 | types := root["types"].(json.Object) 91 | 92 | for luaType, val in types { 93 | odinTypes := val.(json.Array) 94 | for type in odinTypes { 95 | config.lua_types[strings.clone(type.(json.String))] = strings.clone(luaType) 96 | } 97 | } 98 | } 99 | 100 | 101 | config_package :: proc(config: ^GeneratorConfig, pkg: string, filename: string) { 102 | result, ok := &config.files[pkg] 103 | if !ok { 104 | using strings 105 | 106 | 107 | path := filepath.dir(filename, context.temp_allocator) 108 | 109 | name := filepath.stem(filename) 110 | filename := strings.concatenate({path, "/", pkg, config.odin_ext}) 111 | luaFilename := strings.concatenate({config.meta_directory, "/", pkg, config.lua_ext}) 112 | 113 | config.files[pkg] = package_file_make(filename, luaFilename) 114 | sb := &(&config.files[pkg]).builder 115 | file := &config.files[pkg] 116 | 117 | luaSb := &(&config.files[pkg]).lua_builder 118 | 119 | write_string(sb, "package ") 120 | write_string(sb, pkg) 121 | write_string(sb, "\n\n") 122 | 123 | // Add required imports 124 | 125 | file.imports["c"] = FileImport { 126 | name = "c", 127 | text = `import c "core:c"`, 128 | } 129 | file.imports["fmt"] = FileImport { 130 | name = "fmt", 131 | text = `import fmt "core:fmt"`, 132 | } 133 | file.imports["runtime"] = FileImport { 134 | name = "runtime", 135 | text = `import runtime "core:runtime"`, 136 | } 137 | file.imports["lua"] = FileImport { 138 | name = "lua", 139 | text = `import lua "shared:lua"`, 140 | } 141 | file.imports["luaL"] = FileImport { 142 | name = "luaL", 143 | text = `import luaL "shared:luaL"`, 144 | } 145 | file.imports["mani"] = FileImport { 146 | name = "mani", 147 | text = `import mani "shared:mani"`, 148 | } 149 | file.imports["strings"] = FileImport { 150 | name = "strings", 151 | text = `import strings "core:strings"`, 152 | } 153 | 154 | for _, imp in file.imports { 155 | write_string(sb, imp.text) 156 | write_string(sb, "\n") 157 | } 158 | write_string(sb, "\n") 159 | 160 | write_string(luaSb, "---@meta\n\n") 161 | } 162 | } 163 | 164 | 165 | 166 | 167 | 168 | generate_struct_lua_wrapper :: proc(config: ^GeneratorConfig, exports: FileExports, s: StructExport, filename: string) { 169 | using strings 170 | sb := &(&config.files[exports.symbols_package]).builder 171 | exportAttribs := s.attribs["LuaExport"].(Attributes) 172 | if methods, found := exportAttribs["Methods"].(Attributes); found { 173 | generate_methods_mapping(config, exports, methods, s.name) 174 | } 175 | write_lua_index(sb, exports, s) 176 | write_lua_newindex(sb, exports, s) 177 | write_lua_struct_init(sb, exports, s) 178 | } 179 | 180 | generate_array_lua_wrapper :: proc(config: ^GeneratorConfig, exports: FileExports, arr: ArrayExport, filename: string) { 181 | using strings 182 | sb := &(&config.files[exports.symbols_package]).builder 183 | 184 | exportAttribs := arr.attribs["LuaExport"].(Attributes) 185 | if methods, found := exportAttribs["Methods"].(Attributes); found { 186 | generate_methods_mapping(config, exports, methods, arr.name) 187 | } 188 | 189 | write_lua_array_index(sb, exports, arr) 190 | write_lua_array_newindex(sb, exports, arr) 191 | write_lua_array_init(sb, exports, arr) 192 | } 193 | 194 | generate_methods_mapping :: proc(config: ^GeneratorConfig, exports: FileExports, methods: Attributes, name: string) { 195 | using strings, fmt 196 | sb := &(&config.files[exports.symbols_package]).builder 197 | 198 | write_string(sb, `@(private = "file")`) 199 | write_rune(sb, '\n') 200 | write_string(sb, "_mani_methods_") 201 | write_string(sb, name) 202 | write_string(sb, " := map[string]lua.CFunction {\n") 203 | 204 | for odinName, v in methods { 205 | luaName := v.(String) 206 | sbprintf(sb, " \"%s\" = _mani_%s,\n", luaName, odinName) 207 | } 208 | write_string(sb, "}\n") 209 | } 210 | 211 | 212 | add_import :: proc(file: ^PackageFile, import_statement: FileImport) { 213 | if import_statement.name not_in file.imports { 214 | using strings 215 | sb := &file.builder 216 | write_string(sb, import_statement.text) 217 | write_string(sb, "\n") 218 | file.imports[import_statement.name] = import_statement 219 | } 220 | } 221 | 222 | 223 | generate_lua_exports :: proc(config: ^GeneratorConfig, exports: FileExports) { 224 | using strings 225 | config_package(config, exports.symbols_package, exports.relpath) 226 | file := &config.files[exports.symbols_package] 227 | 228 | 229 | for _, imp in exports.imports { 230 | add_import(file, imp) 231 | } 232 | 233 | for k, exp in exports.symbols { 234 | 235 | switch x in exp { 236 | case ProcedureExport: { 237 | if "LuaExport" in x.attribs { 238 | generate_proc_lua_wrapper(config, exports, x, exports.relpath) 239 | write_proc_meta(config, exports, x) 240 | } else if "LuaImport" in x.attribs { 241 | generate_pcall_wrapper(config, exports, x, exports.relpath) 242 | } 243 | } 244 | 245 | case StructExport: { 246 | generate_struct_lua_wrapper(config, exports, x, exports.relpath) 247 | write_struct_meta(config, exports, x) 248 | } 249 | 250 | case ArrayExport: { 251 | generate_array_lua_wrapper(config, exports, x, exports.relpath) 252 | write_array_meta(config, exports, x) 253 | } 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /manigen/main.odin: -------------------------------------------------------------------------------- 1 | package mani_generator 2 | 3 | import "core:fmt" 4 | import os "core:os" 5 | import dlib "core:dynlib" 6 | import c "core:c" 7 | import libc "core:c/libc" 8 | import runtime "core:runtime" 9 | import strings "core:strings" 10 | import intr "core:intrinsics" 11 | import filepath "core:path/filepath" 12 | 13 | 14 | import ast "core:odin/ast" 15 | import parser "core:odin/parser" 16 | import tokenizer "core:odin/tokenizer" 17 | import "core:mem" 18 | import "core:log" 19 | 20 | import "core:time" 21 | 22 | 23 | 24 | main :: proc() { 25 | using fmt 26 | 27 | stopwatch: time.Stopwatch 28 | time.stopwatch_start(&stopwatch) 29 | 30 | context.logger = log.create_console_logger(.Debug, log.Options{.Terminal_Color, .Level, .Line, .Procedure}) 31 | defer log.destroy_console_logger(context.logger) 32 | 33 | 34 | config := create_config_from_args() 35 | if !os.is_dir(config.input_directory) { 36 | fprintf(os.stderr, "Could not open input directory %s\n", config.input_directory) 37 | } 38 | 39 | dirQueue := make([dynamic]string) 40 | append(&dirQueue, config.input_directory) 41 | for len(dirQueue) > 0 { 42 | idx := len(dirQueue) - 1 43 | if dirfd, err := os.open(dirQueue[idx]); err == os.ERROR_NONE { 44 | defer os.close(dirfd) 45 | pop(&dirQueue) 46 | if files, err := os.read_dir(dirfd, 0); err == os.ERROR_NONE { 47 | for file in files { 48 | if os.is_dir(file.fullpath) { 49 | append(&dirQueue, file.fullpath) 50 | } else if filepath.long_ext(file.fullpath) == ".odin" { 51 | symbols := parse_symbols(file.fullpath) 52 | // Note(Dragos): I should generate a file per package. Have a map[package]file0 53 | generate_lua_exports(&config, symbols) 54 | } 55 | } 56 | } 57 | } 58 | else { 59 | pop(&dirQueue) 60 | } 61 | } 62 | delete(dirQueue) 63 | 64 | for pkg, file in &config.files { 65 | when os.OS != .Windows { fperms := 0o666 } 66 | when os.OS == .Windows { fperms := 0 } 67 | { 68 | sb := &file.builder 69 | str := strings.to_string(sb^) 70 | fd, err := os.open(file.filename, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, fperms) 71 | if err != os.ERROR_NONE { 72 | fmt.printf("Failed to open %s\n", file.lua_filename) 73 | } 74 | defer os.close(fd) 75 | fmt.printf("Writing to %s\n", file.filename) 76 | os.write_string(fd, str) 77 | } 78 | { 79 | sb := &file.lua_builder 80 | str := strings.to_string(sb^) 81 | fd, err := os.open(file.lua_filename, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, fperms) 82 | if err != os.ERROR_NONE { 83 | fmt.printf("Failed to open %s\n", file.lua_filename) 84 | } 85 | defer os.close(fd) 86 | fmt.printf("Writing to %s\n", file.lua_filename) 87 | os.write_string(fd, str) 88 | } 89 | } 90 | 91 | time.stopwatch_stop(&stopwatch) 92 | duration := time.stopwatch_duration(stopwatch) 93 | if config.show_timings { 94 | fmt.printf("Total Duration - %f ms\n", cast(f64)duration / cast(f64)time.Millisecond) 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /manigen/parser.odin: -------------------------------------------------------------------------------- 1 | package mani_generator 2 | 3 | import fmt "core:fmt" 4 | import os "core:os" 5 | import ast "core:odin/ast" 6 | import parser "core:odin/parser" 7 | import tokenizer "core:odin/tokenizer" 8 | import strings "core:strings" 9 | import filepath "core:path/filepath" 10 | import "core:log" 11 | import "../shared/mani" 12 | import "core:reflect" 13 | import "core:strconv" 14 | 15 | LUA_PROC_ATTRIBUTES := map[string]bool { 16 | "Name" = true, 17 | } 18 | 19 | LUA_STRUCT_ATTRIBUTES := map[string]typeid { 20 | "Name" = string, 21 | "AllowRef" = nil, 22 | "AllowCopy" = nil, 23 | } 24 | 25 | ALLOWED_PROPERTIES := map[typeid]map[string]map[string]bool { 26 | ^ast.Proc_Lit = { 27 | "LuaExport" = { 28 | "Name" = true, 29 | }, 30 | }, 31 | 32 | ^ast.Struct_Type = { 33 | "LuaExport" = { 34 | "Name" = true, 35 | "AllowRef" = true, 36 | "AllowCopy" = true, 37 | }, 38 | "LuaFields" = nil, 39 | }, 40 | } 41 | 42 | LUAEXPORT_STR :: "LuaExport" 43 | LUAFIELDS_STR :: "LuaFields" 44 | 45 | String :: string 46 | Identifier :: distinct String 47 | Int :: i64 48 | Float :: f64 49 | 50 | 51 | AttribVal :: union { 52 | // nil: empty attribute, like a flag, array element, etc 53 | String, 54 | Identifier, 55 | Int, 56 | Attributes, 57 | } 58 | 59 | Attributes :: distinct map[string]AttribVal 60 | 61 | 62 | 63 | NodeExport :: struct { 64 | attribs: Attributes, 65 | lua_docs: [dynamic]string, 66 | } 67 | 68 | Field :: struct { 69 | name: string, 70 | type: string, 71 | } 72 | 73 | StructExport :: struct { 74 | using base: NodeExport, 75 | name: string, 76 | fields: map[string]Field, // Key: odin_name 77 | } 78 | 79 | ArrayExport :: struct { 80 | using base: NodeExport, 81 | name: string, 82 | len: int, 83 | value_type: string, 84 | } 85 | 86 | ProcedureExport :: struct { 87 | using base: NodeExport, 88 | name: string, 89 | type: string, 90 | calling_convention: string, 91 | params: #soa [dynamic]Field, 92 | results: #soa [dynamic]Field, 93 | } 94 | 95 | SymbolExport :: union { 96 | ProcedureExport, 97 | StructExport, 98 | ArrayExport, 99 | } 100 | 101 | FileImport :: struct { 102 | name: string, 103 | text: string, 104 | } 105 | 106 | FileExports :: struct { 107 | symbols_package: string, 108 | relpath: string, 109 | symbols: map[string]SymbolExport, 110 | imports: map[string]FileImport, 111 | } 112 | 113 | file_exports_make :: proc(allocator := context.allocator) -> FileExports { 114 | result := FileExports{} 115 | result.symbols = make(map[string]SymbolExport, 128, allocator) 116 | result.imports = make(map[string]FileImport, 128, allocator) 117 | return result 118 | } 119 | 120 | file_exports_destroy :: proc(obj: ^FileExports) { 121 | // Note(Dragos): Taken from string builder, shouldn't it be reversed? 122 | delete(obj.symbols) 123 | clear(&obj.symbols) 124 | 125 | } 126 | 127 | parse_symbols :: proc(fileName: string) -> (symbol_exports: FileExports) { 128 | context.logger = mani.create_generator_logger(log.Level.Debug) 129 | defer mani.destroy_generator_logger(context.logger) 130 | 131 | data, ok := os.read_entire_file(fileName); 132 | if !ok { 133 | fmt.fprintf(os.stderr, "Error reading the file\n") 134 | } 135 | 136 | p := parser.Parser{} 137 | p.flags += {parser.Flag.Optional_Semicolons} 138 | p.err = proc(pos: tokenizer.Pos, format: string, args: ..any) { 139 | fmt.printf(format, args) 140 | fmt.printf("\n") 141 | } 142 | 143 | 144 | f := ast.File{ 145 | src = string(data), 146 | fullpath = fileName, 147 | } 148 | 149 | ok = parser.parse_file(&p, &f) 150 | 151 | 152 | if p.error_count > 0 { 153 | return 154 | } 155 | 156 | root := p.file 157 | 158 | commentMapping := make(map[int]^ast.Comment_Group) 159 | for comment in root.comments { 160 | commentMapping[comment.end.line] = comment 161 | } 162 | 163 | 164 | // Note(Dragos): memory leaks around everywhere 165 | symbol_exports = file_exports_make() 166 | abspath, succ := filepath.abs(".") 167 | err: filepath.Relative_Error 168 | symbol_exports.relpath, err = filepath.rel(abspath, fileName) 169 | symbol_exports.relpath, _ = filepath.to_slash(symbol_exports.relpath) 170 | symbol_exports.symbols_package = root.pkg_name 171 | 172 | for x, index in root.decls { 173 | 174 | #partial switch x in x.derived { 175 | case ^ast.Import_Decl: { 176 | importName: string 177 | importText := root.src[x.pos.offset : x.end.offset] 178 | if x.name.kind != .Invalid { 179 | // got a name 180 | importName = x.name.text 181 | } else { 182 | // Take the name from the path 183 | startPos := 0 184 | for c, i in x.fullpath { 185 | if c == ':' { 186 | startPos = i + 1 187 | break 188 | } 189 | } 190 | // Note(Dragos): Even more memory leaks I don't care 191 | split := strings.split(x.fullpath[startPos:], "/") 192 | importName = split[len(split) - 1] 193 | importName = strings.trim(importName, "\"") 194 | if importName not_in symbol_exports.imports { 195 | symbol_exports.imports[importName] = FileImport { 196 | name = importName, 197 | text = importText, 198 | } 199 | } 200 | 201 | } 202 | } 203 | } 204 | 205 | if decl, ok := x.derived.(^ast.Value_Decl); ok { 206 | if len(decl.attributes) < 1 do continue // No attributes here, move on 207 | 208 | #partial switch v in decl.values[0].derived { 209 | case ^ast.Proc_Lit: { 210 | exportProc, err := parse_proc(root, decl, v) 211 | if err == .Export { 212 | exportProc.lua_docs = parse_lua_annotations(root, decl, commentMapping) 213 | symbol_exports.symbols[exportProc.name] = exportProc 214 | } 215 | 216 | } 217 | 218 | case ^ast.Struct_Type: { 219 | exportStruct, err := parse_struct(root, decl, v) 220 | if err == .Export { 221 | exportStruct.lua_docs = parse_lua_annotations(root, decl, commentMapping) 222 | symbol_exports.symbols[exportStruct.name] = exportStruct 223 | } 224 | } 225 | 226 | case ^ast.Distinct_Type: { 227 | #partial switch x in v.derived { 228 | case ^ast.Array_Type: { 229 | exportArr, err := parse_array(root, decl, x) 230 | if err == .Export { 231 | exportArr.lua_docs = parse_lua_annotations(root, decl, commentMapping) 232 | symbol_exports.symbols[exportArr.name] = exportArr 233 | } 234 | } 235 | } 236 | } 237 | 238 | case ^ast.Array_Type: { 239 | exportArr, err := parse_array(root, decl, v) 240 | if err == .Export { 241 | exportArr.lua_docs = parse_lua_annotations(root, decl, commentMapping) 242 | symbol_exports.symbols[exportArr.name] = exportArr 243 | } 244 | } 245 | } 246 | 247 | 248 | } 249 | } 250 | 251 | return 252 | } 253 | 254 | 255 | 256 | AttribErr :: enum { 257 | Skip, 258 | Export, 259 | Error, 260 | } 261 | 262 | parse_lua_annotations :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, mapping: map[int]^ast.Comment_Group, allocator := context.allocator) -> (docs: [dynamic]string) { 263 | if len(value_decl.attributes) > 0 { 264 | firstLine := value_decl.attributes[0].pos.line 265 | if group, found := mapping[firstLine - 1]; found { 266 | docs = make([dynamic]string, allocator) 267 | text := root.src[group.pos.offset : group.end.offset] 268 | lines := strings.split_lines(text, context.temp_allocator) 269 | for line in lines { 270 | for c, i in line { 271 | if c == '@' { 272 | append(&docs, line[i:]) 273 | } 274 | } 275 | } 276 | } 277 | } 278 | return 279 | } 280 | 281 | validate_proc_attributes :: proc(proc_decl: ^ast.Proc_Lit, attribs: Attributes) -> (err: AttribErr, msg: Maybe(string)) { 282 | if LUAEXPORT_STR not_in attribs && "LuaImport" not_in attribs { 283 | return .Skip, nil 284 | } 285 | 286 | exportAttribs := attribs[LUAEXPORT_STR] 287 | 288 | 289 | return .Export, nil 290 | } 291 | 292 | validate_struct_attributes :: proc(struct_decl: ^ast.Struct_Type, attribs: Attributes) -> (err: AttribErr, msg: Maybe(string)) { 293 | if LUAEXPORT_STR not_in attribs { 294 | return .Skip, nil 295 | } 296 | 297 | exportAttribs := attribs[LUAEXPORT_STR] 298 | 299 | 300 | return .Export, nil 301 | } 302 | 303 | validate_array_attributes :: proc(arr_decl: ^ast.Array_Type, attribs: Attributes) -> (err: AttribErr, msg: Maybe(string)) { 304 | if LUAEXPORT_STR not_in attribs { 305 | return .Skip, nil 306 | } 307 | 308 | exportAttribs := attribs[LUAEXPORT_STR] 309 | 310 | 311 | return .Export, nil 312 | } 313 | 314 | get_attr_name :: proc(root: ^ast.File, elem: ^ast.Expr) -> (name: string) { 315 | #partial switch x in elem.derived { 316 | case ^ast.Field_Value: { 317 | attr := x.field.derived.(^ast.Ident) 318 | name = attr.name 319 | } 320 | 321 | case ^ast.Ident: { 322 | name = x.name 323 | } 324 | } 325 | return 326 | } 327 | 328 | parse_attributes :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl) -> (result: Attributes) { 329 | result = make(Attributes) 330 | for attr, i in value_decl.attributes { 331 | for x, j in attr.elems { 332 | name := get_attr_name(root, x) 333 | 334 | result[name] = parse_attrib_val(root, x) 335 | } 336 | 337 | } 338 | return 339 | } 340 | 341 | parse_attrib_object :: proc(root: ^ast.File, obj: ^ast.Comp_Lit) -> (result: Attributes) { 342 | result = make(Attributes) 343 | for elem, i in obj.elems { 344 | name := get_attr_name(root, elem) 345 | result[name] = parse_attrib_val(root, elem) 346 | } 347 | return 348 | } 349 | 350 | parse_attrib_val :: proc(root: ^ast.File, elem: ^ast.Expr) -> (result: AttribVal) { 351 | #partial switch x in elem.derived { 352 | case ^ast.Field_Value: { 353 | #partial switch v in x.value.derived { 354 | case ^ast.Basic_Lit: { 355 | result = strings.trim(v.tok.text, "\"") 356 | return 357 | } 358 | 359 | case ^ast.Ident: { 360 | value := root.src[v.pos.offset : v.end.offset] 361 | result = cast(Identifier)value 362 | } 363 | 364 | case ^ast.Comp_Lit: { 365 | result = parse_attrib_object(root, v) 366 | } 367 | } 368 | return 369 | } 370 | 371 | case ^ast.Ident: { 372 | //result = cast(Identifier)x.name 373 | //fmt.printf("We shouldn't be an identifier %s\n", x.name) 374 | return nil 375 | } 376 | 377 | case ^ast.Comp_Lit: { 378 | //result = parse_attrib_object(root, x) 379 | fmt.printf("We shouldn't be in comp literal %v\n", x) 380 | return 381 | } 382 | } 383 | return nil 384 | } 385 | 386 | parse_array :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, arr_decl: ^ast.Array_Type, allocator := context.allocator) -> (result: ArrayExport, err: AttribErr) { 387 | result.attribs = parse_attributes(root, value_decl) 388 | 389 | if err, msg := validate_array_attributes(arr_decl, result.attribs); err != .Export { 390 | return result, err 391 | } 392 | 393 | result.name = value_decl.names[0].derived.(^ast.Ident).name 394 | result.value_type = arr_decl.elem.derived.(^ast.Ident).name 395 | lenLit := arr_decl.len.derived.(^ast.Basic_Lit) 396 | lenStr := root.src[lenLit.pos.offset : lenLit.end.offset] 397 | result.len, _ = strconv.parse_int(lenStr) 398 | 399 | err = .Export 400 | return 401 | } 402 | 403 | parse_struct :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, struct_decl: ^ast.Struct_Type, allocator := context.allocator) -> (result: StructExport, err: AttribErr) { 404 | 405 | result.attribs = parse_attributes(root, value_decl) 406 | 407 | if err, msg := validate_struct_attributes(struct_decl, result.attribs); err != .Export { 408 | return result, err 409 | } 410 | 411 | result.name = value_decl.names[0].derived.(^ast.Ident).name 412 | result.fields = make(map[string]Field) 413 | 414 | for field in struct_decl.fields.list { 415 | 416 | fType: string 417 | #partial switch x in field.type.derived { 418 | case ^ast.Ident: { 419 | fType = x.name 420 | } 421 | case ^ast.Selector_Expr: { // What was this again? 422 | fType = root.src[x.pos.offset : x.end.offset] //godlike odin 423 | } 424 | case ^ast.Pointer_Type: { 425 | fType = root.src[x.pos.offset : x.end.offset] 426 | } 427 | } 428 | for name in field.names { 429 | fName := name.derived.(^ast.Ident).name 430 | result.fields[fName] = Field { 431 | name = fName, 432 | type = fType, 433 | } 434 | } 435 | 436 | } 437 | 438 | // Check if LuaFields match 439 | /*if LUAFIELDS_STR in result.properties { 440 | for fieldName, _ in result.properties[LUAFIELDS_STR] { 441 | if fieldName not_in result.fields { 442 | mani.temp_logger_token(context.logger.data, value_decl, fieldName) 443 | log.errorf("Found unknown field in LuaFields. Make sure they match the struct!") 444 | err = .UnknownProperty 445 | return 446 | } 447 | } 448 | }*/ 449 | 450 | err = .Export 451 | return 452 | } 453 | 454 | 455 | parse_proc :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, proc_lit: ^ast.Proc_Lit, allocator := context.allocator) -> (result: ProcedureExport, err: AttribErr) { 456 | 457 | //result.properties, err = parse_properties(root, value_decl) 458 | result.attribs = parse_attributes(root, value_decl) 459 | 460 | if err, msg := validate_proc_attributes(proc_lit, result.attribs); err != .Export { 461 | return result, err 462 | } 463 | 464 | v := proc_lit 465 | procType := v.type 466 | declName := value_decl.names[0].derived.(^ast.Ident).name // Note(Dragos): Does this work with 'a, b: int' ????? 467 | 468 | result.name = declName 469 | result.type = root.src[procType.pos.offset : procType.end.offset] 470 | switch conv in procType.calling_convention { 471 | case string: { 472 | result.calling_convention = strings.trim(conv, `"`) 473 | } 474 | 475 | case ast.Proc_Calling_Convention_Extra: { 476 | result.calling_convention = "c" //not fully correct 477 | } 478 | 479 | case: { // nil, default calling convention 480 | result.calling_convention = "odin" 481 | } 482 | } 483 | // Note(Dragos): these should be checked for 0 484 | 485 | 486 | result.params = make_soa(type_of(result.params)) 487 | result.results = make_soa(type_of(result.results)) 488 | // Get parameters 489 | if procType.params != nil { 490 | 491 | for param, i in procType.params.list { 492 | paramType: string 493 | #partial switch x in param.type.derived { 494 | case ^ast.Ident: { 495 | paramType = x.name 496 | } 497 | case ^ast.Selector_Expr: { 498 | paramType = root.src[x.pos.offset : x.end.offset] //godlike odin 499 | } 500 | case ^ast.Pointer_Type: { 501 | paramType = root.src[x.pos.offset : x.end.offset] 502 | } 503 | } 504 | 505 | for name in param.names { 506 | append_soa(&result.params, Field{ 507 | name = name.derived.(^ast.Ident).name, 508 | type = paramType, 509 | }) 510 | } 511 | } 512 | } 513 | 514 | // Get results 515 | if procType.results != nil { 516 | for rval, i in procType.results.list { 517 | resName: string 518 | resType: string 519 | #partial switch x in rval.type.derived { 520 | case ^ast.Ident: { 521 | resType = x.name 522 | if len(rval.names) != 0 { 523 | resName = rval.names[0].derived.(^ast.Ident).name 524 | } 525 | } 526 | case ^ast.Selector_Expr: { 527 | if len(rval.names) != 0 { 528 | resName = rval.names[0].derived.(^ast.Ident).name 529 | } 530 | resType = root.src[x.pos.offset : x.end.offset] //godlike odin 531 | } 532 | } 533 | if len(rval.names) == 0 || resName == resType { 534 | // Result name is not specified 535 | sb := strings.builder_make(context.temp_allocator) 536 | strings.write_string(&sb, "mani_result") 537 | strings.write_int(&sb, i) 538 | resName = strings.to_string(sb) 539 | } 540 | 541 | 542 | 543 | append_soa(&result.results, Field{ 544 | name = resName, 545 | type = resType, 546 | }) 547 | } 548 | } 549 | 550 | 551 | return result, .Export 552 | } 553 | 554 | 555 | get_attr_elem :: proc(root: ^ast.File, elem: ^ast.Expr) -> (name: string, value: string) { // value can be a map maybe 556 | #partial switch x in elem.derived { 557 | case ^ast.Field_Value: { 558 | attr := x.field.derived.(^ast.Ident) 559 | 560 | #partial switch v in x.value.derived { 561 | case ^ast.Basic_Lit: { 562 | value = strings.trim(v.tok.text, "\"") 563 | } 564 | 565 | case ^ast.Ident: { 566 | value = root.src[v.pos.offset : v.end.offset] 567 | } 568 | 569 | case ^ast.Comp_Lit: { 570 | 571 | } 572 | } 573 | name = attr.name 574 | } 575 | 576 | case ^ast.Ident: { 577 | name = x.name 578 | } 579 | } 580 | return 581 | } 582 | 583 | is_pointer_type :: #force_inline proc(token: string) -> bool { 584 | return token[0] == '^' 585 | } 586 | 587 | 588 | -------------------------------------------------------------------------------- /manigen/proc_gen.odin: -------------------------------------------------------------------------------- 1 | package mani_generator 2 | 3 | import "core:fmt" 4 | import "core:strings" 5 | 6 | import cw "code_writer" 7 | 8 | write_proc_meta :: proc(config: ^GeneratorConfig, exports: FileExports, fn: ProcedureExport, override_lua_name := "", start_param := 0) { 9 | using strings, fmt 10 | sb := &(&config.files[exports.symbols_package]).lua_builder 11 | 12 | for comment in fn.lua_docs { 13 | fmt.sbprintf(sb, "---%s\n", comment) 14 | } 15 | exportAttribs := fn.attribs[LUAEXPORT_STR].(Attributes) or_else DEFAULT_PROC_ATTRIBUTES 16 | luaName := exportAttribs["Name"].(String) if "Name" in exportAttribs else fn.name 17 | if override_lua_name != "" { 18 | luaName = override_lua_name 19 | } 20 | for param, i in fn.params[start_param:] { 21 | paramType := param.type[1:] if is_pointer_type(param.type) else param.type 22 | luaType := "any" // default unknown type 23 | if type, found := config.lua_types[paramType]; found { 24 | luaType = type 25 | } else { 26 | #partial switch type in exports.symbols[paramType] { 27 | case ArrayExport: { 28 | // Note(Dragos): Not the best. Will need some refactoring 29 | luaType = type.attribs["LuaExport"].(Attributes)["Name"].(String) or_else type.name 30 | 31 | } 32 | 33 | case StructExport: { 34 | luaType = type.attribs["LuaExport"].(Attributes)["Name"].(String) or_else type.name 35 | } 36 | } 37 | } 38 | fmt.sbprintf(sb, "---@param %s %s\n", param.name, luaType) 39 | } 40 | 41 | for result, i in fn.results { 42 | resultType := result.type[1:] if is_pointer_type(result.type) else result.type 43 | luaType := "any" // default unknown type 44 | if type, found := config.lua_types[resultType]; found { 45 | luaType = type 46 | } else { 47 | #partial switch type in exports.symbols[resultType] { 48 | case ArrayExport: { 49 | // Note(Dragos): Not the best. Will need some refactoring 50 | luaType = type.attribs["LuaExport"].(Attributes)["Name"].(String) or_else type.name 51 | 52 | } 53 | 54 | case StructExport: { 55 | luaType = type.attribs["LuaExport"].(Attributes)["Name"].(String) or_else type.name 56 | } 57 | } 58 | } 59 | if strings.has_prefix(result.name, "mani_") { 60 | fmt.sbprintf(sb, "---@return %s\n", luaType) 61 | } else { 62 | fmt.sbprintf(sb, "---@return %s %s\n", luaType, result.name) 63 | } 64 | 65 | } 66 | 67 | params := fn.params[start_param:] 68 | fmt.sbprintf(sb, "function %s(", luaName) 69 | for param, i in params { 70 | write_string(sb, param.name) 71 | if i != len(params) - 1 do write_string(sb, ", ") 72 | } 73 | 74 | write_string(sb, ") end\n\n") 75 | } 76 | 77 | generate_proc_lua_wrapper :: proc(config: ^GeneratorConfig, exports: FileExports, fn: ProcedureExport, filename: string) { 78 | using strings 79 | fn_name := strings.concatenate({"_mani_", fn.name}, context.temp_allocator) 80 | 81 | sb := &(&config.files[exports.symbols_package]).builder 82 | 83 | exportAttribs := fn.attribs[LUAEXPORT_STR].(Attributes) or_else DEFAULT_PROC_ATTRIBUTES 84 | luaName := exportAttribs["Name"].(String) if "Name" in exportAttribs else fn.name 85 | 86 | write_string(sb, fn_name) 87 | write_string(sb, " :: proc \"c\" (L: ^lua.State) -> c.int {\n ") 88 | if fn.calling_convention == "odin" { 89 | write_string(sb, "context = mani.default_context()\n\n ") 90 | } 91 | // Declare parameters 92 | for param in fn.params { 93 | write_string(sb, param.name) 94 | write_string(sb, ": ") 95 | write_string(sb, param.type) 96 | write_string(sb, "\n ") 97 | } 98 | 99 | write_string(sb, "\n ") 100 | 101 | //Get parameters from lua 102 | if fn.params != nil && len(fn.params) != 0 { 103 | for param, i in fn.params { 104 | write_string(sb, "mani.to_value(L, ") // Note(Dragos): This needs to be replaced 105 | write_int(sb, i + 1) 106 | write_string(sb, ", &") 107 | write_string(sb, param.name) 108 | write_string(sb, ")\n ") 109 | } 110 | } 111 | 112 | 113 | // Declare results 114 | if fn.results != nil && len(fn.results) != 0 { 115 | for result, i in fn.results { 116 | 117 | write_string(sb, result.name) 118 | 119 | if i < len(fn.results) - 1 { 120 | write_string(sb, ", ") 121 | } 122 | } 123 | write_string(sb, " := ") 124 | } 125 | write_string(sb, fn.name) 126 | write_string(sb, "(") 127 | // Pass parameters to odin function 128 | for param, i in fn.params { 129 | write_string(sb, param.name) 130 | if i < len(fn.params) - 1 { 131 | write_string(sb, ", ") 132 | } 133 | } 134 | write_string(sb, ")\n\n ") 135 | 136 | for result, i in fn.results { 137 | write_string(sb, "mani.push_value(L, ") 138 | write_string(sb, result.name) 139 | write_string(sb, ")\n ") 140 | } 141 | write_string(sb, "\n ") 142 | 143 | write_string(sb, "return ") 144 | write_int(sb, len(fn.results) if fn.results != nil else 0) 145 | write_string(sb, "\n") 146 | write_string(sb, "}\n") 147 | 148 | // Generate @init function to bind to mani 149 | 150 | write_string(sb, "\n") 151 | write_string(sb, "@(init)\n") 152 | write_string(sb, fn_name) 153 | write_string(sb, "_init :: proc() {\n ") 154 | write_string(sb, "fn: mani.ProcExport\n ") 155 | 156 | write_string(sb, "fn.pkg = ") 157 | write_rune(sb, '"') 158 | write_string(sb, exports.symbols_package) 159 | write_rune(sb, '"') 160 | write_string(sb, "\n ") 161 | 162 | write_string(sb, "fn.odin_name = ") 163 | write_rune(sb, '"') 164 | write_string(sb, fn.name) 165 | write_rune(sb, '"') 166 | write_string(sb, "\n ") 167 | 168 | write_string(sb, "fn.lua_name = ") 169 | write_rune(sb, '"') 170 | write_string(sb, luaName) 171 | write_rune(sb, '"') 172 | write_string(sb, "\n ") 173 | 174 | write_string(sb, "fn.mani_name = ") 175 | write_rune(sb, '"') 176 | write_string(sb, fn_name) 177 | write_rune(sb, '"') 178 | write_string(sb, "\n ") 179 | 180 | write_string(sb, "fn.lua_proc = ") 181 | write_string(sb, fn_name) 182 | write_string(sb, "\n ") 183 | 184 | write_string(sb, "mani.add_function(fn)\n") 185 | 186 | write_string(sb, "}\n\n") 187 | } 188 | 189 | generate_pcall_wrapper :: proc(config: ^GeneratorConfig, exports: FileExports, fn: ProcedureExport, filename: string) { 190 | using strings, fmt 191 | fn_name := strings.concatenate({"_mani_", fn.name}, context.temp_allocator) 192 | 193 | sb := &(&config.files[exports.symbols_package]).builder 194 | 195 | importAttribs := fn.attribs["LuaImport"].(Attributes) // I should or_else this. This is not entirely correct right now 196 | writer := cw.writer_make(sb, " ") 197 | mani_wrapper: { 198 | cw.begin_block_decl(&writer, fn_name, fn.type) 199 | cw.write(&writer, "L := mani.global_state.lua_state") 200 | 201 | if global, found := importAttribs["GlobalSymbol"].(String); found { 202 | cw.write(&writer, tprintf("lua.getglobal(L, \"%s\")", global)) 203 | } 204 | 205 | for param in fn.params { 206 | cw.write(&writer, tprintf("mani.push_value(L, %s%s)", "&" if is_pointer_type(param.type) else "", param.name)) 207 | } 208 | 209 | if_stmt: { 210 | cw.begin_if(&writer, fmt.tprintf("lua.pcall(L, %d, %d, 0) != lua.OK", len(fn.params), len(fn.results))) 211 | cw.write(&writer, tprintf("fmt.printf(\"Error calling function %s: %%s\", lua.tostring(L, -1))", fn_name)) 212 | } 213 | 214 | for result, i in fn.results { 215 | cw.write(&writer, tprintf("_result%d: %s", i, result.type)) 216 | cw.write(&writer, tprintf("mani.to_value(L, -%d, &_result%d)", i + 1, i)) 217 | } 218 | 219 | cw.write(&writer, tprintf("lua.pop(L, %d)", len(fn.results))) 220 | 221 | if len(fn.results) > 0 { 222 | cw.indent(&writer) 223 | write_string(sb, "return ") 224 | for result, i in fn.results { 225 | sbprintf(sb, "_result%d", i) 226 | if i < len(fn.results) - 1 do write_string(sb, ", ") 227 | } 228 | cw.next_line(&writer) 229 | } 230 | } 231 | } -------------------------------------------------------------------------------- /manigen/struct_gen.odin: -------------------------------------------------------------------------------- 1 | package mani_generator 2 | 3 | import "core:fmt" 4 | import "core:strings" 5 | 6 | write_lua_struct_init :: proc(sb: ^strings.Builder, exports: FileExports, s: StructExport) { 7 | using strings, fmt 8 | 9 | exportAttribs := s.attribs[LUAEXPORT_STR].(Attributes) or_else DEFAULT_STRUCT_ATTRIBUTES 10 | udataType := exportAttribs["Type"].(Attributes) 11 | allowLight := "Light" in udataType 12 | allowFull := "Full" in udataType 13 | 14 | luaName := exportAttribs["Name"].(String) or_else s.name 15 | 16 | 17 | write_string(sb, "@(init)\n") 18 | write_string(sb, "_mani_init_") 19 | write_string(sb, s.name) 20 | write_string(sb, " :: proc() {\n ") 21 | 22 | write_string(sb, "expStruct: mani.StructExport") 23 | write_string(sb, "\n ") 24 | write_string(sb, "expStruct.pkg = ") 25 | write_rune(sb, '"') 26 | write_string(sb, exports.symbols_package) 27 | write_rune(sb, '"') 28 | write_string(sb, "\n ") 29 | 30 | write_string(sb, "expStruct.odin_name = ") 31 | write_rune(sb, '"') 32 | write_string(sb, s.name) 33 | write_rune(sb, '"') 34 | write_string(sb, "\n ") 35 | 36 | write_string(sb, "expStruct.lua_name = ") 37 | write_rune(sb, '"') 38 | write_string(sb, luaName) 39 | write_rune(sb, '"') 40 | write_string(sb, "\n ") 41 | 42 | write_string(sb, "expStruct.type = ") 43 | write_string(sb, s.name) 44 | write_string(sb, "\n ") 45 | 46 | if methodsAttrib, found := exportAttribs["Methods"]; found { 47 | write_string(sb, "expStruct.methods = make(map[mani.LuaName]lua.CFunction)") 48 | write_string(sb, "\n ") 49 | methods := methodsAttrib.(Attributes) 50 | for odinName, attribVal in methods { 51 | luaName: string 52 | if name, ok := attribVal.(String); ok { 53 | luaName = name 54 | } else { 55 | luaName = odinName 56 | } 57 | fullName := strings.concatenate({"_mani_", odinName}, context.temp_allocator) 58 | write_string(sb, "expStruct.methods[") 59 | write_rune(sb, '"') 60 | write_string(sb, luaName) 61 | write_rune(sb, '"') 62 | write_string(sb, "] = ") 63 | write_string(sb, fullName) 64 | write_string(sb, "\n ") 65 | } 66 | 67 | } 68 | 69 | if allowLight { 70 | write_string(sb, "\n ") 71 | write_string(sb, "refMeta: mani.MetatableData\n ") 72 | write_string(sb, "refMeta.name = ") 73 | write_rune(sb, '"') 74 | write_string(sb, s.name) 75 | write_string(sb, "_ref") 76 | write_rune(sb, '"') 77 | write_string(sb, "\n ") 78 | 79 | write_string(sb, "refMeta.odin_type = ") 80 | write_rune(sb, '^') 81 | write_string(sb, s.name) 82 | write_string(sb, "\n ") 83 | 84 | write_string(sb, "refMeta.index = ") 85 | write_string(sb, "_mani_index_") 86 | write_string(sb, s.name) 87 | write_string(sb, "_ref") 88 | write_string(sb, "\n ") 89 | 90 | write_string(sb, "refMeta.newindex = ") 91 | write_string(sb, "_mani_newindex_") 92 | write_string(sb, s.name) 93 | write_string(sb, "_ref") 94 | write_string(sb, "\n ") 95 | 96 | if metaAttrib, found := exportAttribs["Metamethods"]; found { 97 | write_string(sb, "refMeta.methods = make(map[cstring]lua.CFunction)") 98 | write_string(sb, "\n ") 99 | methods := metaAttrib.(Attributes) 100 | for name, val in methods { 101 | odinProc := val.(Identifier) 102 | fmt.sbprintf(sb, "refMeta.methods[\"%s\"] = _mani_%s", name, cast(String)odinProc) 103 | write_string(sb, "\n ") 104 | } 105 | } 106 | 107 | 108 | write_string(sb, "expStruct.light_meta = refMeta") 109 | write_string(sb, "\n ") 110 | } 111 | 112 | if allowFull { 113 | write_string(sb, "\n ") 114 | write_string(sb, "copyMeta: mani.MetatableData\n ") 115 | write_string(sb, "copyMeta.name = ") 116 | write_rune(sb, '"') 117 | write_string(sb, s.name) 118 | write_rune(sb, '"') 119 | write_string(sb, "\n ") 120 | 121 | write_string(sb, "copyMeta.odin_type = ") 122 | write_string(sb, s.name) 123 | write_string(sb, "\n ") 124 | 125 | write_string(sb, "copyMeta.index = ") 126 | write_string(sb, "_mani_index_") 127 | write_string(sb, s.name) 128 | write_string(sb, "\n ") 129 | 130 | write_string(sb, "copyMeta.newindex = ") 131 | write_string(sb, "_mani_newindex_") 132 | write_string(sb, s.name) 133 | write_string(sb, "\n ") 134 | 135 | if metaAttrib, found := exportAttribs["Metamethods"]; found { 136 | write_string(sb, "copyMeta.methods = make(map[cstring]lua.CFunction)") 137 | write_string(sb, "\n ") 138 | methods := metaAttrib.(Attributes) 139 | for name, val in methods { 140 | odinProc := val.(Identifier) 141 | fmt.sbprintf(sb, "copyMeta.methods[\"%s\"] = _mani_%s", name, cast(String)odinProc) 142 | write_string(sb, "\n ") 143 | } 144 | } 145 | 146 | write_string(sb, "expStruct.full_meta = copyMeta") 147 | write_string(sb, "\n ") 148 | write_string(sb, "\n ") 149 | 150 | } 151 | 152 | 153 | 154 | write_string(sb, "mani.add_struct(expStruct)") 155 | write_string(sb, "\n}\n\n") 156 | } 157 | 158 | write_lua_newstruct :: proc(sb: ^strings.Builder, exports: FileExports, s: StructExport) { 159 | 160 | } 161 | 162 | write_lua_index :: proc(sb: ^strings.Builder, exports: FileExports, s: StructExport) { 163 | using strings 164 | exportAttribs := s.attribs[LUAEXPORT_STR].(Attributes) or_else DEFAULT_STRUCT_ATTRIBUTES 165 | luaFields := exportAttribs["Fields"].(Attributes) if "Fields" in exportAttribs else nil 166 | udataType := exportAttribs["Type"].(Attributes) 167 | allowLight := "Light" in udataType 168 | allowFull := "Full" in udataType 169 | hasMethods := "Methods" in exportAttribs 170 | 171 | 172 | if allowFull { 173 | if hasMethods { 174 | fmt.sbprintf(sb, 175 | ` 176 | _mani_index_{0:s} :: proc "c" (L: ^lua.State) -> c.int {{ 177 | context = mani.default_context() 178 | udata := transmute(^{1:s})luaL.checkudata(L, 1, "{0:s}") 179 | key := lua.tostring(L, 2) 180 | if method, found := _mani_methods_{0:s}[key]; found {{ 181 | mani.push_value(L, method) 182 | return 1 183 | }} 184 | switch key {{ 185 | case: {{ 186 | lua.pushnil(L) 187 | return 1 188 | }} 189 | `, s.name, s.name) 190 | } else { 191 | fmt.sbprintf(sb, 192 | ` 193 | _mani_index_{0:s} :: proc "c" (L: ^lua.State) -> c.int {{ 194 | context = mani.default_context() 195 | udata := transmute(^{1:s})luaL.checkudata(L, 1, "{0:s}") 196 | key := lua.tostring(L, 2) 197 | switch key {{ 198 | case: {{ 199 | lua.pushnil(L) 200 | return 1 201 | }} 202 | `, s.name, s.name) 203 | } 204 | 205 | if luaFields != nil { 206 | for k, field in s.fields { 207 | shouldExport := false 208 | name: string 209 | if luaField, ok := luaFields[field.name]; ok { 210 | shouldExport = true 211 | if luaName, ok := luaField.(String); ok { 212 | name = luaName 213 | } else { 214 | name = field.name 215 | } 216 | } else { 217 | shouldExport = false 218 | } 219 | 220 | 221 | if shouldExport { 222 | fmt.sbprintf(sb, 223 | ` 224 | case "{0:s}": {{ 225 | mani.push_value(L, udata.{1:s}) 226 | return 1 227 | }} 228 | `, name, field.name) 229 | } 230 | } 231 | } 232 | 233 | fmt.sbprintf(sb, 234 | ` 235 | }} 236 | return 1 237 | }} 238 | 239 | ` ) 240 | } 241 | 242 | if allowLight { 243 | if hasMethods { 244 | fmt.sbprintf(sb, 245 | ` 246 | _mani_index_{0:s}_ref :: proc "c" (L: ^lua.State) -> c.int {{ 247 | context = mani.default_context() 248 | udata := transmute(^{1:s})luaL.checkudata(L, 1, "{0:s}") 249 | key := lua.tostring(L, 2) 250 | if method, found := _mani_methods_{0:s}[key]; found {{ 251 | mani.push_value(L, method) 252 | return 1 253 | }} 254 | switch key {{ 255 | case: {{ 256 | lua.pushnil(L) 257 | return 1 258 | }} 259 | `, s.name, s.name) 260 | } else { 261 | fmt.sbprintf(sb, 262 | ` 263 | _mani_index_{0:s}_ref :: proc "c" (L: ^lua.State) -> c.int {{ 264 | context = mani.default_context() 265 | udata := transmute(^{1:s})luaL.checkudata(L, 1, "{0:s}") 266 | key := lua.tostring(L, 2) 267 | switch key {{ 268 | case: {{ 269 | lua.pushnil(L) 270 | return 1 271 | }} 272 | `, s.name, s.name) 273 | } 274 | 275 | if luaFields != nil { 276 | for k, field in s.fields { 277 | shouldExport := false 278 | name: string 279 | if luaField, ok := luaFields[field.name]; ok { 280 | shouldExport = true 281 | if luaName, ok := luaField.(String); ok { 282 | name = luaName 283 | } else { 284 | name = field.name 285 | } 286 | } else { 287 | shouldExport = false 288 | } 289 | 290 | 291 | if shouldExport { 292 | fmt.sbprintf(sb, 293 | ` 294 | case "{0:s}": {{ 295 | mani.push_value(L, udata^.{1:s}) 296 | return 1 297 | }} 298 | `, name, field.name) 299 | } 300 | } 301 | } 302 | 303 | fmt.sbprintf(sb, 304 | ` 305 | }} 306 | return 1 307 | }} 308 | 309 | ` ) 310 | } 311 | 312 | } 313 | 314 | write_lua_newindex :: proc(sb: ^strings.Builder, exports: FileExports, s: StructExport) { 315 | using strings 316 | exportAttribs := s.attribs[LUAEXPORT_STR].(Attributes) or_else DEFAULT_STRUCT_ATTRIBUTES 317 | luaFields := exportAttribs["Fields"].(Attributes) if "Fields" in exportAttribs else nil 318 | udataType := exportAttribs["Type"].(Attributes) 319 | allowLight := "Light" in udataType 320 | allowFull := "Full" in udataType 321 | 322 | 323 | if allowFull { 324 | fmt.sbprintf(sb, 325 | ` 326 | _mani_newindex_{0:s} :: proc "c" (L: ^lua.State) -> c.int {{ 327 | context = mani.default_context() 328 | udata := transmute(^{1:s})luaL.checkudata(L, 1, "{0:s}") 329 | key := lua.tostring(L, 2) 330 | switch key {{ 331 | case: {{ 332 | // Throw an error here 333 | return 0 334 | }} 335 | ` , s.name, s.name) 336 | if luaFields != nil { 337 | for k, field in s.fields { 338 | shouldExport := false 339 | name: string 340 | if luaField, ok := luaFields[field.name]; ok { 341 | shouldExport = true 342 | if luaName, ok := luaField.(String); ok { 343 | name = luaName 344 | } else { 345 | name = field.name 346 | } 347 | } else { 348 | shouldExport = false 349 | } 350 | 351 | 352 | if shouldExport { 353 | fmt.sbprintf(sb, 354 | ` 355 | case "{0:s}": {{ 356 | mani.to_value(L, 3, &udata.{1:s}) 357 | return 1 358 | }} 359 | `, name, field.name) 360 | } 361 | } 362 | } 363 | 364 | fmt.sbprintf(sb, 365 | ` 366 | }} 367 | return 0 368 | }} 369 | 370 | ` ) 371 | } 372 | 373 | if allowLight { 374 | fmt.sbprintf(sb, 375 | ` 376 | _mani_newindex_{0:s}_ref :: proc "c" (L: ^lua.State) -> c.int {{ 377 | context = mani.default_context() 378 | udata := transmute(^^{1:s})luaL.checkudata(L, 1, "{0:s}") 379 | key := lua.tostring(L, 2) 380 | switch key {{ 381 | case: {{ 382 | lua.pushnil(L) 383 | }} 384 | ` , s.name, s.name) 385 | if luaFields != nil { 386 | for k, field in s.fields { 387 | shouldExport := false 388 | name: string 389 | if luaField, ok := luaFields[field.name]; ok { 390 | shouldExport = true 391 | if luaName, ok := luaField.(String); ok { 392 | name = luaName 393 | } else { 394 | name = field.name 395 | } 396 | } else { 397 | shouldExport = false 398 | } 399 | 400 | 401 | if shouldExport { 402 | fmt.sbprintf(sb, 403 | ` 404 | case "{0:s}": {{ 405 | mani.to_value(L, 3, &udata^.{1:s}) 406 | return 1 407 | }} 408 | `, name, field.name) 409 | } 410 | } 411 | } 412 | 413 | fmt.sbprintf(sb, 414 | ` 415 | }} 416 | return 1 417 | }} 418 | 419 | ` ) 420 | } 421 | } 422 | 423 | write_struct_meta :: proc(config: ^GeneratorConfig, exports: FileExports, s: StructExport) { 424 | using strings 425 | sb := &(&config.files[exports.symbols_package]).lua_builder 426 | 427 | exportAttribs := s.attribs[LUAEXPORT_STR].(Attributes) or_else DEFAULT_PROC_ATTRIBUTES 428 | // This makes LuaExport.Name not enitrely usable, I should map struct names to lua names 429 | className := exportAttribs["Name"].(String) or_else s.name 430 | fmt.sbprintf(sb, "---@class %s\n", className) 431 | for comment in s.lua_docs { 432 | fmt.sbprintf(sb, "---%s\n", comment) 433 | } 434 | if fieldsAttrib, found := exportAttribs["Fields"].(Attributes); found { 435 | for k, field in s.fields { 436 | name: string 437 | luaType := "any" 438 | fieldType := field.type[1:] if is_pointer_type(field.type) else field.type 439 | if luaField, ok := fieldsAttrib[field.name]; ok { 440 | if luaName, ok := luaField.(String); ok { 441 | name = luaName 442 | } else { 443 | name = field.name 444 | } 445 | 446 | if type, found := config.lua_types[fieldType]; found { 447 | luaType = type 448 | } else { 449 | #partial switch type in exports.symbols[fieldType] { 450 | case ArrayExport: { 451 | // Note(Dragos): Not the best. Will need some refactoring 452 | luaType = type.attribs["LuaExport"].(Attributes)["Name"].(String) or_else type.name 453 | 454 | } 455 | 456 | case StructExport: { 457 | luaType = type.attribs["LuaExport"].(Attributes)["Name"].(String) or_else type.name 458 | } 459 | } 460 | } 461 | fmt.sbprintf(sb, "---@field %s %s\n", name, luaType) 462 | } 463 | } 464 | } 465 | fmt.sbprintf(sb, "%s = {{}}\n\n", className) 466 | 467 | if methodsAttrib, found := exportAttribs["Methods"]; found { 468 | methods := methodsAttrib.(Attributes) 469 | for odinProc, val in methods { 470 | methodName: string 471 | if name, found := val.(String); found { 472 | methodName = name 473 | } else { 474 | methodName = odinProc 475 | } 476 | 477 | 478 | procExport := exports.symbols[odinProc].(ProcedureExport) 479 | write_proc_meta(config, exports, procExport, fmt.tprintf("%s:%s", className, methodName), 1) 480 | 481 | } 482 | } 483 | } -------------------------------------------------------------------------------- /manigen2/attriparse/parser.odin: -------------------------------------------------------------------------------- 1 | package attriparse 2 | 3 | import ast "core:odin/ast" 4 | import parser "core:odin/parser" 5 | import tokenizer "core:odin/tokenizer" 6 | import "core:strings" 7 | import "core:fmt" 8 | import "core:strconv" 9 | import "core:path/filepath" 10 | 11 | BUILTIN_ATTRIBUTES := [?]string { 12 | "private", 13 | "require", 14 | "link_name", 15 | "link_prefix", 16 | "export", 17 | "linkage", 18 | "default_calling_convention", 19 | "link_section", 20 | "extra_linker_flags", 21 | "deferred_in", 22 | "deferred_out", 23 | "deferred_in_out", 24 | "deferred_none", 25 | "deprecated", 26 | "require_results", 27 | "warning", 28 | "disabled", 29 | "init", 30 | "cold", 31 | "optimization_mode", 32 | "static", 33 | "thread_local", 34 | "builtin", 35 | "objc_name", 36 | "objc_type", 37 | "objc_is_class_method", 38 | "require_target_feature", 39 | "enable_target_feature", 40 | } 41 | 42 | 43 | String :: string 44 | Identifier :: distinct String 45 | Int :: int 46 | Float :: f32 47 | 48 | Attribute_Value :: union { 49 | // nil: empty attribute, like a flag, array element, etc 50 | String, 51 | Identifier, 52 | Int, 53 | Attribute_Map, 54 | } 55 | 56 | Field :: struct { 57 | name: string, 58 | type: string, 59 | } 60 | 61 | Struct :: struct { 62 | fields: [dynamic]Field, 63 | } 64 | 65 | Proc :: struct { 66 | type: string, 67 | calling_convention: string, 68 | params: [dynamic]Field, 69 | results: [dynamic]Field, 70 | } 71 | 72 | Array :: struct { 73 | len: int, 74 | value_type: string, 75 | } 76 | 77 | External_Symbol :: struct { 78 | pkg_path: string, 79 | ident: string, 80 | pkg: string, // lazily evaluated 81 | } 82 | 83 | 84 | Symbol :: struct { 85 | attribs: Attribute_Map, 86 | is_distinct: bool, 87 | var: union { 88 | Struct, 89 | Proc, 90 | Array, 91 | External_Symbol, // This might not be needed 92 | }, 93 | } 94 | 95 | 96 | Attribute_Map :: distinct map[string]Attribute_Value 97 | 98 | Program :: struct { 99 | root_pkg: ^Package, 100 | parser: parser.Parser, 101 | collections: map[string]string, 102 | pkgs: map[string]^Package, 103 | symbols: map[string]Symbol, 104 | } 105 | 106 | _parse_package :: proc(p: ^Program, path: string) -> (pkg: ^ast.Package) { 107 | pkg_collected: bool 108 | path := make_import_path() 109 | pkg, pkg_collected = parser.collect_package(path) 110 | assert(pkg_collected, "Package collection failed.") 111 | parser.parse_package(pkg, &p.parser) 112 | for filename, file in pkg.files { 113 | root := file 114 | for decl, index in root.decls { 115 | #partial switch derived in decl.derived { 116 | case ^ast.Import_Decl: { 117 | import_name: string 118 | import_text := root.src[derived.pos.offset : derived.end.offset] 119 | if derived.name.kind != .Invalid { 120 | // got a name 121 | import_name = derived.name.text 122 | } else { 123 | // Take the name from the path 124 | start_pos := 0 125 | for c, i in derived.fullpath { 126 | if c == ':' { 127 | start_pos = i + 1 128 | break 129 | } 130 | } 131 | // Use the last folder 132 | split := strings.split(derived.fullpath[start_pos:], "/", context.temp_allocator) 133 | import_name = split[len(split) - 1] 134 | import_name = strings.trim(import_name, "\"") 135 | } 136 | p.imports_alias_path_mapping[import_name] = derived.fullpath 137 | } 138 | } 139 | if value_decl, is_value_decl := decl.derived.(^ast.Value_Decl); is_value_decl { 140 | if len(value_decl.attributes) < 1 do continue 141 | 142 | #partial switch value in value_decl.values[0].derived { 143 | case ^ast.Distinct_Type: { // This needs to be sorted out 144 | 145 | } 146 | 147 | case ^ast.Proc_Lit: { 148 | symbol, name := parse_proc(root, value_decl, value) 149 | p.symbols[name] = symbol 150 | } 151 | 152 | case ^ast.Struct_Type: { 153 | symbol, name := parse_struct(root, value_decl, value) 154 | p.symbols[name] = symbol 155 | } 156 | 157 | case ^ast.Array_Type: { 158 | symbol, name := parse_array(root, value_decl, value) 159 | p.symbols[name] = symbol 160 | } 161 | 162 | case ^ast.Selector_Expr: { // Expecting a package 163 | symbol, name := parse_external_symbol(p, root, value_decl, value) 164 | p.symbols[name] = symbol 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | parse_program :: proc(root_path: string) -> (program: Program) { 173 | program.parser = parser.default_parser() 174 | return program 175 | } 176 | 177 | Parser :: struct { 178 | parser: parser.Parser, 179 | src_pkg: ^ast.Package, 180 | src_path: string, 181 | collections: map[string]string, 182 | allowed_attributes: map[string]bool, 183 | symbols: map[string]Symbol, 184 | imports_alias_path_mapping: map[string]string, // alias, path 185 | parsed_packages: map[string]^ast.Package, 186 | } 187 | 188 | make_import_path :: proc(p: ^Parser, path: string, allocator := context.allocator) -> (result: string) { 189 | parts := strings.split(path, ":", context.temp_allocator) 190 | collection_path := "./" 191 | pkg_path := "" 192 | if len(parts) == 2 { // we have a collection thing 193 | collection_path = p.collections[parts[0]] 194 | pkg_path = parts[1] 195 | } else { 196 | pkg_path = parts[0] 197 | } 198 | return filepath.join({collection_path, pkg_path}, allocator) 199 | } 200 | 201 | print_parser_data :: proc(p: ^Parser) { 202 | for name, symbol in p.symbols { 203 | fmt.printf("Symbol: %v\n", name) 204 | fmt.printf("distinct: %v\n", symbol.is_distinct) 205 | fmt.printf("Attribs: %v\n", symbol.attribs) 206 | switch var in symbol.var { 207 | case Struct: { 208 | fmt.printf("Variant: Struct\n") 209 | fmt.printf("Fields: %v\n", var.fields) 210 | } 211 | 212 | case Proc: { 213 | fmt.printf("Variant: Proc\n") 214 | fmt.printf("Type: %v\n", var.type) 215 | fmt.printf("Params: %v\n", var.params) 216 | fmt.printf("Results: %v\n", var.results) 217 | } 218 | 219 | case Array: { 220 | fmt.printf("Variant: Array\n") 221 | fmt.printf("Length: %v\n", var.len) 222 | fmt.printf("Value Type: %v\n", var.value_type) 223 | } 224 | 225 | case External_Symbol: { 226 | fmt.printf("Variant: External Symbol\n") 227 | fmt.printf("Package: %v\n", var.pkg_path) 228 | fmt.printf("Identifier: %v\n", var.ident) 229 | } 230 | } 231 | 232 | fmt.printf("\n") 233 | } 234 | } 235 | 236 | parser_init :: proc(p: ^Parser, allowed_attributes: []string) { 237 | p.parser = parser.default_parser() 238 | for attrib in allowed_attributes { 239 | key := strings.clone(attrib) 240 | p.allowed_attributes[key] = true 241 | } 242 | 243 | for attrib in BUILTIN_ATTRIBUTES { 244 | key := strings.clone(attrib) 245 | p.allowed_attributes[key] = true 246 | } 247 | 248 | p.collections["core"] = fmt.aprintf("%s/core", ODIN_ROOT) 249 | } 250 | 251 | is_attribute_valid :: #force_inline proc(p: ^Parser, attrib: string) -> (valid: bool) { 252 | return attrib in p.allowed_attributes 253 | } 254 | 255 | parse_attributes :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl) -> (result: Attribute_Map) { 256 | result = make(Attribute_Map) 257 | for attr, i in value_decl.attributes { 258 | for x, j in attr.elems { 259 | name := get_attr_name(root, x) 260 | 261 | result[name] = parse_attrib_val(root, x) 262 | } 263 | 264 | } 265 | return 266 | } 267 | 268 | parse_attrib_object :: proc(root: ^ast.File, obj: ^ast.Comp_Lit) -> (result: Attribute_Map) { 269 | result = make(Attribute_Map) 270 | for elem, i in obj.elems { 271 | name := get_attr_name(root, elem) 272 | result[name] = parse_attrib_val(root, elem) 273 | } 274 | return 275 | } 276 | 277 | get_attr_name :: proc(root: ^ast.File, elem: ^ast.Expr) -> (name: string) { 278 | #partial switch x in elem.derived { 279 | case ^ast.Field_Value: { 280 | attr := x.field.derived.(^ast.Ident) 281 | name = attr.name 282 | } 283 | 284 | case ^ast.Ident: { 285 | name = x.name 286 | } 287 | } 288 | return 289 | } 290 | 291 | get_attr_elem :: proc(root: ^ast.File, elem: ^ast.Expr) -> (name: string, value: string) { // value can be a map maybe 292 | #partial switch x in elem.derived { 293 | case ^ast.Field_Value: { 294 | attr := x.field.derived.(^ast.Ident) 295 | 296 | #partial switch v in x.value.derived { 297 | case ^ast.Basic_Lit: { 298 | value = strings.trim(v.tok.text, "\"") 299 | } 300 | 301 | case ^ast.Ident: { 302 | value = root.src[v.pos.offset : v.end.offset] 303 | } 304 | 305 | case ^ast.Comp_Lit: { 306 | 307 | } 308 | } 309 | name = attr.name 310 | } 311 | 312 | case ^ast.Ident: { 313 | name = x.name 314 | } 315 | } 316 | return 317 | } 318 | 319 | parse_attrib_val :: proc(root: ^ast.File, elem: ^ast.Expr) -> (result: Attribute_Value) { 320 | #partial switch x in elem.derived { 321 | case ^ast.Field_Value: { 322 | #partial switch v in x.value.derived { 323 | case ^ast.Basic_Lit: { 324 | result = strings.trim(v.tok.text, "\"") 325 | return 326 | } 327 | 328 | case ^ast.Ident: { 329 | value := root.src[v.pos.offset : v.end.offset] 330 | result = cast(Identifier)value 331 | } 332 | 333 | case ^ast.Comp_Lit: { 334 | result = parse_attrib_object(root, v) 335 | } 336 | } 337 | return 338 | } 339 | 340 | case ^ast.Ident: { 341 | //result = cast(Identifier)x.name 342 | //fmt.printf("We shouldn't be an identifier %s\n", x.name) 343 | //fmt.printf("Found identifier %v\n", x) 344 | return nil 345 | } 346 | 347 | case ^ast.Comp_Lit: { 348 | //result = parse_attrib_object(root, x) 349 | //fmt.printf("We shouldn't be in comp literal %v\n", x) 350 | return nil 351 | } 352 | } 353 | return nil 354 | } 355 | 356 | lazy_parse_external_symbol :: proc(p: ^Parser, symbol: ^Symbol) { 357 | 358 | } 359 | 360 | parse_package :: proc(p: ^Parser, path: string) { 361 | p.src_path = path 362 | pkg_collected: bool 363 | p.src_pkg, pkg_collected = parser.collect_package(path) 364 | assert(pkg_collected, "Package collection failed.") 365 | parser.parse_package(p.src_pkg, &p.parser) 366 | for filename, file in p.src_pkg.files { 367 | root := file 368 | for decl, index in root.decls { 369 | #partial switch derived in decl.derived { 370 | case ^ast.Import_Decl: { 371 | import_name: string 372 | import_text := root.src[derived.pos.offset : derived.end.offset] 373 | if derived.name.kind != .Invalid { 374 | // got a name 375 | import_name = derived.name.text 376 | } else { 377 | // Take the name from the path 378 | start_pos := 0 379 | for c, i in derived.fullpath { 380 | if c == ':' { 381 | start_pos = i + 1 382 | break 383 | } 384 | } 385 | // Use the last folder 386 | split := strings.split(derived.fullpath[start_pos:], "/", context.temp_allocator) 387 | import_name = split[len(split) - 1] 388 | import_name = strings.trim(import_name, "\"") 389 | } 390 | p.imports_alias_path_mapping[import_name] = derived.fullpath 391 | } 392 | } 393 | if value_decl, is_value_decl := decl.derived.(^ast.Value_Decl); is_value_decl { 394 | if len(value_decl.attributes) < 1 do continue 395 | 396 | #partial switch value in value_decl.values[0].derived { 397 | case ^ast.Distinct_Type: { // This needs to be sorted out 398 | 399 | } 400 | 401 | case ^ast.Proc_Lit: { 402 | symbol, name := parse_proc(root, value_decl, value) 403 | p.symbols[name] = symbol 404 | } 405 | 406 | case ^ast.Struct_Type: { 407 | symbol, name := parse_struct(root, value_decl, value) 408 | p.symbols[name] = symbol 409 | } 410 | 411 | case ^ast.Array_Type: { 412 | symbol, name := parse_array(root, value_decl, value) 413 | p.symbols[name] = symbol 414 | } 415 | 416 | case ^ast.Selector_Expr: { // Expecting a package 417 | symbol, name := parse_external_symbol(p, root, value_decl, value) 418 | p.symbols[name] = symbol 419 | } 420 | } 421 | } 422 | } 423 | } 424 | } 425 | 426 | parse_external_symbol :: proc(p: ^Parser, root: ^ast.File, value_decl: ^ast.Value_Decl, ext_decl: ^ast.Selector_Expr) -> (result: Symbol, name: string) { 427 | result.attribs = parse_attributes(root, value_decl) 428 | result.var = External_Symbol{} 429 | var_external := &result.var.(External_Symbol) 430 | name = value_decl.names[0].derived.(^ast.Ident).name 431 | pkg := ext_decl.expr.derived.(^ast.Ident).name 432 | var_external.pkg_path = p.imports_alias_path_mapping[pkg] 433 | // This might crash in some conditions 434 | // This is also a bit weird... We'll need to test further 435 | var_external.ident = root.src[ext_decl.op.pos.offset + 1 : ext_decl.expr_base.end.offset] 436 | //fmt.printf("Selector expr: %v\n", ext_decl) 437 | 438 | return 439 | } 440 | 441 | parse_array :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, arr_decl: ^ast.Array_Type) -> (result: Symbol, name: string) { 442 | result.attribs = parse_attributes(root, value_decl) 443 | result.var = Array{} 444 | var_array := &result.var.(Array) 445 | name = value_decl.names[0].derived.(^ast.Ident).name 446 | var_array.value_type = arr_decl.elem.derived.(^ast.Ident).name 447 | lenLit := arr_decl.len.derived.(^ast.Basic_Lit) 448 | lenStr := root.src[lenLit.pos.offset : lenLit.end.offset] 449 | var_array.len, _ = strconv.parse_int(lenStr) 450 | 451 | return 452 | } 453 | 454 | parse_struct :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, struct_decl: ^ast.Struct_Type) -> (result: Symbol, name: string) { 455 | result.attribs = parse_attributes(root, value_decl) 456 | result.var = Struct{} 457 | var_struct := &result.var.(Struct) 458 | name = value_decl.names[0].derived.(^ast.Ident).name 459 | //result.fields = make(map[string]Field) 460 | 461 | for field in struct_decl.fields.list { 462 | 463 | fType: string 464 | #partial switch x in field.type.derived { 465 | case ^ast.Ident: { 466 | fType = x.name 467 | } 468 | case ^ast.Selector_Expr: { // What was this again? 469 | fType = root.src[x.pos.offset : x.end.offset] //godlike odin 470 | } 471 | case ^ast.Pointer_Type: { 472 | fType = root.src[x.pos.offset : x.end.offset] 473 | } 474 | } 475 | for name in field.names { 476 | fName := name.derived.(^ast.Ident).name 477 | append(&var_struct.fields, Field { 478 | name = fName, 479 | type = fType, 480 | }) 481 | } 482 | 483 | } 484 | 485 | return 486 | } 487 | 488 | 489 | parse_proc :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, proc_lit: ^ast.Proc_Lit) -> (result: Symbol, name: string) { 490 | 491 | //result.properties, err = parse_properties(root, value_decl) 492 | result.attribs = parse_attributes(root, value_decl) 493 | result.var = Proc{} 494 | var_proc := &result.var.(Proc) 495 | v := proc_lit 496 | procType := v.type 497 | declName := value_decl.names[0].derived.(^ast.Ident).name // Note(Dragos): Does this work with 'a, b: int' ????? 498 | 499 | name = declName 500 | var_proc.type = root.src[procType.pos.offset : procType.end.offset] 501 | switch conv in procType.calling_convention { 502 | case string: { 503 | var_proc.calling_convention = strings.trim(conv, `"`) 504 | } 505 | 506 | case ast.Proc_Calling_Convention_Extra: { 507 | var_proc.calling_convention = "c" //not fully correct 508 | } 509 | 510 | case: { // nil, default calling convention 511 | var_proc.calling_convention = "odin" 512 | } 513 | } 514 | // Note(Dragos): these should be checked for 0 515 | 516 | 517 | var_proc.params = make(type_of(var_proc.params)) 518 | var_proc.results = make(type_of(var_proc.results)) 519 | // Get parameters 520 | if procType.params != nil { 521 | 522 | for param, i in procType.params.list { 523 | paramType: string 524 | #partial switch x in param.type.derived { 525 | case ^ast.Ident: { 526 | paramType = x.name 527 | } 528 | case ^ast.Selector_Expr: { 529 | paramType = root.src[x.pos.offset : x.end.offset] //godlike odin 530 | } 531 | case ^ast.Pointer_Type: { 532 | paramType = root.src[x.pos.offset : x.end.offset] 533 | } 534 | } 535 | 536 | for name in param.names { 537 | append(&var_proc.params, Field{ 538 | name = name.derived.(^ast.Ident).name, 539 | type = paramType, 540 | }) 541 | } 542 | } 543 | } 544 | 545 | // Get results 546 | if procType.results != nil { 547 | for rval, i in procType.results.list { 548 | resName: string 549 | resType: string 550 | #partial switch x in rval.type.derived { 551 | case ^ast.Ident: { 552 | resType = x.name 553 | if len(rval.names) != 0 { 554 | resName = rval.names[0].derived.(^ast.Ident).name 555 | } 556 | } 557 | case ^ast.Selector_Expr: { 558 | if len(rval.names) != 0 { 559 | resName = rval.names[0].derived.(^ast.Ident).name 560 | } 561 | resType = root.src[x.pos.offset : x.end.offset] //godlike odin 562 | } 563 | } 564 | if len(rval.names) == 0 || resName == resType { 565 | // Result name is not specified 566 | sb := strings.builder_make(context.temp_allocator) 567 | strings.write_string(&sb, "mani_result") 568 | strings.write_int(&sb, i) 569 | resName = strings.to_string(sb) 570 | } 571 | 572 | 573 | 574 | append(&var_proc.results, Field{ 575 | name = resName, 576 | type = resType, 577 | }) 578 | } 579 | } 580 | 581 | 582 | return 583 | } -------------------------------------------------------------------------------- /manigen2/codegen/parser.odin: -------------------------------------------------------------------------------- 1 | package attriparse 2 | 3 | import ast "core:odin/ast" 4 | import parser "core:odin/parser" 5 | import tokenizer "core:odin/tokenizer" 6 | import "core:strings" 7 | import "core:fmt" 8 | import "core:strconv" 9 | import "core:path/filepath" 10 | 11 | BUILTIN_ATTRIBUTES := [?]string { 12 | "private", 13 | "require", 14 | "link_name", 15 | "link_prefix", 16 | "export", 17 | "linkage", 18 | "default_calling_convention", 19 | "link_section", 20 | "extra_linker_flags", 21 | "deferred_in", 22 | "deferred_out", 23 | "deferred_in_out", 24 | "deferred_none", 25 | "deprecated", 26 | "require_results", 27 | "warning", 28 | "disabled", 29 | "init", 30 | "cold", 31 | "optimization_mode", 32 | "static", 33 | "thread_local", 34 | "builtin", 35 | "objc_name", 36 | "objc_type", 37 | "objc_is_class_method", 38 | "require_target_feature", 39 | "enable_target_feature", 40 | } 41 | 42 | 43 | String :: string 44 | Identifier :: distinct String 45 | Int :: int 46 | Float :: f32 47 | 48 | Attribute_Value :: union { 49 | // nil: empty attribute, like a flag, array element, etc 50 | String, 51 | Identifier, 52 | Int, 53 | Attribute_Map, 54 | } 55 | 56 | Field :: struct { 57 | name: string, 58 | type: string, 59 | } 60 | 61 | Struct :: struct { 62 | fields: [dynamic]Field, 63 | } 64 | 65 | Proc :: struct { 66 | type: string, 67 | calling_convention: string, 68 | params: [dynamic]Field, 69 | results: [dynamic]Field, 70 | } 71 | 72 | Array :: struct { 73 | len: int, 74 | value_type: string, 75 | } 76 | 77 | Package :: struct { 78 | using pkg: ^ast.Package, 79 | path_to_import: string, // Path that can be used relative to the Program being parsed 80 | } 81 | 82 | Symbol :: struct { 83 | attribs: Attribute_Map, 84 | is_distinct: bool, 85 | var: union { 86 | Struct, 87 | Proc, 88 | Array, 89 | }, 90 | } 91 | 92 | 93 | Attribute_Map :: distinct map[string]Attribute_Value 94 | 95 | Program :: struct { 96 | root_pkg: ^ast.Package, 97 | parser: parser.Parser, 98 | collections: map[string]string, 99 | pkgs: map[string]Package, 100 | symbols: map[string]Symbol, // All symbols in the program. Should be formated as pkg.name 101 | anon_symbols: [dynamic]Symbol, // You can add attributes to things that are unnamed, so we should store them aswell. Only top level package? 102 | } 103 | 104 | // Parse all imports first as packages, do it recursively no? 105 | parse_package :: proc(p: ^Program, path: string) -> (result: Package) { 106 | fmt.printf("Trying to parse path: %v\n", path) 107 | path := make_import_path(p, path) 108 | fmt.printf("Making import path %v\n", path) 109 | pkg_collected: bool 110 | result.pkg, pkg_collected = parser.collect_package(path) // Todo(Dragos): This needs to be rewritten to accept a ban list 111 | 112 | assert(pkg_collected, "Package collection failed.") 113 | pkg_parsed := parser.parse_package(result.pkg, &p.parser) 114 | fmt.assertf(pkg_parsed, "Failed to parse package at path %v\n", result.fullpath) 115 | if result.name in p.pkgs { 116 | return p.pkgs[result.name] 117 | } 118 | p.pkgs[result.name] = result 119 | fmt.printf("Found package %v\n", result.name) 120 | for filename, file in result.files { 121 | root := file 122 | import_pkg_names: map[string]string // map[alias] = pkg.name. Temporary map made for each file 123 | for decl, index in root.decls { 124 | #partial switch derived in decl.derived { 125 | case ^ast.Import_Decl: { 126 | import_name: string 127 | pkg := parse_package(p, strings.trim(derived.fullpath, "\"")) 128 | import_text := root.src[derived.pos.offset : derived.end.offset] 129 | 130 | if derived.name.kind != .Invalid { 131 | // got a name 132 | import_name = derived.name.text 133 | } else { 134 | // Take the name from the path 135 | start_pos := 0 136 | for c, i in derived.fullpath { 137 | if c == ':' { 138 | start_pos = i + 1 139 | break 140 | } 141 | } 142 | // Use the last folder 143 | split := strings.split(derived.fullpath[start_pos:], "/", context.temp_allocator) 144 | import_name = split[len(split) - 1] 145 | import_name = strings.trim(import_name, "\"") 146 | import_pkg_names[import_name] = pkg.name 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | return 154 | } 155 | 156 | parse_program :: proc(program: ^Program, root_path: string) { 157 | program.parser = parser.default_parser({.Optional_Semicolons, .Allow_Reserved_Package_Name}) 158 | parse_package(program, root_path) 159 | } 160 | 161 | make_import_path :: proc(p: ^Program, path: string, allocator := context.allocator) -> (result: string) { 162 | parts := strings.split(path, ":", context.temp_allocator) 163 | collection_path := "./" 164 | pkg_path := "" 165 | if len(parts) == 2 { // we have a collection thing 166 | assert(parts[0] in p.collections, "Collection not found.") 167 | collection_path = p.collections[parts[0]] 168 | pkg_path = parts[1] 169 | } else { 170 | pkg_path = parts[0] 171 | } 172 | return filepath.join({collection_path, pkg_path}, allocator) 173 | } 174 | 175 | print_program_data :: proc(p: ^Program) { 176 | fmt.printf("Packages:\n") 177 | for name, pkg in p.pkgs { 178 | fmt.printf("%v\n", name) 179 | } 180 | fmt.printf("\n") 181 | for name, symbol in p.symbols { 182 | fmt.printf("Symbol: %v\n", name) 183 | fmt.printf("distinct: %v\n", symbol.is_distinct) 184 | fmt.printf("Attribs: %v\n", symbol.attribs) 185 | switch var in symbol.var { 186 | case Struct: { 187 | fmt.printf("Variant: Struct\n") 188 | fmt.printf("Fields: %v\n", var.fields) 189 | } 190 | 191 | case Proc: { 192 | fmt.printf("Variant: Proc\n") 193 | fmt.printf("Type: %v\n", var.type) 194 | fmt.printf("Params: %v\n", var.params) 195 | fmt.printf("Results: %v\n", var.results) 196 | } 197 | 198 | case Array: { 199 | fmt.printf("Variant: Array\n") 200 | fmt.printf("Length: %v\n", var.len) 201 | fmt.printf("Value Type: %v\n", var.value_type) 202 | } 203 | } 204 | 205 | fmt.printf("\n") 206 | } 207 | } 208 | 209 | parse_attributes :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl) -> (result: Attribute_Map) { 210 | result = make(Attribute_Map) 211 | for attr, i in value_decl.attributes { 212 | for x, j in attr.elems { 213 | name := get_attr_name(root, x) 214 | 215 | result[name] = parse_attrib_val(root, x) 216 | } 217 | 218 | } 219 | return 220 | } 221 | 222 | parse_attrib_object :: proc(root: ^ast.File, obj: ^ast.Comp_Lit) -> (result: Attribute_Map) { 223 | result = make(Attribute_Map) 224 | for elem, i in obj.elems { 225 | name := get_attr_name(root, elem) 226 | result[name] = parse_attrib_val(root, elem) 227 | } 228 | return 229 | } 230 | 231 | get_attr_name :: proc(root: ^ast.File, elem: ^ast.Expr) -> (name: string) { 232 | #partial switch x in elem.derived { 233 | case ^ast.Field_Value: { 234 | attr := x.field.derived.(^ast.Ident) 235 | name = attr.name 236 | } 237 | 238 | case ^ast.Ident: { 239 | name = x.name 240 | } 241 | } 242 | return 243 | } 244 | 245 | get_attr_elem :: proc(root: ^ast.File, elem: ^ast.Expr) -> (name: string, value: string) { // value can be a map maybe 246 | #partial switch x in elem.derived { 247 | case ^ast.Field_Value: { 248 | attr := x.field.derived.(^ast.Ident) 249 | 250 | #partial switch v in x.value.derived { 251 | case ^ast.Basic_Lit: { 252 | value = strings.trim(v.tok.text, "\"") 253 | } 254 | 255 | case ^ast.Ident: { 256 | value = root.src[v.pos.offset : v.end.offset] 257 | } 258 | 259 | case ^ast.Comp_Lit: { 260 | 261 | } 262 | } 263 | name = attr.name 264 | } 265 | 266 | case ^ast.Ident: { 267 | name = x.name 268 | } 269 | } 270 | return 271 | } 272 | 273 | parse_attrib_val :: proc(root: ^ast.File, elem: ^ast.Expr) -> (result: Attribute_Value) { 274 | #partial switch x in elem.derived { 275 | case ^ast.Field_Value: { 276 | #partial switch v in x.value.derived { 277 | case ^ast.Basic_Lit: { 278 | result = strings.trim(v.tok.text, "\"") 279 | return 280 | } 281 | 282 | case ^ast.Ident: { 283 | value := root.src[v.pos.offset : v.end.offset] 284 | result = cast(Identifier)value 285 | } 286 | 287 | case ^ast.Comp_Lit: { 288 | result = parse_attrib_object(root, v) 289 | } 290 | } 291 | return 292 | } 293 | 294 | case ^ast.Ident: { 295 | //result = cast(Identifier)x.name 296 | //fmt.printf("We shouldn't be an identifier %s\n", x.name) 297 | //fmt.printf("Found identifier %v\n", x) 298 | return nil 299 | } 300 | 301 | case ^ast.Comp_Lit: { 302 | //result = parse_attrib_object(root, x) 303 | //fmt.printf("We shouldn't be in comp literal %v\n", x) 304 | return nil 305 | } 306 | } 307 | return nil 308 | } 309 | 310 | // This needs to be remade 311 | /* 312 | parse_external_symbol :: proc(p: ^Program, root: ^ast.File, value_decl: ^ast.Value_Decl, ext_decl: ^ast.Selector_Expr) -> (result: Symbol, name: string) { 313 | result.attribs = parse_attributes(root, value_decl) 314 | result.var = External_Symbol{} 315 | var_external := &result.var.(External_Symbol) 316 | name = value_decl.names[0].derived.(^ast.Ident).name 317 | pkg := ext_decl.expr.derived.(^ast.Ident).name 318 | //var_external.pkg_path = p.imports_alias_path_mapping[pkg] 319 | // This might crash in some conditions 320 | // This is also a bit weird... We'll need to test further 321 | var_external.ident = root.src[ext_decl.op.pos.offset + 1 : ext_decl.expr_base.end.offset] 322 | //fmt.printf("Selector expr: %v\n", ext_decl) 323 | 324 | return 325 | } 326 | */ 327 | 328 | parse_array :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, arr_decl: ^ast.Array_Type) -> (result: Symbol, name: string) { 329 | result.attribs = parse_attributes(root, value_decl) 330 | result.var = Array{} 331 | var_array := &result.var.(Array) 332 | name = value_decl.names[0].derived.(^ast.Ident).name 333 | var_array.value_type = arr_decl.elem.derived.(^ast.Ident).name 334 | lenLit := arr_decl.len.derived.(^ast.Basic_Lit) 335 | lenStr := root.src[lenLit.pos.offset : lenLit.end.offset] 336 | var_array.len, _ = strconv.parse_int(lenStr) 337 | 338 | return 339 | } 340 | 341 | parse_struct :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, struct_decl: ^ast.Struct_Type) -> (result: Symbol, name: string) { 342 | result.attribs = parse_attributes(root, value_decl) 343 | result.var = Struct{} 344 | var_struct := &result.var.(Struct) 345 | name = value_decl.names[0].derived.(^ast.Ident).name 346 | //result.fields = make(map[string]Field) 347 | 348 | for field in struct_decl.fields.list { 349 | 350 | fType: string 351 | #partial switch x in field.type.derived { 352 | case ^ast.Ident: { 353 | fType = x.name 354 | } 355 | case ^ast.Selector_Expr: { // What was this again? 356 | fType = root.src[x.pos.offset : x.end.offset] //godlike odin 357 | } 358 | case ^ast.Pointer_Type: { 359 | fType = root.src[x.pos.offset : x.end.offset] 360 | } 361 | } 362 | for name in field.names { 363 | fName := name.derived.(^ast.Ident).name 364 | append(&var_struct.fields, Field { 365 | name = fName, 366 | type = fType, 367 | }) 368 | } 369 | 370 | } 371 | 372 | return 373 | } 374 | 375 | 376 | parse_proc :: proc(root: ^ast.File, value_decl: ^ast.Value_Decl, proc_lit: ^ast.Proc_Lit) -> (result: Symbol, name: string) { 377 | 378 | //result.properties, err = parse_properties(root, value_decl) 379 | result.attribs = parse_attributes(root, value_decl) 380 | result.var = Proc{} 381 | var_proc := &result.var.(Proc) 382 | v := proc_lit 383 | procType := v.type 384 | declName := value_decl.names[0].derived.(^ast.Ident).name // Note(Dragos): Does this work with 'a, b: int' ????? 385 | 386 | name = declName 387 | var_proc.type = root.src[procType.pos.offset : procType.end.offset] 388 | switch conv in procType.calling_convention { 389 | case string: { 390 | var_proc.calling_convention = strings.trim(conv, `"`) 391 | } 392 | 393 | case ast.Proc_Calling_Convention_Extra: { 394 | var_proc.calling_convention = "c" //not fully correct 395 | } 396 | 397 | case: { // nil, default calling convention 398 | var_proc.calling_convention = "odin" 399 | } 400 | } 401 | // Note(Dragos): these should be checked for 0 402 | 403 | 404 | var_proc.params = make(type_of(var_proc.params)) 405 | var_proc.results = make(type_of(var_proc.results)) 406 | // Get parameters 407 | if procType.params != nil { 408 | 409 | for param, i in procType.params.list { 410 | paramType: string 411 | #partial switch x in param.type.derived { 412 | case ^ast.Ident: { 413 | paramType = x.name 414 | } 415 | case ^ast.Selector_Expr: { 416 | paramType = root.src[x.pos.offset : x.end.offset] //godlike odin 417 | } 418 | case ^ast.Pointer_Type: { 419 | paramType = root.src[x.pos.offset : x.end.offset] 420 | } 421 | } 422 | 423 | for name in param.names { 424 | append(&var_proc.params, Field{ 425 | name = name.derived.(^ast.Ident).name, 426 | type = paramType, 427 | }) 428 | } 429 | } 430 | } 431 | 432 | // Get results 433 | if procType.results != nil { 434 | for rval, i in procType.results.list { 435 | resName: string 436 | resType: string 437 | #partial switch x in rval.type.derived { 438 | case ^ast.Ident: { 439 | resType = x.name 440 | if len(rval.names) != 0 { 441 | resName = rval.names[0].derived.(^ast.Ident).name 442 | } 443 | } 444 | case ^ast.Selector_Expr: { 445 | if len(rval.names) != 0 { 446 | resName = rval.names[0].derived.(^ast.Ident).name 447 | } 448 | resType = root.src[x.pos.offset : x.end.offset] //godlike odin 449 | } 450 | } 451 | if len(rval.names) == 0 || resName == resType { 452 | // Result name is not specified 453 | sb := strings.builder_make(context.temp_allocator) 454 | strings.write_string(&sb, "mani_result") 455 | strings.write_int(&sb, i) 456 | resName = strings.to_string(sb) 457 | } 458 | 459 | 460 | 461 | append(&var_proc.results, Field{ 462 | name = resName, 463 | type = resType, 464 | }) 465 | } 466 | } 467 | 468 | 469 | return 470 | } -------------------------------------------------------------------------------- /manigen2/main.odin: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "core:fmt" 4 | import os "core:os" 5 | import dlib "core:dynlib" 6 | import c "core:c" 7 | import libc "core:c/libc" 8 | import runtime "core:runtime" 9 | import strings "core:strings" 10 | import intr "core:intrinsics" 11 | import filepath "core:path/filepath" 12 | 13 | 14 | import ast "core:odin/ast" 15 | import parser "core:odin/parser" 16 | import tokenizer "core:odin/tokenizer" 17 | import "core:mem" 18 | import "core:log" 19 | 20 | import "core:time" 21 | 22 | import "codegen" 23 | 24 | 25 | 26 | main :: proc() { 27 | fmt.printf("Started mani\n") 28 | 29 | program: codegen.Program 30 | program.collections["core"] = filepath.join({ODIN_ROOT, "core"}) 31 | codegen.parse_program(&program, "./test") 32 | 33 | codegen.print_program_data(&program) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /media/intellisense.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragosPopse/mani/014786e37e78f3af016411649d99bc2f3a5f1eeb/media/intellisense.gif -------------------------------------------------------------------------------- /ols.json: -------------------------------------------------------------------------------- 1 | { 2 | "collections": [ 3 | { 4 | "name": "shared", 5 | "path": "../vendor/odin/shared" 6 | } 7 | ], 8 | "checker_args": "-ignore-unknown-attributes", 9 | "enable_semantic_tokens": true, 10 | "enable_document_symbols": true, 11 | "enable_hover": true, 12 | "enable_snippets": true 13 | } -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | build\mani\mani.exe mani_config.json -show-timings -------------------------------------------------------------------------------- /run_test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | build\test\test.exe -------------------------------------------------------------------------------- /ship.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragosPopse/mani/014786e37e78f3af016411649d99bc2f3a5f1eeb/ship.bat -------------------------------------------------------------------------------- /test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if not exist build mkdir build 4 | if not exist build\test mkdir build\test 5 | 6 | xcopy test\lua542.dll build\test\lua542.dll /Y 7 | odin build test -debug -ignore-unknown-attributes -out:build\test\test.exe 8 | build\test\test.exe -------------------------------------------------------------------------------- /test/lua542.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragosPopse/mani/014786e37e78f3af016411649d99bc2f3a5f1eeb/test/lua542.dll -------------------------------------------------------------------------------- /test/main.odin: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import subtest "subpackage" 4 | import "core:fmt" 5 | import c "core:c" 6 | 7 | 8 | @(this_needs_to_work = true) 9 | test_external :: subtest.proc_test 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/main2.odin: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | // We use different aliases 4 | import subtest2 "subpackage" 5 | import fmt2 "core:fmt" 6 | import c2 "core:c" 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/subpackage/subpackage.odin: -------------------------------------------------------------------------------- 1 | package subpackage_test 2 | 3 | Sub_Struct :: struct { 4 | 5 | } 6 | 7 | proc_test :: proc(var1: int, var2: Sub_Struct) -> (result: f32) { 8 | return 9 | } -------------------------------------------------------------------------------- /test/test.lua: -------------------------------------------------------------------------------- 1 | 2 | -- lua 3 | local v = make_vec4(2, 3, 4, 5) 4 | v.xyz = v.rrr --------------------------------------------------------------------------------