Browse Source

Merge branch 'power_multireturn_5000' into development

Justin Donaldson 9 years ago
parent
commit
a4543ef939

+ 175 - 5
src/generators/genlua.ml

@@ -226,6 +226,56 @@ let is_dynamic_iterator ctx e =
 	| _ ->
 		false
 
+(*
+	return index of a first element in the list for which `f` returns true
+	TODO: is there some standard function to do that?
+ *)
+let index_of f l =
+	let rec find lst idx =
+		match lst with
+		| [] -> raise Not_found
+		| el :: rest ->
+			if f el then
+				idx
+			else
+				find rest (idx + 1)
+	in
+	find l 0
+
+(* create a __lua__ call *)
+let mk_lua_code com code args t pos =
+	let lua_local = Codegen.ExprBuilder.make_local (alloc_var "__lua__" t_dynamic pos) pos in
+	let code_const = Codegen.ExprBuilder.make_string com code pos in
+	mk (TCall (lua_local, code_const :: args)) t pos
+
+(* create a multi-return boxing call for given expr *)
+let mk_mr_box ctx e =
+	let s_fields =
+		match follow e.etype with
+		| TInst (c,_) ->
+			String.concat ", " (List.map (fun f -> "\"" ^ f.cf_name ^ "\"") c.cl_ordered_fields)
+		| _ -> assert false
+	in
+	add_feature ctx "use._hx_box_mr";
+	add_feature ctx "use._hx_tbl_pack";
+	let code = Printf.sprintf "_hx_box_mr(_hx_tbl_pack({0}), {%s})" s_fields in
+	mk_lua_code ctx.com code [e] e.etype e.epos
+
+(* create a multi-return select call for given expr and field name *)
+let mk_mr_select com e name =
+	let i =
+		match follow e.etype with
+		| TInst (c,_) ->
+			index_of (fun f -> f.cf_name = name) c.cl_ordered_fields
+		| _ ->
+			assert false
+	in
+	if i == 0 then
+	    mk_lua_code com "{0}" [e] e.etype e.epos
+	else
+	    let code = Printf.sprintf "_G.select(%i, {0})" (i + 1) in
+	    mk_lua_code com code [e] e.etype e.epos
+
 (* from genphp *)
 let rec is_string_type t =
 	match follow t with
@@ -476,6 +526,14 @@ and gen_expr ?(local=true) ctx e = begin
 		spr ctx " end )({";
 		concat ctx ", " (fun (f,e) -> print ctx "%s = " (anon_field f); gen_value ctx e) fields;
 		spr ctx "})";
+	| TField ({eexpr = TLocal v}, f) when Meta.has Meta.MultiReturn v.v_meta ->
+		(* field of a multireturn local var is actually just a local var *)
+		let (_, args, pos) =  Meta.get (Meta.Custom ":lua_mr_id") v.v_meta  in
+		(match args with
+		| [(EConst(String(id)), _)] ->
+				spr ctx (id ^ "_" ^ (ident v.v_name) ^ "_" ^ (field_name f));
+		| _ ->
+				assert false);
 	| TField (x,f) ->
 		gen_value ctx x;
 		let name = field_name f in
@@ -551,11 +609,37 @@ and gen_expr ?(local=true) ctx e = begin
 				    gen_value ctx e1;
 				    semicolon ctx;
 
+				| _ when Meta.has Meta.MultiReturn v.v_meta ->
+					(* multi-return var is generated as several vars for unpacking *)
+				    let id = temp ctx in
+				    let temp_expr = (EConst(String(id)), null_pos) in
+				    v.v_meta <- (Meta.Custom ":lua_mr_id", [temp_expr], v.v_pos) :: v.v_meta;
+				    let name = ident v.v_name in
+				    let names =
+						    match follow v.v_type with
+						    | TInst (c, _) ->
+										    List.map (fun f -> id ^ "_" ^name ^ "_" ^ f.cf_name) c.cl_ordered_fields
+						    | _ ->
+								    assert false
+				    in
+				    spr ctx "local ";
+				    spr ctx (String.concat ", " names);
+				    spr ctx " = ";
+				    gen_value ctx e;
+				    semicolon ctx
+
 				| _ ->
 				    if local then
-					spr ctx "local ";
+						spr ctx "local ";
 				    spr ctx (ident v.v_name);
 				    spr ctx " = ";
