Przeglądaj źródła

TestEReg passing

Nicolas Cannasse 9 lat temu
rodzic
commit
0dfae87e0b
4 zmienionych plików z 285 dodań i 129 usunięć
  1. 100 71
      genhl.ml
  2. 63 57
      std/hl/_std/EReg.hx
  3. 1 1
      std/hl/_std/String.hx
  4. 121 0
      std/hl/_std/StringBuf.hx

+ 100 - 71
genhl.ml

@@ -552,7 +552,7 @@ let rec to_type ctx t =
 	| TLazy f ->
 		to_type ctx (!f())
 	| TFun (args, ret) ->
-		HFun (List.map (fun (_,_,t) -> to_type ctx t) args, to_type ctx ret)
+		HFun (List.map (fun (_,o,t) -> to_type ctx (if o then ctx.com.basic.tnull t else t)) args, to_type ctx ret)
 	| TAnon a when (match !(a.a_status) with Statics _ | EnumStatics _  -> true | _ -> false) ->
 		HType
 	| TAnon a ->
@@ -825,13 +825,13 @@ let alloc_tmp ctx t =
 	DynArray.add ctx.m.mregs.arr t;
 	rid
 
+let current_pos ctx =
+	DynArray.length ctx.m.mops
+
 let op ctx o =
 	DynArray.add ctx.m.mdebug ctx.m.mcurpos;
 	DynArray.add ctx.m.mops o
 
-let current_pos ctx =
-	DynArray.length ctx.m.mops
-
 let jump ctx f =
 	let pos = current_pos ctx in
 	op ctx (OJAlways (-1)); (* loop *)
@@ -1166,8 +1166,10 @@ and jump_expr ctx e jcond =
 		let r = eval_to ctx e HBool in
 		jump ctx (fun i -> if jcond then OJTrue (r,i) else OJFalse (r,i))
 
-and eval_args ctx el t =
-	List.map2 (fun e t -> eval_to ctx e t) el (match t with HFun (args,_) -> args | HDyn -> List.map (fun _ -> HDyn) el | _ -> assert false)
+and eval_args ctx el t p =
+	let rl = List.map2 (fun e t -> eval_to ctx e t) el (match t with HFun (args,_) -> args | HDyn -> List.map (fun _ -> HDyn) el | _ -> assert false) in
+	set_curpos ctx p;
+	rl
 
 and eval_null_check ctx e =
 	let r = eval_expr ctx e in
@@ -1263,7 +1265,7 @@ and eval_expr ctx e =
 			| None -> assert false
 			| Some f ->
 				let r = alloc_tmp ctx HVoid in
-				let el = eval_args ctx el (to_type ctx f.cf_type) in
+				let el = eval_args ctx el (to_type ctx f.cf_type) e.epos in
 				op ctx (OCallN (r, alloc_fid ctx csup f, 0 :: el));
 				r
 			)
@@ -1513,7 +1515,7 @@ and eval_expr ctx e =
 			| _ -> ec.etype
 		) in
 		let tfun = to_type ctx real_type in
