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\n package 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\n package sf.type;" );
173+ haxePush (' \n extern 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 (' \n extern 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