+
+					(*
+						if it was a multi-return var but it was used as a value itself,
+						we have to box it in an object conforming to a multi-return extern class
+					*)
+					let is_boxed_multireturn = Meta.has (Meta.Custom ":lua_mr_box") v.v_meta in
+					let e = if is_boxed_multireturn then mk_mr_box ctx e else e in
 				    gen_value ctx e;
 				    semicolon ctx;
 		end
@@ -1279,6 +1363,20 @@ and has_continue e =
     with Exit ->
 	true
 
+let check_multireturn ctx c =
+    match c with
+    | _ when Meta.has Meta.MultiReturn c.cl_meta ->
+	    if not c.cl_extern then
+		error "MultiReturns must be externs" c.cl_pos
+	    else if List.length c.cl_ordered_statics > 0 then
+		error "MultiReturns must not contain static fields" c.cl_pos
+		else if (List.exists (fun cf -> match cf.cf_kind with Method _ -> true | _-> false) c.cl_ordered_fields) then
+		    error "MultiReturns must not contain methods" c.cl_pos;
+    | {cl_super = Some(csup,_)} when Meta.has Meta.MultiReturn csup.cl_meta ->
+	    error "Cannot extend a MultiReturn" c.cl_pos
+    | _ -> ()
+
+
 let generate_package_create ctx (p,_) =
 	let rec loop acc = function
 		| [] -> ()
@@ -1615,7 +1713,8 @@ let generate_type ctx = function
 		else if Meta.has Meta.InitPackage c.cl_meta then
 			(match c.cl_path with
 			| ([],_) -> ()
-			| _ -> generate_package_create ctx c.cl_path)
+			| _ -> generate_package_create ctx c.cl_path);
+		check_multireturn ctx c;
 	| TEnumDecl e when e.e_extern ->
 		if Meta.has Meta.LuaRequire e.e_meta && is_directly_used ctx.com e.e_meta then
 		    generate_require ctx e.e_path e.e_meta;
@@ -1673,6 +1772,62 @@ let alloc_ctx com =
 		| _ -> s_path ctx p);
 	ctx
 
+
+let transform_multireturn ctx = function
+	| TClassDecl c ->
+		let transform_field f =
+			match f.cf_expr with
+			| Some e ->
+				let rec loop e =
+					let is_multireturn t =
+						match follow t with
+						| TInst (c, _) when Meta.has Meta.MultiReturn c.cl_meta -> true
+						| _ -> false
+					in
+					match e.eexpr with
+					(*
+						if we found a var declaration initialized by a multi-return call, mark it with @:multiReturn meta,
+						so it will later be generated as multiple locals unpacking the value
+					*)
+					| TVar (v, Some ({ eexpr = TCall _ } as ecall)) when is_multireturn v.v_type ->
+						v.v_meta <- (Meta.MultiReturn,[],v.v_pos) :: v.v_meta;
+						let ecall = Type.map_expr loop ecall in
+						{ e with eexpr = TVar (v, Some ecall) }
+
+					(* if we found a field access for the multi-return call, generate select call *)
+					| TField ({ eexpr = TCall _ } as ecall, f) when is_multireturn ecall.etype ->
+						let ecall = Type.map_expr loop ecall in
+						mk_mr_select ctx.com ecall (field_name f)
+
+					(* if we found a multi-return call used as a value, box it *)
+					| TCall _ when is_multireturn e.etype ->
+						let e = Type.map_expr loop e in
+						mk_mr_box ctx e
+
+					(* if we found a field access for a multi-return local - that's fine, because it'll be generated as a local var *)
+					| TField ({ eexpr = TLocal v}, _) when Meta.has Meta.MultiReturn v.v_meta ->
+						e
+
+					(*
+						if we found usage of local var we previously marked with @:multiReturn as a value itself,
+						remove the @:multiReturn meta and add "box me" meta so it'll be boxed on var initialization
+					*)
+					| TLocal v when Meta.has Meta.MultiReturn v.v_meta ->
+						v.v_meta <- List.filter (fun (m,_,_) -> m <> Meta.MultiReturn) v.v_meta;
+						v.v_meta <- (Meta.Custom ":lua_mr_box", [], v.v_pos) :: v.v_meta;
+						e
+
+					| _ ->
+						Type.map_expr loop e
+				in
+				f.cf_expr <- Some (loop e);
+			| _ -> ()
+		in
+		List.iter transform_field c.cl_ordered_fields;
+		List.iter transform_field c.cl_ordered_statics;
+		Option.may transform_field c.cl_constructor;
+	| _ -> ()
+
 let generate com =
 	let t = Common.timer "generate lua" in
 	let ctx = alloc_ctx com in