-		let el() = eval_args ctx el tfun in
+		let el() = eval_args ctx el tfun e.epos in
 		let ret = alloc_tmp ctx (match tfun with HFun (_,r) -> r | _ -> HDyn) in
 		(match get_access ctx ec with
 		| AStaticFun f ->
@@ -1594,7 +1596,7 @@ and eval_expr ctx e =
 		| Some { cf_expr = None } -> error (s_type_path c.cl_path ^ " does not have a constructor") e.epos
 		| Some ({ cf_expr = Some { eexpr = TFunction({ tf_expr = { eexpr = TBlock([]) } }) } }) when el = [] -> ()
 		| Some ({ cf_expr = Some cexpr } as constr) ->
-			let rl = eval_args ctx el (to_type ctx cexpr.etype) in
+			let rl = eval_args ctx el (to_type ctx cexpr.etype) e.epos in
 			let ret = alloc_tmp ctx HVoid in
 			let g = alloc_fid ctx c constr in
 			op ctx (match rl with
@@ -2130,6 +2132,12 @@ and gen_assign_op ctx acc e1 f =
 		let r = f r in
 		op ctx (OSetGlobal (g,r));
 		r
+	| ACaptured idx ->
+		let r = alloc_tmp ctx (to_type ctx e1.etype) in
+		op ctx (OEnumField (r, ctx.m.mcaptreg, 0, idx));
+		let r = f r in
+		op ctx (OSetEnumField (ctx.m.mcaptreg,idx,r));
+		r
 	| AArray (ra,at,ridx) ->
 		(match at with
 		| HDyn ->
@@ -2245,6 +2253,8 @@ and make_fun ?gen_content ctx fidx f cthis cparent =
 
 	ctx.m <- method_context fidx (to_type ctx f.tf_type) capt;
 
+	set_curpos ctx f.tf_expr.epos;
+
 	let tthis = (match cthis with
 	| None -> None
 	| Some c ->
@@ -2256,7 +2266,7 @@ and make_fun ?gen_content ctx fidx f cthis cparent =
 	let rcapt = if has_captured_vars && cparent <> None then Some (alloc_tmp ctx capt.c_type) else None in
 
 	let args = List.map (fun (v,o) ->
-		let r = alloc_reg ctx v in
+		let r = alloc_reg ctx (if o = None then v else { v with v_type = ctx.com.basic.tnull v.v_type }) in
 		rtype ctx r
 	) f.tf_args in
 
@@ -2273,17 +2283,34 @@ and make_fun ?gen_content ctx fidx f cthis cparent =
 		(match o with
 		| None | Some TNull -> ()
 		| Some c ->
-			op ctx (OJNotNull (r,1));
-			match c with
+			op ctx (OJNotNull (r,2));
+			(match c with
 			| TNull | TThis | TSuper -> assert false
-			| TInt i -> op ctx (OInt (r, alloc_i32 ctx i))
-			| TFloat s -> op ctx (OFloat (r, alloc_float ctx (float_of_string s)))
-			| TBool b -> op ctx (OBool (r, b))
-			| TString s -> assert false (* TODO *)
+			| TInt i ->
+				let tmp = alloc_tmp ctx HI32 in
+				op ctx (OInt (tmp, alloc_i32 ctx i));
+				op ctx (OToDyn (r, tmp));
+			| TFloat s ->
+				let tmp = alloc_tmp ctx HF64 in
+				op ctx (OFloat (tmp, alloc_float ctx (float_of_string s)));
+				op ctx (OToDyn (r, tmp));
+			| TBool b ->
+				let tmp = alloc_tmp ctx HBool in
+				op ctx (OBool (tmp, b));
+				op ctx (OToDyn (r, tmp));
+			| TString s ->
+				assert false);
+			(* if optional but not null, turn into a not nullable here *)
+			let vt = to_type ctx v.v_type in
+			if not (is_nullable vt) then begin
+				let t = alloc_tmp ctx vt in
+				ctx.m.mregs.map <- PMap.add v.v_id t ctx.m.mregs.map;
+				op ctx (OSafeCast (t,r));
+			end;
 		);
 		if v.v_capture then begin
 			let index = (try PMap.find v.v_id capt.c_map with Not_found -> assert false) in
-			op ctx (OSetEnumField (ctx.m.mcaptreg, index, r));
+			op ctx (OSetEnumField (ctx.m.mcaptreg, index, alloc_reg ctx v));
 		end
 	) f.tf_args;
 
@@ -2947,6 +2974,7 @@ let interp code =
 	let functions = Array.create (Array.length code.functions + Array.length code.natives) (FNativeFun ("",(fun _ -> assert false),HDyn)) in
 	let cached_protos = Hashtbl.create 0 in
 	let func f = Array.unsafe_get functions f in
+	let streof s = try String.sub s 0 (String.index s '\000') with Not_found -> s in
 
 	let stack = ref [] in
 	let exc_stack = ref [] in
@@ -2965,6 +2993,7 @@ let interp code =
 
 	let error msg = raise (Runtime_error msg) in
 	let throw v = exc_stack := []; raise (InterpThrow v) in
+	let throw_msg msg = throw (VDyn (VBytes (msg ^ "\x00"),HBytes)) in
 
 	let hash_cache = Hashtbl.create 0 in
 
@@ -3047,7 +3076,7 @@ let interp code =
 			(match get_method o.oproto.pclass "__string" with
 			| None -> "#" ^ o.oproto.pclass.pname
 			| Some f -> vstr (fcall (func f) [v]) HBytes)
-		| VBytes b -> (if String.length b > 0 && String.get b (String.length b - 1) = '\x00' then String.sub b 0 (String.length b - 1) else b)
+		| VBytes b -> streof b
 		| VClosure (f,_) -> fstr f
 		| VArray (a,t) -> "[" ^ String.concat ", " (Array.to_list (Array.map (fun v -> vstr v t) a)) ^ "]"
 		| VUndef -> "undef"
@@ -3079,6 +3108,8 @@ let interp code =
 				f args
 			with InterpThrow v ->
 				raise (InterpThrow v)
+			| Failure msg ->
+				throw_msg msg
 			| e ->
 				error (Printexc.to_string e)
 
@@ -3227,7 +3258,7 @@ let interp code =
 			let rec loop args fargs =
 				match args, fargs with
 				| [], [] -> []
-				| _, [] -> error (Printf.sprintf "Too many arguments (%s) != (%s)" (String.concat "," (List.map (fun (v,_) -> vstr_d v) full_args)) (String.concat "," (List.map tstr full_fargs)))
+				| _, [] -> throw_msg (Printf.sprintf "Too many arguments (%s) != (%s)" (String.concat "," (List.map (fun (v,_) -> vstr_d v) full_args)) (String.concat "," (List.map tstr full_fargs)))
 				| (v,t) :: args, ft :: fargs -> dyn_cast v t ft :: loop args fargs
 				| [], _ :: _ -> default ft :: loop args fargs
 			in
@@ -3684,7 +3715,7 @@ let interp code =
 					if i >= 0 && i < Array.length indexes then pos := !pos + indexes.(i)
 				| _ -> assert false)
 			| ONullCheck r ->
-				if get r = VNull then error "Null access"
+				if get r = VNull then throw_msg "Null access"
 			| OTrap (r,j) ->
 				let target = !pos + j in
 				traps := (r,target) :: !traps
@@ -3721,8 +3752,6 @@ let interp code =
 	in
 	let int = Int32.to_int in
 	let to_int i = VInt (Int32.of_int i) in
-	let string s = String.sub s 0 (String.length s - 1) in (* chop last \0 which is not needed in ocaml *)
-	let streof s = try String.sub s 0 (String.index s '\000') with Not_found -> s in
 	let load_native lib name t =
 		let unresolved() = (fun args -> error ("Unresolved native " ^ lib ^ "@" ^ name)) in
 		let f = (match lib with
@@ -3777,7 +3806,9 @@ let interp code =
 			| "utf8pos" ->
 				(function
 				| [VBytes b; VInt start; VInt len] ->
-					to_int (UTF8.length (String.sub b (int start) (int len)))
+					let s = int start in
+					let b = streof b in
+					to_int (UTF8.nth (String.sub b s (String.length b - s)) (int len))
 				| _ -> assert false)
 			| "byteslength" ->
 				(function
@@ -3838,7 +3869,7 @@ let interp code =
 				| _ -> assert false)
 			| "safe_cast" ->
 				(function
-				| [v;VType t] -> if is_compatible v t then v else error ("Cannot cast " ^ vstr_d v ^ " to " ^ tstr t);
+				| [v;VType t] -> if is_compatible v t then v else throw_msg ("Cannot cast " ^ vstr_d v ^ " to " ^ tstr t);
 				| _ -> assert false)
 			| "hballoc" ->
 				(function
@@ -4011,7 +4042,7 @@ let interp code =
 						| 'm' -> () (* always ON ? *)
 						| 'i' -> case_sensitive := false
 						| c -> failwith ("Unsupported regexp option '" ^ String.make 1 c ^ "'")
-					) (ExtString.String.explode (string opt));
+					) (ExtString.String.explode (streof opt));
 					let buf = Buffer.create 0 in
 					let rec loop prev esc = function
 						| [] -> ()
@@ -4020,9 +4051,10 @@ let interp code =
 							| 'n' -> Buffer.add_char buf '\n'
 							| 'r' -> Buffer.add_char buf '\r'
 							| 't' -> Buffer.add_char buf '\t'
+							| 's' -> Buffer.add_string buf "[ \t\r\n]"
 							| 'd' -> Buffer.add_string buf "[0-9]"
 							| '\\' -> Buffer.add_string buf "\\\\"
-							| '(' | ')' -> Buffer.add_char buf c
+							| '(' | ')' | '{' | '}' -> Buffer.add_char buf c
 							| '1'..'9' | '+' | '$' | '^' | '*' | '?' | '.' | '[' | ']' ->
 								Buffer.add_char buf '\\';
 								Buffer.add_char buf c;
@@ -4043,7 +4075,7 @@ let interp code =
 								Buffer.add_char buf c;
 								loop c false l
 					in
-					loop '\000' false (ExtString.String.explode (string str));
+					loop '\000' false (ExtString.String.explode (streof str));
 					let str = Buffer.contents buf in
 					let r = {
 						r = if !case_sensitive then Str.regexp str else Str.regexp_case_fold str;
@@ -4053,50 +4085,47 @@ let interp code =
 					VAbstract (AReg r)
 				| _ ->
 					assert false);
-		| "regexp_match" ->
-			(function
-			| [VAbstract (AReg r);VBytes str;VInt pos;VInt len] ->
-				let str = string str and pos = int pos and len = int len in
-				let nstr, npos, delta = (if len = String.length str - pos then str, pos, 0 else String.sub str pos len, 0, pos) in
-				(try
-					ignore(Str.search_forward r.r nstr npos);
-					let rec loop n =
-						if n = 9 then
-							[]
-						else try
-							(Some (Str.group_beginning n + delta, Str.group_end n + delta)) :: loop (n + 1)
-						with Not_found ->
-							None :: loop (n + 1)
-						| Invalid_argument _ ->
-							[]
-					in
-					r.r_string <- str;
-					r.r_groups <- Array.of_list (loop 0);
-					VBool true;
-				with Not_found ->
-					VBool false)
-			| _ -> assert false);
-		| "regexp_matched_pos" ->
-			(function
-			| [VAbstract (AReg r); VInt n; VRef (regs,rlen,HI32)] ->
-				let n = int n in
-				(match (try r.r_groups.(n) with _ -> failwith ("Invalid group " ^ string_of_int n)) with
-				| None -> to_int (-1)
-				| Some (pos,pend) -> regs.(rlen) <- to_int (pend - pos); to_int pos)
-			| _ -> assert false)
-
-(*		"regexp_matched", Fun2 (fun r n ->
-			match r, n with
-			| VAbstract (AReg r), VInt n ->
-				(match (try r.r_groups.(n) with _ -> failwith ("Invalid group " ^ string_of_int n)) with
-				| None -> VNull
-				| Some (pos,pend) -> VString (String.sub r.r_string pos (pend - pos)))
-			| _ -> error()
-		);
-		(* regexp_replace : not used by Haxe *)
-		(* regexp_replace_all : not used by Haxe *)
-		(* regexp_replace_fun : not used by Haxe *)
-	]*)
+			| "regexp_match" ->
+				(function
+				| [VAbstract (AReg r);VBytes str;VInt pos;VInt len] ->
+					let str = streof str and pos = int pos and len = int len in
+					let nstr, npos, delta = (if len = String.length str - pos then str, pos, 0 else String.sub str pos len, 0, pos) in
+					(try
+						ignore(Str.search_forward r.r nstr npos);
+						let rec loop n =
+							if n = 9 then
+								[]
+							else try
+								(Some (Str.group_beginning n + delta, Str.group_end n + delta)) :: loop (n + 1)
+							with Not_found ->
+								None :: loop (n + 1)
+							| Invalid_argument _ ->
+								[]
+						in
+						r.r_string <- str;
+						r.r_groups <- Array.of_list (loop 0);
+						VBool true;
+					with Not_found ->
+						VBool false)
+				| _ -> assert false);
+			| "regexp_matched_pos" ->
+				(function
+				| [VAbstract (AReg r); VInt n; VRef (regs,rlen,HI32)] ->
+					let n = int n in
+					(match (try r.r_groups.(n) with _ -> failwith ("Invalid group " ^ string_of_int n)) with
+					| None -> to_int (-1)
+					| Some (pos,pend) -> regs.(rlen) <- to_int (pend - pos); to_int pos)
+				| _ -> assert false)
+			| "regexp_matched" ->
+				(function
+				| [VAbstract (AReg r); VInt n; VRef (regs,rlen,HI32)] ->
+					let n = int n in
+					(match (try r.r_groups.(n) with _ -> failwith ("Invalid group " ^ string_of_int n)) with
+					| None -> VNull
+					| Some (pos,pend) ->
+						regs.(rlen) <- to_int (pend - pos);
+						VBytes (String.sub r.r_string pos (pend - pos)))
+				| _ -> assert false)
 			| _ ->
 				unresolved())
 		| _ ->

