Skip to content
This repository was archived by the owner on Nov 14, 2025. It is now read-only.

Commit a36ad3c

Browse files
Vurv78Vurv78
authored andcommitted
Add the generator script
1 parent 0cdb252 commit a36ad3c

1 file changed

Lines changed: 250 additions & 0 deletions

File tree

src/Generator.hx

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#if lua
2+
import haxe.ds.StringMap;
3+
import haxe.ds.Option;
4+
5+
// Lua Std Imports
6+
import lua.Table as LuaTable;
7+
import lua.PairTools;
8+
using lua.NativeStringTools; // Allow using gsub, sub, replace, etc on strings.
9+
10+
// SF Imports
11+
import sf.library.Json;
12+
import sf.library.Http;
13+
import sf.library.File;
14+
import sf.library.Builtins.*;
15+
import sf.type.*;
16+
17+
18+
// metadata and number of args
19+
typedef HxOpData = { mdata: String, nargs: Int };
20+
typedef AbstractPile = Array< {fn_data: FnData, fn_name: String, haxe_op: HxOpData} >;
21+
22+
var HaxeOps:StringMap<HxOpData> = [
23+
"__mul" => { mdata: "@:op(A*B)", nargs: 1 },
24+
"__add" => { mdata: "@:op(A+B)", nargs: 1 },
25+
"__sub" => { mdata: "@:op(A-B)", nargs: 1 },
26+
"__div" => { mdata: "@:op(A/B)", nargs: 1 },
27+
"__newindex" => { mdata: "@:op([])", nargs: 2 }, // Array Indexing SET, Two args to differentiate from GET
28+
"__index" => { mdata: "@:op([])", nargs: 1 }, // Array Indexing GET
29+
"__unm" => { mdata: "@:op(-A)", nargs: 0 }, // Negative Unary Operator
30+
"__eq" => { mdata: "@:op(A==B)", nargs: 1 } // Is equal to
31+
];
32+
33+
// String buffer of the current output file.
34+
var HaxeCode = new StringBuf();
35+
36+
typedef DocData = {
37+
methods: LuaTable<String, FnData>,
38+
}
39+
40+
typedef ParamData = {name: String};
41+
typedef ParamGroup = LuaTable<Int, ParamData>;
42+
// Data for a single function in here.
43+
typedef FnData = {
44+
?params: ParamGroup,
45+
?realm: String,
46+
?description: String,
47+
?returns: String
48+
};
49+
50+
/**
51+
If the name is a keyword, returns the mangled version.
52+
Replaces class with _class for example, since haxe really doesn't like keywords as function names.
53+
Also used to make lua varargs (...) into a haxe argument (...varargs:Any) will result in a Rest<Any>
54+
**/
55+
function mangleName(name:String) {
56+
switch( name ) {
57+
case "throw"|"class"|"try"|"catch"|"continue"|"default"|"case"|"break"|"dynamic"|"enum"|"return"|"var":
58+
// Reserved keywords
59+
return '_$name';
60+
case "...":
61+
// For arguments using ...
62+
return "...varargs";
63+
default:
64+
return name;
65+
}
66+
}
67+
68+
/**
69+
Pushes code to the Haxe buffer.
70+
**/
71+
function haxePush(str:String)
72+
HaxeCode.add(str);
73+
74+
/**
75+
Returns the currently written code in the Haxe buffer and clears it
76+
**/
77+
function haxePop():String {
78+
var ret = HaxeCode.toString();
79+
HaxeCode = new StringBuf();
80+
return ret;
81+
}
82+
83+
/**
84+
Makes the first letter in the string uppercase
85+
**/
86+
function upperFirst(str:String):String {
87+
return ((str.sub(1,1).match).upper() + str.sub(2).match);
88+
}
89+
90+
/**
91+
Tries to unwrap the Nullable value. If it's null, throws an error
92+
**/
93+
function unwrap_or_panic<T>(s:Null<T>):T {
94+
switch s {
95+
case null: throw "Null error";
96+
default: return s;
97+
}
98+
}
99+
100+
function unwrap_or<T>(s:Null<T>, ret:T):T {
101+
switch s {
102+
case null: return ret;
103+
default: return s;
104+
}
105+
}
106+
107+
/**
108+
If the Option wrapped value is None, return ret
109+
**/
110+
function opt_unwrap_or<T>(s:Option<T>, ret:T):T {
111+
switch s {
112+
case None: return ret;
113+
case Some(obj): return obj;
114+
}
115+
}
116+
117+
/**
118+
Returns a string of args from function data.
119+
Ex: "hello:Any, apple:Any, ok:Any"
120+
**/
121+
function getArgs(fn_data:FnData):Option<String> {
122+
var params: ParamGroup = switch(fn_data.params) {
123+
case null: return None;
124+
default: fn_data.params;
125+
}
126+
var param_arr = LuaTable.toArray(params);
127+
var args = [];
128+
for (k => info in param_arr) {
129+
var arg: String = info.name.match("%s*([^%s]*)");
130+
args[k] = mangleName(arg);
131+
}
132+
return Some('${ args.join(":Any, ") }:Any');
133+
}
134+
135+
function getReturn(fn_data:FnData):String {
136+
// Since Starfall doesn't have good / consistent docs, we will only have this be typed in whether a function returns something.
137+
return fn_data.returns != null ? "Any" : "Void";
138+
}
139+
140+
/**
141+
SHARED
142+
Generates docs just like these docs here. Returns the realm to avoid repeated code
143+
**/
144+
function genDocs(fn_data:FnData):String {
145+
var realm = unwrap_or_panic(fn_data.realm).toUpperCase(); // SF Really fucked up if this panics
146+
var desc = unwrap_or(fn_data.description, "No description found");
147+
var desc_fixed = desc.gsub("\n","\n\t\t"); // Indented to fit better. Won't support if we ever go outside of one tab.
148+
haxePush('\t/**\n\t\t${realm}\n\t\t$desc_fixed\n\t**/\n');
149+
return realm;
150+
}
151+
152+
function genLibrary( lib_name:String, class_data: DocData ) {
153+
haxePush("// Generated by SFHaxe 0.2.0\npackage sf.library;");
154+
haxePush('\n@:native("${ lib_name=="builtins" ? "_G" : lib_name }") extern class ${upperFirst(lib_name)} {\n');
155+
PairTools.pairsEach( class_data.methods, function(fn_name, fn_data) {
156+
var realm = genDocs(fn_data);
157+
if (realm != "SHARED") {
158+
// CLIENT or SERVER
159+
haxePush('\t#if $realm @:native("$fn_name") public static function ${mangleName(fn_name)}(${ opt_unwrap_or( getArgs(fn_data), "" ) }):${ getReturn(fn_data) };#end\n');
160+
}else{
161+
haxePush('\t@:native("$fn_name") public static function ${mangleName(fn_name)}(${ opt_unwrap_or( getArgs(fn_data), "" ) }):${ getReturn(fn_data) };\n');
162+
}
163+
});
164+
haxePush("}\n\n");
165+
File.write('sf_haxe/Library/$lib_name.txt', haxePop());
166+
}
167+
168+
function genType( type_name:String, class_data: DocData ) {
169+
var abstract_pile: AbstractPile = []; // A list of metamethods that will be made into a separate abstract class
170+
var use_pile = false;
171+
172+
haxePush("// Generated by SFHaxe 0.2.0\npackage sf.type;");
173+
haxePush('\nextern class ${type_name}Data {\n');
174+
PairTools.pairsEach( class_data.methods, function(fn_name, fn_data) {
175+
var op_data = HaxeOps.get(fn_name); // Operator data
176+
if (op_data != null) {
177+
use_pile = true;
178+
abstract_pile.push({
179+
fn_data: fn_data,
180+
fn_name: fn_name,
181+
haxe_op: op_data
182+
});
183+
} else {
184+
// Not an operator
185+
var realm = genDocs(fn_data);
186+
if (realm != "SHARED") {
187+
// CLIENT or SERVER
188+
haxePush('\t#if $realm @:native("$fn_name") public function ${mangleName(fn_name)}(${ opt_unwrap_or( getArgs(fn_data), "") }):${ getReturn(fn_data) };#end\n');
189+
}else{
190+
haxePush('\t@:native("$fn_name") public function ${mangleName(fn_name)}(${ opt_unwrap_or( getArgs(fn_data), "") }):${ getReturn(fn_data) };\n');
191+
}
192+
}
193+
});
194+
haxePush("}\n\n");
195+
196+
// Until Haxe adds support for metamethods on classes, the only way you can do this is through abstracts of that class.
197+
haxePush("@:forward"); // This passes all of the methods to the class.
198+
haxePush('\nextern abstract $type_name(${type_name}Data) {\n');
199+
for (abstract_data in abstract_pile) {
200+
var nargs = abstract_data.haxe_op.nargs;
201+
var haxe_op = abstract_data.haxe_op.mdata;
202+
var fn_name = abstract_data.fn_name;
203+
var args = [];
204+
for (k in 0...nargs) {
205+
// Creates filler arguments for the operator overload.
206+
// a:Any, b:Any, c:Any, d:Any.
207+
// In case some operator that needs 5 inputs is created I guess
208+
args[k] = '${ NativeStringTools.char(65+k) }:Any';
209+
}
210+
genDocs(abstract_data.fn_data);
211+
// TODO: Replace :Any with our return checking, when SF has returns / args documented.
212+
haxePush('\t$haxe_op public function $fn_name(${args.join(", ")}):Any;\n');
213+
}
214+
haxePush("}\n\n");
215+
216+
File.write('sf_haxe/Type/$type_name.txt', haxePop());
217+
}
218+
219+
function genTypes(docs: LuaTable<String, DocData>) {
220+
PairTools.pairsEach(docs, function(classname, classdata){
221+
genType( classname, classdata );
222+
});
223+
}
224+
225+
/**
226+
Generates Haxe Extern Functions to SF libararies. Example: Coroutine class with Coroutine.yield as a static function.
227+
**/
228+
function genLibraries(docs: LuaTable<String, DocData>) {
229+
PairTools.pairsEach(docs, function(classname, classdata){
230+
genLibrary( classname, classdata );
231+
});
232+
}
233+
234+
class HaxeGen {
235+
static function main() {
236+
if(player() != owner())
237+
Sys.exit(0);
238+
Http.get("https://raw.githubusercontent.com/thegrb93/StarfallEx/gh-pages/sf_doc.json", function(body: String) {
239+
var docs: LuaTable<Any, Any> = Json.decode(body);
240+
File.createDir("sf_haxe");
241+
File.createDir("sf_haxe/library");
242+
File.createDir("sf_haxe/type");
243+
genTypes(docs.Types);
244+
genLibraries(docs.Libraries);
245+
// TODO: Generate enums
246+
}, null, null);
247+
}
248+
}
249+
250+
#end

0 commit comments

Comments
 (0)