@@ -1764,7 +1919,7 @@ let generate com =
 	List.iter (generate_type_forward ctx) com.types; newline ctx;
 
 	(* Generate some dummy placeholders for utility libs that may be required*)
-	println ctx "local _hx_bind, _hx_bit, _hx_staticToInstance, _hx_funcToField, _hx_maxn, _hx_print, _hx_apply_self";
+	println ctx "local _hx_bind, _hx_bit, _hx_staticToInstance, _hx_funcToField, _hx_maxn, _hx_print, _hx_apply_self, _hx_box_mr, _hx_tbl_pack";
 
 	if has_feature ctx "use._bitop" || has_feature ctx "lua.Boot.clamp" then begin
 	    println ctx "pcall(require, 'bit32') pcall(require, 'bit')";
@@ -1781,10 +1936,9 @@ let generate com =
 	List.iter (gen__init__hoist ctx) (List.rev ctx.inits); newline ctx;
 	ctx.inits <- []; (* reset inits after hoist *)
 
+	List.iter (transform_multireturn ctx) com.types;
 	List.iter (generate_type ctx) com.types;
 
-
-
 	(* If we use haxe Strings, patch Lua's string *)
 	if has_feature ctx "use.string" then begin
 	    println ctx "local _hx_string_mt = _G.getmetatable('');";
@@ -1893,6 +2047,22 @@ let generate com =
 	    println ctx "end";
 	end;
 
+	if has_feature ctx "use._hx_box_mr" then begin
+	    println ctx "_hx_box_mr = function(x,nt)";
+	    println ctx "   res = _hx_o({__fields__={}})";
+	    println ctx "   for i,v in ipairs(nt) do";
+	    println ctx "     res[v] = x[i]";
+	    println ctx "   end";
+	    println ctx "   return res";
+	    println ctx "end";
+	end;
+
+	if has_feature ctx "use._hx_tbl_pack" then begin
+	    println ctx "_hx_tbl_pack = function(...)";
+	    println ctx "  return {n=select('#',...),...}";
+	    println ctx "end";
+	end;
+
 
 	List.iter (generate_enumMeta_fields ctx) com.types;
 

+ 1 - 0
src/syntax/ast.ml

@@ -117,6 +117,7 @@ module Meta = struct
 		| Macro
 		| MaybeUsed
 		| MergeBlock
+		| MultiReturn
 		| MultiType
 		| Native
 		| NativeChildren

+ 1 - 0
src/typing/common.ml

@@ -636,6 +636,7 @@ module MetaInfo = struct
 		| Macro -> ":macro",("(deprecated)",[])
 		| MaybeUsed -> ":maybeUsed",("Internally used by DCE to mark fields that might be kept",[Internal])
 		| MergeBlock -> ":mergeBlock",("Merge the annotated block into the current scope",[UsedOn TExpr])
+		| MultiReturn -> ":multiReturn",("Annotates an extern class as the result of multi-return function",[UsedOn TClass; Platform Lua])
 		| MultiType -> ":multiType",("Specifies that an abstract chooses its this-type from its @:to functions",[UsedOn TAbstract; HasParam "Relevant type parameters"])
 		| Native -> ":native",("Rewrites the path of a class or enum during generation",[HasParam "Output type path";UsedOnEither [TClass;TEnum]])
 		| NativeChildren -> ":nativeChildren",("Annotates that all children from a type should be treated as if it were an extern definition - platform native",[Platforms [Java;Cs]; UsedOn TClass])

+ 5 - 5
std/lua/Boot.hx

@@ -354,19 +354,19 @@ class Boot {
 	}
 
 	public static function fieldIterator( o : Table<String,Dynamic>) : Iterator<String> {
-		var tbl = (untyped o.__fields__ != null) ?  o.__fields__ : o;
-		var cur = Lua.pairs(tbl);
+		var tbl : Table<String,String> =  cast (untyped o.__fields__ != null) ?  o.__fields__ : o;
+		var cur = Lua.pairs(tbl).next;
 		var next_valid = function(tbl, val){
 			while (hiddenFields[untyped val] != null){
-				val = cur(tbl, val);
+				val = cur(tbl, val).index;
 			}
 			return val;
 		}
-		var cur_val = next_valid(tbl, cur(tbl, null));
+		var cur_val = next_valid(tbl, cur(tbl, null).index);
 		return {
 			next : function(){
 				var ret = cur_val;
-				cur_val = next_valid(tbl, cur(tbl, cur_val));
+				cur_val = next_valid(tbl, cur(tbl, cur_val).index);
 				return ret;
 			},
 			hasNext : function() return cur_val !=  null

+ 38 - 20
std/lua/Coroutine.hx

@@ -22,40 +22,45 @@
 
 package lua;
 
-import haxe.Constraints.Function;
 import haxe.extern.Rest;
+import haxe.Constraints.Function;
 
 /**
 	Externs for native Lua coroutines.
 **/
 @:native("_G.coroutine")
-extern class Coroutine {
+extern class Coroutine<T:Function> extends Thread {
+	/**
+		Creates a new coroutine, with body `f`. `f` must be a Lua function.
+	**/
+	public static function create<T:Function>(f : T)  : Coroutine<T>;
+
 	/**
-		Creates a new coroutine, with body `f`. `f` must be a Lua function. 
+		Returns the running coroutine plus a boolean, true when the running coroutine is the main one.
 	**/
-	public static function create(f : Function)  : Thread;
+	public static function running() : CoroutineRunning;
 
 	/**
 		Returns the status of coroutine.
 	**/
-	public static function status(c : Coroutine) : ThreadState;
+	public static function status(c : Coroutine<Dynamic>) : CoroutineState;
 
 	/**
-		Starts or continues the execution of coroutine. 
+		Starts or continues the execution of coroutine.
 		The first time you resume a coroutine, it starts running its body.
 		The values `args` are passed as the arguments to the body function.
-		If the coroutine has yielded, `resume` restarts it; 
+		If the coroutine has yielded, `resume` restarts it;
 		the values `args` are passed as the results from the yield.
 
-		If the coroutine runs without any errors, `resume` returns `true` plus any 
-		values passed to `yield` (if the coroutine yields) or any values returned 
-		by the body function (if the coroutine terminates). If there is any error, 
+		If the coroutine runs without any errors, `resume` returns `true` plus any
+		values passed to `yield` (if the coroutine yields) or any values returned
+		by the body function (if the coroutine terminates). If there is any error,
 		`resume` returns `false` plus the error message.
 	**/
-	public static function resume(c : Coroutine, args : Rest<Dynamic>) : Dynamic;
+	public static function resume(c : Coroutine<Dynamic>, args : Rest<Dynamic>) : CoroutineResume;
 
 	/**
-		Suspends the execution of the calling coroutine. 
+		Suspends the execution of the calling coroutine.
 		The coroutine cannot be running a C function, a metamethod, or an iterator.
 		Any arguments to `yield` are passed as extra results to `resume`.
 	**/
@@ -63,21 +68,21 @@ extern class Coroutine {
 
 	/**
 		Creates a new coroutine, with body `f`.
-		Returns a function that resumes the coroutine each time it is called. 
-		Any arguments passed to the function behave as the extra arguments to `resume`. 
-		Returns the same values returned by `resume`, except the first boolean. 
+		Returns a function that resumes the coroutine each time it is called.
+		Any arguments passed to the function behave as the extra arguments to `resume`.
+		Returns the same values returned by `resume`, except the first boolean.
 		In case of error, propagates the error.
 	**/
-	public static function wrap(f : Function) : Thread;
+	public static function wrap<T:Function>(f : T) : Coroutine<T>;
 }
 
 /**
 	A enumerator that describes the output of `Coroutine.status()`.
 **/
 @:enum
-abstract ThreadState(String) {
+abstract CoroutineState(String) {
 	/**
-		If the coroutine is suspended in a call to yield, or if it has not started 
+		If the coroutine is suspended in a call to yield, or if it has not started
 		running yet.
 	**/
 	var Suspended = "suspended";
@@ -88,15 +93,28 @@ abstract ThreadState(String) {
 	var Running = "running";
 
 	/**
-		If the coroutine is active but not running. That is, it has resumed another 
+		If the coroutine is active but not running. That is, it has resumed another
 		coroutine.
 	**/
 	var Normal = "normal";
 
 	/**
-		If the coroutine has finished its body function or if it has stopped with 
+		If the coroutine has finished its body function or if it has stopped with
 		an error.
 	**/
 	var Dead = "dead";
 }
 
+@:multiReturn
+extern class CoroutineResume  {
+	var success  : Bool;
+	var result : Dynamic;
+}
+
+
+@:multiReturn
+extern class CoroutineRunning {
+	var coroutine : Coroutine<Dynamic>;
+	var status    : Bool;
+}
+

+ 2 - 2
std/lua/Debug.hx

@@ -71,7 +71,7 @@ extern class Debug {
 		the current hook function, the current hook mask, and the current hook count 
 		(as set by the `Debug.sethook` function).
 	**/
-	public static function gethook(thread : Coroutine) : Function;
+	public static function gethook(thread : Thread) : Function;
 
 	/**
 		Returns the registry table.
@@ -120,7 +120,7 @@ extern class Debug {
 		@param level (optional) tells at which level to start the traceback. 
 		       default is `1`, the function calling traceback.
 	**/
-	public static function traceback(?thread : Coroutine, ?message : String, ?level : Int) : String;
+	public static function traceback(?thread : Thread, ?message : String, ?level : Int) : String;
 
 	/**
 		Returns a unique identifier (as a light userdata) for the upvalue numbered 

+ 73 - 48
std/lua/Lua.hx

@@ -10,7 +10,7 @@ import haxe.extern.Rest;
 @:native("_G")
 extern class Lua {
 	/**
-		A global variable that holds a string containing the current interpreter 
+		A global variable that holds a string containing the current interpreter
 		version.
 	**/
 	public static var _VERSION : String;
@@ -23,103 +23,103 @@ extern class Lua {
 	public static function getmetatable(tbl:Table<Dynamic,Dynamic>): Table<Dynamic,Dynamic>;
 
 	/**
-		Pops a table from the stack and sets it as the new metatable for the value 
+		Pops a table from the stack and sets it as the new metatable for the value
 		at the given acceptable index.
 	**/
 	public static function setmetatable(tbl:Table<Dynamic,Dynamic>, mtbl: Table<Dynamic, Dynamic>): Void;
 
 	/**
-		Pops a table from the stack and sets it as the new environment for the value 
-		at the given index. If the value at the given index is neither a function nor 
-		a thread nor a userdata, lua_setfenv returns `0`. 
+		Pops a table from the stack and sets it as the new environment for the value
+		at the given index. If the value at the given index is neither a function nor
+		a thread nor a userdata, lua_setfenv returns `0`.
 		Otherwise it returns `1`.
 	**/
 	public static function setfenv(i:Int , tbl:Table<Dynamic, Dynamic>): Void;
 
 	/**
-		Allows a program to traverse all fields of a table. 
-		Its first argument is a table and its second argument is an index in this 
-		table. `next` returns the next index of the table and its associated value. 
+		Allows a program to traverse all fields of a table.
+		Its first argument is a table and its second argument is an index in this
+		table. `next` returns the next index of the table and its associated value.
 		When `i` is `null`, `next` returns an initial index and its associated value.
 		When called with the last index, or with `null` in an empty table, `next`
-		returns `null`.  In particular, you can use `next(t)` to check whether a 
+		returns `null`.  In particular, you can use `next(t)` to check whether a
 		table is empty.
 
-		The order in which the indices are enumerated is not specified, even for 
-		numeric indices. (To traverse a table in numeric order, use a numerical for 
+		The order in which the indices are enumerated is not specified, even for
+		numeric indices. (To traverse a table in numeric order, use a numerical for
 		or the `ipairs` function).
 
-		The behavior of next is undefined if, during the traversal, any value 
-		to a non-existent field in the table is assigned. Existing fields may 
+		The behavior of next is undefined if, during the traversal, any value
+		to a non-existent field in the table is assigned. Existing fields may
 		however be modified. In particular, existing fields may be cleared.
 	**/
-	public static function next<T>(k:Table<Dynamic, T>, ?i : Null<Int>): T;
+	public static function next<K,V>(k:Table<K, V>, ?i : V): NextResult<K,V>;
 
 	/**
-		Receives an argument of any type and converts it to a string in a reasonable 
-		format. 
-		
+		Receives an argument of any type and converts it to a string in a reasonable
+		format.
+
 		For complete control of how numbers are converted, use`NativeStringTools.format`.
 	**/
 	public static function tostring(v:Dynamic): String;
 
-	public static function ipairs<T>(t:Table<Int,T>): Dynamic->Int->T;
+	public static function ipairs<T>(t:Table<Int,T>): PairsResult<Int,T>;
 
-	public static function pairs<A,B>(t:Table<A,B>): Dynamic->Dynamic->A;
+	public static function pairs<K,V>(t:Table<K,V>): PairsResult<K,V> ;
 
 	public static function require(module:String) : Dynamic;
 
 	/**
-		Converts the Lua value at the given acceptable base to `Int`. 
-		The Lua value must be a number or a string convertible to a number, 
+		Converts the Lua value at the given acceptable base to `Int`.
+		The Lua value must be a number or a string convertible to a number,
 		otherwise `tonumber` returns `0`.
 	**/
 	public static function tonumber(str:String, ?base:Int): Int;
 
 	/**
-		Returns the Lua type of its only argument as a string. 
+		Returns the Lua type of its only argument as a string.
 		The possible results of this function are:
-		
+
 		 * `"nil"` (a string, not the Lua value nil),
 		 * `"number"`
 		 * `"string"`
 		 * `"boolean"`
-		 * `"table"` 
-		 * `"function"` 
+		 * `"table"`
+		 * `"function"`
 		 * `"thread"`
 		 * `"userdata"`
 	**/
 	public static function type(v:Dynamic) : String;
 
 	/**
-		Receives any number of arguments, and prints their values to stdout, 
-		using the tostring function to convert them to strings. 
-		`print` is not intended for formatted output, but only as a quick way to show 
-		a value, typically for debugging. 
-		
+		Receives any number of arguments, and prints their values to stdout,
+		using the tostring function to convert them to strings.
+		`print` is not intended for formatted output, but only as a quick way to show
+		a value, typically for debugging.
+
 		For complete control of how numbers are converted, use `NativeStringTools.format`.
 	**/
-	public static function print(v:Dynamic) : Void;
+	public static function print(v:haxe.extern.Rest<Dynamic>) : Void;
 
 	/**
 		If `n` is a number, returns all arguments after argument number `n`.
-		Otherwise, `n` must be the string `"#"`, and select returns the total 
+		Otherwise, `n` must be the string `"#"`, and select returns the total
 		number of extra arguments it received.
 	**/
 	public static function select(n:Dynamic, rest:Rest<Dynamic>) : Dynamic;
 
 	/**
-		Gets the real value of `table[index]`, without invoking any metamethod. 
+		Gets the real value of `table[index]`, without invoking any metamethod.
 	**/
 	public static function rawget<K,V>(t:Table<K,V>, k:K) : V;
 
 	/**
-		Sets the real value of `table[index]` to value, without invoking any metamethod. 
+		Sets the real value of `table[index]` to value, without invoking any metamethod.
 	**/
 	public static function rawset<K,V>(t:Table<K,V>, k:K, v:V) : Void;
 
 	/**
-		This function is a generic interface to the garbage collector. 
+		This function is a generic interface to the garbage collector.
 		It performs different functions according to its first argument.
 	**/
 	public static function collectgarbage(opt:CollectGarbageOption, ?arg:Int) : Int;
@@ -130,15 +130,15 @@ extern class Lua {
 		when absent, it defaults to "assertion failed!"
 	**/
 	public static function assert<T>(v:T, ?message:String) : T;
-	
+
 	/**
-		Loads and runs the given file. 
+		Loads and runs the given file.
 	**/
 	public static function dofile(filename:String) : Void;
 
 	/**
-		Generates a Lua error. The error message (which can actually be a Lua value 
-		of any type) must be on the stack top. This function does a long jump, 
+		Generates a Lua error. The error message (which can actually be a Lua value
+		of any type) must be on the stack top. This function does a long jump,
 		and therefore never returns.
 	**/
 	public static function error(message:String, ?level:Int) : Void;
@@ -146,32 +146,32 @@ extern class Lua {
 	/**
 		Calls a function in protected mode.
 	**/
-	public static function pcall(f:Function, rest:Rest<Dynamic>) : Bool;
+	public static function pcall(f:Function, rest:Rest<Dynamic>) : PCallResult;
 
 	/**
-		Returns `true` if the two values in acceptable indices `v1` and `v2` are 
-		primitively equal (that is, without calling metamethods). 
-		Otherwise returns `false`. 
+		Returns `true` if the two values in acceptable indices `v1` and `v2` are
+		primitively equal (that is, without calling metamethods).
+		Otherwise returns `false`.
 		Also returns `false` if any of the indices are non valid.
 	**/
 	public static function rawequal(v1:Dynamic, v2:Dynamic) : Bool;
 
 	/**
-		This function is similar to pcall, except that you can set a new error 
+		This function is similar to pcall, except that you can set a new error
 		handler.
 	**/
-	public static function xpcall(f:Function, msgh:Function, rest:Rest<Dynamic> ) : Bool;
+	public static function xpcall(f:Function, msgh:Function, rest:Rest<Dynamic> ) : PCallResult;
 
 	/**
-		Loads the chunk from file filename or from the standard input if no filename 
+		Loads the chunk from file filename or from the standard input if no filename
 		is given.
 	**/
-	public static function loadfile(filename:String) : Void;
+	public static function loadfile(filename:String) : LoadResult;
 
 	/**
 		Loads the chunk from given string.
 	**/
-	public static function loadstring(code:String) : Void;
+	public static function load(code:haxe.extern.EitherType<String,Void->String>) : LoadResult;
 
 }
 
@@ -188,3 +188,28 @@ abstract CollectGarbageOption(String) {
 	var SetPause = "setpause";
 	var SetStepMul = "setstepmul";
 }
+
+@:multiReturn
+extern class PCallResult {
+	var status : Bool;
+	var error : String;
+}
+
+@:multiReturn
+extern class NextResult<K,V> {
+	var index : K;
+	var value : V;
+}
+
+@:multiReturn
+extern class PairsResult<K,V> {
+	var next : Table<K,V>->K->NextResult<K,V>;
+	var table : Table<K,V>;
+	var index : V;
+}
+
+@:multiReturn
+extern class LoadResult {
+	var func : Function;
+	var message : String;
+}

+ 13 - 2
std/lua/NativeStringTools.hx

@@ -21,6 +21,8 @@ extern class NativeStringTools {
 	**/
 	public static function char(codes: haxe.extern.Rest<Int>): String;
 
+
+	// TODO: make a note about handling matched groups with multireturn
 	/**
 		Returns the substring of `str` that starts at `start` and continues until `end`; 
 		`start` and `end` can be negative. If `end` is absent, then it is assumed to be 
@@ -29,7 +31,7 @@ extern class NativeStringTools {
 		with length `end`, and `sub(str, -end)` returns a suffix of `str` with 
 		length `start`.
 	**/
-	public static function sub(str : String, start : Int, ?end : Int): String;
+	public static function sub(str : String, start : Int, ?end : Int): StringSub;
 
 	/**
 		Returns the character code at position `index` of `str`.
@@ -49,7 +51,7 @@ extern class NativeStringTools {
 		       a plain "find substring" operation, with no characters in pattern 
 		       being considered "magic". Note that if plain is given, then `start` must be given as well.
 	**/
-	public static function find(str : String, target : String, ?start : Int, ?plain : Bool): Int;
+	public static function find(str : String, target : String, ?start : Int, ?plain : Bool): StringFind;
 
 	/**
 		Returns the internal numerical codes of the characters `str[index]`.
@@ -127,3 +129,12 @@ extern class NativeStringTools {
 	public static function dump(d:Dynamic) : Dynamic;
 }
 
+@:multiReturn extern class StringFind {
+	var begin : Int;
+	var end : Int;
+}
+
+@:multiReturn extern class StringSub {
+	var match : String;
+	var count : Int;
+}

+ 4 - 4
std/lua/_std/EReg.hx

@@ -59,12 +59,12 @@ class EReg {
 	public function matched( n : Int ) : String {
 		if (m[1] == null || n < 0) throw "EReg::matched";
 		else if (n == 0) {
-			var k =  NativeStringTools.sub(s, m[1], m[2]);
+			var k =  NativeStringTools.sub(s, m[1], m[2]).match;
 			return k;
 		} else if (Std.is(m[3], lua.Table)){
 			var mn = 2 * (n - 1);
 			if (Std.is(untyped m[3][mn+1], Bool)) return null;
-			return NativeStringTools.sub(s, untyped m[3][mn + 1], untyped m[3][mn + 2]);
+			return NativeStringTools.sub(s, untyped m[3][mn + 1], untyped m[3][mn + 2]).match;
 		} else {
 			throw "EReg:matched";
 		}
@@ -72,12 +72,12 @@ class EReg {
 
 	public function matchedLeft() : String {
 		if( m[1] == null ) throw "No string matched";
-		return NativeStringTools.sub(s, 1, m[1]-1);
+		return NativeStringTools.sub(s, 1, m[1]-1).match;
 	}
 
 	public function matchedRight() : String {
 		if( m[1] == null ) throw "No string matched";
-		return NativeStringTools.sub(s, m[2]+1);
+		return NativeStringTools.sub(s, m[2]+1).match;
 	}
 
 	public function matchedPos() : { pos : Int, len : Int } {

+ 8 - 8
std/lua/_std/String.hx

@@ -47,7 +47,7 @@ class String {
 	public function indexOf( str : String, ?startIndex : Int ) : Int {
 		if (startIndex == null) startIndex = 1;
 		else startIndex += 1;
-		var r = NativeStringTools.find(this, str, startIndex, true);
+		var r = NativeStringTools.find(this, str, startIndex, true).begin;
 		if (r != null && r > 0) return r-1;
 		else return -1;
 	}
@@ -70,7 +70,7 @@ class String {
 		while (idx != null){
 			var newidx = 0;
 			if (delimiter.length > 0){
-				newidx = NativeStringTools.find(this, delimiter, idx, true);
+				newidx = NativeStringTools.find(this, delimiter, idx, true).begin;
 			} else if (idx >= this.length){
 				newidx = null;
 			} else {
@@ -78,11 +78,11 @@ class String {
 			}
 
 			if (newidx != null){
-				var match = NativeStringTools.sub(this, idx, newidx-1);
+				var match = NativeStringTools.sub(this, idx, newidx-1).match;
 				ret.push(match);
 				idx = newidx + delimiter.length;
 			} else {
-				ret.push(NativeStringTools.sub(this,idx,NativeStringTools.len(this)));
+				ret.push(NativeStringTools.sub(this,idx,NativeStringTools.len(this)).match);
 				idx = null;
 			}
 		}
@@ -98,9 +98,9 @@ class String {
 		if (startIndex < 0) startIndex = 0;
 		if (endIndex < startIndex) {
 			// swap the index positions
-			return NativeStringTools.sub(this, endIndex+1, startIndex);
+			return NativeStringTools.sub(this, endIndex+1, startIndex).match;
 		} else {
-			return NativeStringTools.sub(this, startIndex+1, endIndex);
+			return NativeStringTools.sub(this, startIndex+1, endIndex).match;
 		}
 	}
 
@@ -108,7 +108,7 @@ class String {
 		return NativeStringTools.len(this);
 	}
 	public function charAt( index : Int) : String {
-		return NativeStringTools.sub(this,index+1, index+1);
+		return NativeStringTools.sub(this,index+1, index+1).match;
 	}
 	public function charCodeAt( index : Int) : Null<Int> {
 		return NativeStringTools.byte(this,index+1);
@@ -119,7 +119,7 @@ class String {
 		else if (len < 0) len = length + len;
 		if (pos < 0) pos = length + pos;
 		if (pos < 0) pos = 0;
-		return NativeStringTools.sub(this, pos + 1, pos+len);
+		return NativeStringTools.sub(this, pos + 1, pos+len).match;
 	}
 
 	public inline static function fromCharCode( code : Int ) : String {

+ 1 - 1
std/lua/_std/Sys.hx

@@ -65,7 +65,7 @@ class Sys {
 	}
 
 	public static function systemName() : String {
-		switch(Package.config.sub(1,1)){
+		switch(Package.config.sub(1,1).match){
 			case "/" : {
 				var f = Lua.assert(lua.Io.popen("uname"));
 				var s = Lua.assert(f.read(All));

+ 3 - 0
tests/unit/src/unit/Test.hx

@@ -328,6 +328,9 @@ class Test {
 			#if java
 			new TestJava(),
 			#end
+			#if lua
+			new TestLua(),
+			#end
 			#if python
 			new TestPython(),
 			#end

+ 29 - 0
tests/unit/src/unit/TestLua.hx

@@ -0,0 +1,29 @@
+package unit;
+
+class TestLua extends Test {
+	function testMultiReturnWrap(){
+		var multi : Multi = untyped MultiCall.doit();
+		var l = lua.Lua.type(multi);
+		// test that the original multi variable was wrapped
+		eq(lua.Lua.type(untyped __lua__("multi")), "table");
+		eq(l, "table");
+	}
+	function testMultiReturnValue(){
+		var multi : Multi = untyped MultiCall.doit();
+		var l = lua.Lua.type(multi.b);
+		// test that the original multi wrapped variable was never created
+		eq(untyped __lua__("multi"), null);
+		eq(l, "string");
+	}
+}
+
+@:multiReturn extern class Multi {
+	var a : Int;
+	var b : String;
+}
+
+class MultiCall {
+	public static function doit() : Dynamic {
+		return untyped __lua__("1,'hi'");	
+	}
+}