+ 63 - 57
std/hl/_std/EReg.hx

@@ -70,11 +70,16 @@ private typedef ERegValue = hl.types.NativeAbstract<"ereg">;
 		var len = 0;
 		var pos = regexp_matched_pos(r, 0, new hl.types.Ref(len));
 		if( pos < 0 ) return null;
-		return { pos : pos, len : len };
+		return { pos : last.bytes.utf8Length(0,pos), len : last.bytes.utf8Length(pos,len) };
 	}
 
 	public function matchSub( s : String, pos : Int, len : Int = -1):Bool {
-		var p = regexp_match(r, s.bytes, pos, len < 0 ? s.length - pos : len);
+		if( pos < 0 ) pos = 0;
+		if( pos > s.length ) pos = s.length;
+		if( len < 0 || pos + len > s.length ) len = s.length - pos;
+		var bpos = pos == 0 ? 0 : s.bytes.utf8Pos(0, pos);
+		var blen = pos + len == s.length ? s.size - bpos : s.bytes.utf8Pos(bpos, len);
+		var p = regexp_match(r, s.bytes, bpos, blen);
 		if( p )
 			last = s;
 		else
@@ -83,65 +88,68 @@ private typedef ERegValue = hl.types.NativeAbstract<"ereg">;
 	}
 
 	public function split( s : String ) : Array<String> {
-		/*var pos = 0;
-		var len = s.length;
+		var pos = 0;
 		var a = new Array();
 		var first = true;
+		var sbytes = s.bytes;
+		var ssize = s.size;
 		do {
-			if( !regexp_match(r,s.__s,pos,len) )
+			if( !regexp_match(r,sbytes,pos,ssize) )
 				break;
-			var p = regexp_matched_pos(r,0);
-			if( p.len == 0 && !first ) {
-				if( p.pos == s.length )
+			var msize = 0;
+			var mpos = regexp_matched_pos(r, 0, new hl.types.Ref(msize));
+			if( msize == 0 && !first ) {
+				if( mpos == s.size )
 					break;
-				p.pos += 1;
+				mpos++;
 			}
-			a.push(s.substr(pos,p.pos - pos));
-			var tot = p.pos + p.len - pos;
+			a.push(s.subBytes(pos,mpos - pos));
+			var tot = mpos + msize - pos;
 			pos += tot;
-			len -= tot;
+			ssize -= tot;
 			first = false;
 		} while( global );
-		a.push(s.substr(pos,len));
-		return a;*/
-		throw "TODO";
-		return null;
+		a.push(s.subBytes(pos,ssize));
+		return a;
 	}
 
