1 module lumars.emmylua; 2 3 import lumars; 4 5 struct EmmyLuaBuilder 6 { 7 import std.array : Appender; 8 9 private Appender!(char[]) _output; 10 private string[] _tables; 11 private string[] _typeNames; 12 13 private void addTable(string name) 14 { 15 import std.algorithm : canFind; 16 17 if(!name.length) 18 return; 19 20 if(!this._tables.canFind(name)) 21 { 22 auto start = 0; 23 auto cursor = 0; 24 while(cursor < name.length) 25 { 26 if(name[cursor] == '.') 27 { 28 auto slice = name[start..cursor]; 29 if(!this._tables.canFind(slice)) 30 this._tables ~= slice; 31 start = cursor+1; 32 } 33 cursor++; 34 } 35 36 this._tables ~= name; 37 } 38 } 39 40 private void putDeclaration(string table, string name, string def) 41 { 42 if(table.length) 43 { 44 this._output.put(table); 45 this._output.put('.'); 46 } 47 this._output.put(name); 48 this._output.put(" = "); 49 if(table.length) 50 { 51 this._output.put(table); 52 this._output.put('.'); 53 } 54 this._output.put(name); 55 this._output.put(" or "); 56 this._output.put(def ? def : "nil"); 57 this._output.put('\n'); 58 } 59 60 private void putDescription(string description) 61 { 62 if(description.length) 63 { 64 this._output.put("---"); 65 this._output.put(description); 66 this._output.put('\n'); 67 } 68 } 69 70 private void putType(T)() 71 { 72 import std.algorithm : canFind; 73 import std.traits : fullyQualifiedName, isDynamicArray, isSomeFunction; 74 75 const fqn = fullyQualifiedName!T; 76 if(this._typeNames.canFind(fqn) || is(T == LuaValue) || is(T == LuaNumber)) 77 return; 78 this._typeNames ~= fqn; 79 80 static if(is(T == struct)) 81 { 82 Appender!(char[]) suboutput; 83 84 suboutput.put("---@class "); 85 suboutput.put(getTypeName!T); 86 suboutput.put('\n'); 87 88 static foreach(member; __traits(allMembers, T)) 89 {{ 90 alias Member = __traits(getMember, T, member); 91 92 static if(__traits(compiles, mixin("T.init."~member~" = T.init."~member))) // Is it public and a variable? 93 { 94 alias MemberT = typeof(mixin("T.init."~member)); 95 static if(!isSomeFunction!MemberT) // Handles a weird edge case: T func(T) 96 { 97 suboutput.put("---@field public "); 98 suboutput.put(member); 99 suboutput.put(' '); 100 suboutput.put(getTypeName!MemberT); 101 suboutput.put('\n'); 102 } 103 } 104 }} 105 106 suboutput.put("local "); 107 suboutput.put(getTypeName!T); 108 suboutput.put('\n'); 109 this._output.put(suboutput.data); 110 } 111 } 112 113 void addArray(T)(string table, string name, string description = "") 114 { 115 this.addTable(table); 116 this.putType!T(); 117 this.putDescription(description); 118 this._output.put("---@type "); 119 this._output.put(getTypeName!T); 120 this._output.put("[]\n"); 121 this.putDeclaration(table, name, "{}"); 122 } 123 124 void addTable(Key, Value)(string table, string name, string description = "") 125 { 126 this.addTable(table); 127 this.putType!Key(); 128 this.putType!Value(); 129 this.putDescription(description); 130 this._output.put("---@type table<"); 131 this._output.put(getTypeName!Key); 132 this._output.put(", "); 133 this._output.put(getTypeName!Value); 134 this._output.put(">\n"); 135 this.putDeclaration(table, name, "{}"); 136 } 137 138 string toString() const 139 { 140 Appender!(char[]) tables; 141 foreach(t; this._tables) 142 { 143 tables.put(t); 144 tables.put(" = "); 145 tables.put(t); 146 tables.put(" or {}\n"); 147 } 148 tables.put(this._output.data); 149 return tables.data.idup; 150 } 151 } 152 /// 153 unittest 154 { 155 static struct S 156 { 157 LuaNumber a; 158 LuaValue b; 159 string c; 160 } 161 162 EmmyLuaBuilder b; 163 b.addArray!LuaNumber(null, "globalTable", "Some description"); 164 b.addArray!LuaValue("t1", "field", "Some field"); 165 b.addArray!string("t2.t", "field"); 166 b.addTable!(string, LuaValue)(null, "map"); 167 b.addArray!S(null, "s"); 168 b.addFunction!( 169 (string _, LuaValue __, S ____) 170 { 171 return 200; 172 } 173 )(null, "test", "icles"); 174 alias f = LuaTable.makeNew; 175 b.addFunction!f(null, "readText"); 176 b.addFunctions!( 177 "writeln", (string[] s) {}, 178 "readln", () { return ""; } 179 )("sh"); 180 181 // writeln(b.toString()); 182 } 183 184 template addFunction(alias Func) // Have to do this otherwise I get the fucking crappy-ass "dual-context" error. 185 { 186 void addFunction(ref EmmyLuaBuilder b, string table, string name, string description = "") 187 { 188 import std.array : Appender; 189 import std.traits : ParameterIdentifierTuple, Parameters, ReturnType; 190 191 b.addTable(table); 192 193 Appender!(char[]) suboutput; 194 195 suboutput.put("--@type fun("); 196 alias idents = ParameterIdentifierTuple!Func; 197 static foreach(i, param; Parameters!Func) 198 {{ 199 static if(!is(param == LuaState*)) // Wrapped functions can ask for the LuaState. This is not a parameter passed by Lua code. 200 { 201 b.putType!param(); 202 203 const ident = idents[i].length ? idents[i] : "_"; 204 suboutput.put(ident); 205 suboutput.put(':'); 206 suboutput.put(getTypeName!param); 207 208 static if(i != Parameters!Func.length - 1) 209 suboutput.put(", "); 210 } 211 }} 212 b.putType!(ReturnType!Func)(); 213 suboutput.put("):"); 214 suboutput.put(getTypeName!(ReturnType!Func)); 215 suboutput.put('\n'); 216 217 b.putDescription(description); 218 b._output.put(suboutput.data); 219 b.putDeclaration(table, name, "function() assert(false, 'not implemented') end"); 220 } 221 } 222 223 template addFunctions(Funcs...) 224 { 225 void addFunctions(ref EmmyLuaBuilder b, string table) 226 { 227 static foreach(i; 0..Funcs.length/2) 228 addFunction!(Funcs[i*2+1])(b, table, Funcs[i*2]); 229 } 230 } 231 232 private: 233 234 string getTypeName(T)() 235 { 236 import std.range : ElementType; 237 import std.traits : isNumeric, isDynamicArray; 238 239 static if(is(T == LuaValue)) 240 return "any"; 241 else static if(is(T == LuaNumber) || isNumeric!T) 242 return "number"; 243 else static if(is(T == void) || is(T == typeof(null))) 244 return "nil"; 245 else static if(isDynamicArray!T && !is(T == string)) 246 return getTypeName!(ElementType!T)~"[]"; 247 else 248 return T.stringof; 249 }