-	public function replace( s : String, by : String ) : String {
-		/*var b = new StringBuf();
+	public function replace( s : String, by : String ) : String @:privateAccess {
+		var b = new StringBuf();
 		var pos = 0;
-		var len = s.length;
+		var sbytes = s.bytes;
+		var size = s.size;
 		var a = by.split("$");
 		var first = true;
 		do {
-			if( !regexp_match(r,s.__s,pos,len) )
+			if( !regexp_match(r,sbytes,pos,size) )
 				break;
-			var p = regexp_matched_pos(r,0);
-			if( p.len == 0 && !first ) {
-				if( p.pos == s.length )
+			var msize = 0;
+			var mpos = regexp_matched_pos(r,0,new hl.types.Ref(msize));
+			if( msize == 0 && !first ) {
+				if( mpos == s.size )
 					break;
-				p.pos += 1;
+				mpos++;
 			}
-			b.addSub(s,pos,p.pos-pos);
+			b.__add(sbytes,pos,mpos-pos);
 			if( a.length > 0 )
 				b.add(a[0]);
 			var i = 1;
 			while( i < a.length ) {
 				var k = a[i];
-				var c = k.charCodeAt(0);
+				var c = StringTools.fastCodeAt(k, 0);
 				// 1...9
 				if( c >= 49 && c <= 57 ) {
-					var p = try regexp_matched_pos(r,Std.int(c)-48) catch( e : String ) null;
-					if( p == null ){
-						b.add("$");
+					var psize = 0;
+					var p = try regexp_matched_pos(r,c-48, new hl.types.Ref(psize)) catch( e : String ) -1;
+					if( p < 0 ){
+						b.addChar("$".code);
 						b.add(k);
 					} else {
-						if( p.pos >= 0 ) b.addSub(s,p.pos,p.len);
+						if( p > 0 ) b.__add(sbytes, p, psize);
 						b.addSub(k,1,k.length - 1);
 					}
-				} else if( c == null ) {
-					b.add("$");
+				} else if( c == 0 ) {
+					b.addChar("$".code);
 					i++;
 					var k2 = a[i];
 					if( k2 != null && k2.length > 0 )
@@ -150,43 +158,41 @@ private typedef ERegValue = hl.types.NativeAbstract<"ereg">;
 					b.add("$"+k);
 				i++;
 			}
-			var tot = p.pos + p.len - pos;
+			var tot = mpos + msize - pos;
 			pos += tot;
-			len -= tot;
+			size -= tot;
 			first = false;
 		} while( global );
-		b.addSub(s,pos,len);
-		return b.toString();*/
-		throw "TODO";
-		return null;
+		b.__add(sbytes,pos,size);
+		return b.toString();
 	}
 
-	public function map( s : String, f : EReg -> String ) : String {
-		/*
-		var offset = 0;
+	public function map( s : String, f : EReg -> String ) : String @:privateAccess {
+		var boffset = 0;
+		var ssize = s.size;
 		var buf = new StringBuf();
 		do {
-			if (offset >= s.length)
+			if( boffset >= ssize )
 				break;
-			else if (!matchSub(s, offset)) {
-				buf.add(s.substr(offset));
+			else if (!matchSub(s, s.bytes.utf8Length(0,boffset))) {
+				buf.__add(s.bytes, boffset, ssize - boffset);
 				break;
 			}
-			var p = regexp_matched_pos(r,0);
-			buf.add(s.substr(offset, p.pos - offset));
+			var msize = 0;
+			var mpos = regexp_matched_pos(r,0, new hl.types.Ref(msize));
+			buf.__add(s.bytes, boffset, mpos - boffset);
 			buf.add(f(this));
-			if (p.len == 0) {
-				buf.add(s.substr(p.pos, 1));
-				offset = p.pos + 1;
-			}
-			else
-				offset = p.pos + p.len;
+			if( msize == 0 ) {
+				if( mpos == ssize ) break;
+				var k = s.bytes.utf8Pos(mpos, 1);
+				buf.__add(s.bytes, mpos, k);
+				boffset = mpos + k;
+			} else
+				boffset = mpos + msize;
 		} while (global);
-		if (!global && offset > 0 && offset < s.length)
-			buf.add(s.substr(offset));
-		return buf.toString();*/
-		throw "TODO";
-		return null;
+		if (!global && boffset > 0 && boffset < ssize )
+			buf.__add(s.bytes, boffset, ssize - boffset);
+		return buf.toString();
 	}
 
 	@:hlNative("regexp", "regexp_new_options") static function regexp_new_options( bytes : hl.types.Bytes, options : hl.types.Bytes ) : ERegValue {

+ 1 - 1
std/hl/_std/String.hx

@@ -84,7 +84,7 @@ class String {
 				out.push(subBytes(pos, size-pos));
 				break;
 			}
-			out.push(subBytes(pos, pos - p));
+			out.push(subBytes(pos, p - pos));
 			pos = p + dsize;
 		}
 		return out;

+ 121 - 0
std/hl/_std/StringBuf.hx

@@ -0,0 +1,121 @@
+/*
+ * Copyright (C)2005-2016 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+@:coreApi class StringBuf {
+
+	var b : hl.types.Bytes;
+	var size : Int;
+	var pos : Int;
+	var slen : Int;
+	public var length(get,never) : Int;
+
+	public function new() : Void {
+		pos = slen = 0;
+		size = 12; // ensure 6 bytes expand for addChar()
+		b = new hl.types.Bytes(size);
+	}
+
+	inline function get_length() : Int {
+		return slen;
+	}
+
+	inline function __expand( need : Int ) : Void {
+		var nsize = (size * 3) >> 1;
+		if( need > nsize ) nsize = need;
+		var b2 = new hl.types.Bytes(nsize);
+		b2.blit(0, b, 0, pos);
+		b = b2;
+		size = nsize;
+	}
+
+	function __addBytes( bytes : hl.types.Bytes, spos : Int, ssize : Int, slen : Int ) : Void {
+		if( pos + ssize > size ) __expand(pos + ssize);
+		b.blit(pos, bytes, spos, ssize);
+		pos += ssize;
+		this.slen += slen;
+	}
+
+	inline function __add( bytes : hl.types.Bytes, spos : Int, ssize : Int ) : Void {
+		__addBytes(bytes, spos, ssize, bytes.utf8Length(spos, ssize));
+	}
+
+	public function add<T>( x : T ) : Void {
+		var ssize = 0;
+		var sbytes = hl.types.Bytes.ofValue(x, new hl.types.Ref(ssize));
+		__add(sbytes, 0, ssize);
+	}
+
+	public function addSub( s : String, pos : Int, ?len : Int ) : Void @:privateAccess {
+		if( pos < 0 ) pos = 0;
+		if( pos > s.length ) pos = s.length;
+		var slen : Int;
+		if( len == null ) slen = s.length - pos else {
+			slen = len;
+			if( pos + slen > s.length ) slen = s.length - pos;
+			if( slen <= 0 ) return;
+		}
+		var bpos = pos == 0 ? 0 : s.bytes.utf8Pos(0,pos);
+		var blen = (pos + len == s.length ? s.size - bpos : s.bytes.utf8Pos(bpos, len));
+		__addBytes(s.bytes, bpos, blen, len - pos);
+	}
+
+	public function addChar( c : Int ) : Void {
+		if( c < 0 )
+			throw "Invalid char code";
+		if( pos + 6 > size ) __expand(0);
+		if( c < 0x80 )
+			b[pos++] = c;
+		else if( c < 0x800 ) {
+			b[pos++] = 0xC0 | (c >> 6);
+			b[pos++] = 0x80 | (c & 63);
+		} else if( c < 0x10000 ) {
+			b[pos++] = 0xE0 | (c >> 12);
+			b[pos++] = 0x80 | ((c >> 6) & 63);
+			b[pos++] = 0x80 | (c & 63);
+		} else if( c < 0x200000 ) {
+			b[pos++] = 0xF0 | (c >> 18);
+			b[pos++] = 0x80 | ((c >> 12) & 63);
+			b[pos++] = 0x80 | ((c >> 6) & 63);
+			b[pos++] = 0x80 | (c & 63);
+		} else if( c < 0x4000000 ) {
+			b[pos++] = 0xF8 | (c >> 24);
+			b[pos++] = 0x80 | ((c >> 18) & 63);
+			b[pos++] = 0x80 | ((c >> 12) & 63);
+			b[pos++] = 0x80 | ((c >> 6) & 63);
+			b[pos++] = 0x80 | (c & 63);
+		} else {
+			b[pos++] = 0xFC | (c >> 30);
+			b[pos++] = 0x80 | ((c >> 24) & 63);
+			b[pos++] = 0x80 | ((c >> 18) & 63);
+			b[pos++] = 0x80 | ((c >> 12) & 63);
+			b[pos++] = 0x80 | ((c >> 6) & 63);
+			b[pos++] = 0x80 | (c & 63);
+		}
+		slen++;
+	}
+
+	public function toString() : String {
+		if( pos == size ) __expand(0);
+		b[pos] = 0;
+		return @:privateAccess String.__alloc__(b, pos, slen);
+	}
+
+}