Procházet zdrojové kódy

`String.iterator()` and `String.keyValueIterator()`

Alexander Kuzmenko před 6 roky
rodič
revize
1d45ab0419

+ 20 - 0
src/generators/genas3.ml

@@ -231,6 +231,18 @@ let open_block ctx =
 	ctx.tabs <- "\t" ^ ctx.tabs;
 	(fun() -> ctx.tabs <- oldt)
 
+(**
+	Returns `true` is `expr` represent a constant string.
+	Recursively looks through casts, metas and parentheses.
+*)
+let rec is_const_string expr =
+	match expr.eexpr with
+		| TConst (TString _) -> true
+		| TCast (e, _) -> is_const_string e
+		| TMeta (_, e) -> is_const_string e
+		| TParenthesis e -> is_const_string e
+		| _ -> false
+
 let parent e =
 	match e.eexpr with
 	| TParenthesis _ -> e
@@ -638,6 +650,14 @@ and gen_expr ctx e =
 	| TEnumParameter (e,_,i) ->
 		gen_value ctx e;
 		print ctx ".params[%i]" i;
+	| TField (e, ((FDynamic "iterator") | (FAnon { cf_name = "iterator" }))) when is_const_string e ->
+		print ctx "new haxe.iterators.StringIterator(";
+		gen_value ctx e;
+		print ctx ")"
+	| TField (e, ((FDynamic "keyValueIterator") | (FAnon { cf_name = "keyValueIterator" }))) when is_const_string e ->
+		print ctx "new haxe.iterators.StringKeyValueIterator(";
+		gen_value ctx e;
+		print ctx ")"
 	| TField (e,s) ->
 		gen_value ctx e;
 		gen_field_access ctx e.etype (field_name s)

+ 2 - 0
src/generators/gencs.ml

@@ -385,6 +385,8 @@ struct
 					{ e with eexpr = TCall(mk_static_field_access_infer string_ext "fromCharCode" e.epos [], [run cc]) }
 				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("charAt" as field) })) }, args )
 				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("charCodeAt" as field) })) }, args )
+				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("iterator" as field) })) }, args )
+				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("keyValueIterator" as field) })) }, args )
 				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("indexOf" as field) })) }, args )
 				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("lastIndexOf" as field) })) }, args )
 				| TCall( { eexpr = TField(ef, FInstance({ cl_path = [], "String" }, _, { cf_name = ("split" as field) })) }, args )

+ 2 - 1
src/generators/genjava.ml

@@ -722,7 +722,8 @@ struct
 					let field = field.cf_name in
 					(match field with
 						| "charAt" | "charCodeAt" | "split" | "indexOf"
-						| "lastIndexOf" | "substring" | "substr" ->
+						| "lastIndexOf" | "substring" | "substr"
+						| "iterator" | "keyValueIterator" ->
 							{ e with eexpr = TCall(mk_static_field_access_infer string_ext field e.epos [], [run ef] @ (List.map run args)) }
 						| _ ->
 							{ e with eexpr = TCall(run efield, List.map run args) }

+ 32 - 2
src/generators/genjs.ml

@@ -329,6 +329,7 @@ let is_dynamic_iterator ctx e =
 	let check x =
 		let rec loop t = match follow t with
 			| TInst ({ cl_path = [],"Array" },_)
+			| TInst ({ cl_path = [],"String" },_)
 			| TInst ({ cl_kind = KTypeParameter _}, _)
 			| TAnon _
 			| TDynamic _
@@ -338,13 +339,33 @@ let is_dynamic_iterator ctx e =
 				loop (Abstract.get_underlying_type a tl)
 			| _ -> false
 		in
-		has_feature ctx "HxOverrides.iter" && loop x.etype
+		(has_feature ctx "HxOverrides.iter" || has_feature ctx "String.iterator") && loop x.etype
 	in
 	match e.eexpr with
 	| TField (x,f) when field_name f = "iterator" -> check x
 	| _ ->
 		false
 
+let is_dynamic_key_value_iterator ctx e =
+	let check x =
+		let rec loop t = match follow t with
+			| TInst ({ cl_path = [],"String" },_)
+			| TInst ({ cl_kind = KTypeParameter _}, _)
+			| TAnon _
+			| TDynamic _
+			| TMono _ ->
+				true
+			| TAbstract(a,tl) when not (Meta.has Meta.CoreType a.a_meta) ->
+				loop (Abstract.get_underlying_type a tl)
+			| _ ->
+				false
+		in
+		has_feature ctx "String.keyValueIterator" && loop x.etype
+	in
+	match e.eexpr with
+	| TField (x,f) when field_name f = "keyValueIterator" -> check x
+	| _ -> false
+
 let gen_constant ctx p = function
 	| TInt i -> print ctx "%ld" i
 	| TFloat s -> spr ctx s
@@ -448,6 +469,11 @@ let rec gen_call ctx e el in_value =
 		print ctx "$getIterator(";
 		gen_value ctx x;
 		print ctx ")";
+	| TField (x,f), [] when field_name f = "keyValueIterator" && is_dynamic_key_value_iterator ctx e ->
+		add_feature ctx "use.$getKeyValueIterator";
+		print ctx "$getKeyValueIterator(";
+		gen_value ctx x;
+		print ctx ")";
 	| _ ->
 		gen_value ctx e;
 		spr ctx "(";
@@ -1731,7 +1757,11 @@ let generate com =
 		newline ctx;
 	end;
 	if has_feature ctx "use.$getIterator" then begin
-		print ctx "function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); else return o.iterator(); }";
+		print ctx "function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); else if (typeof o == 'string') return HxOverrides.strIter(o); else return o.iterator(); }";
+		newline ctx;
+	end;
+	if has_feature ctx "use.$getKeyValueIterator" then begin
+		print ctx "function $getKeyValueIterator(o) { if (typeof o == 'string') return HxOverrides.strKVIter(o); else return o.keyValueIterator(); }";
 		newline ctx;
 	end;
 	if has_feature ctx "use.$bind" then begin

+ 20 - 7
src/generators/genlua.ml

@@ -488,22 +488,33 @@ and gen_call ctx e el =
          spr ctx ")(";
          concat ctx "," (gen_argument ctx) (e::el);
          spr ctx ")";
-     | TField (e, ((FInstance _ | FAnon _ | FDynamic _) as ef)), el ->
+     | TField (field_owner, ((FInstance _ | FAnon _ | FDynamic _) as ef)), el ->
          let s = (field_name ef) in
          if Hashtbl.mem kwds s || not (valid_lua_ident s) then begin
              add_feature ctx "use._hx_apply_self";
              spr ctx "_hx_apply_self(";
-             gen_value ctx e;
+             gen_value ctx field_owner;
              print ctx ",\"%s\"" (field_name ef);
              if List.length(el) > 0 then spr ctx ",";
              concat ctx "," (gen_argument ctx) el;
              spr ctx ")";
          end else begin
-             gen_value ctx e;
-             if is_dot_access e ef then
-                 print ctx ".%s" (field_name ef)
-             else
-                 print ctx ":%s" (field_name ef);
+             let el =
+                if (match ef with FAnon _ | FDynamic _ -> true | _ -> false) && is_possible_string_field field_owner s then
+                    begin
+                        gen_expr ctx e;
+                        field_owner :: el
+                    end
+                else
+                    begin
+                        gen_value ctx field_owner;
+                        if is_dot_access field_owner ef then
+                            print ctx ".%s" (field_name ef)
+                        else
+                            print ctx ":%s" (field_name ef);
+                        el
+                    end
+             in
              gen_paren_arguments ctx el;
          end;
      | _ ->
@@ -585,6 +596,8 @@ and is_possible_string_field e field_name=
         | "toString"
         | "substring"
         | "substr"
+        | "iterator"
+        | "keyValueIterator"
         | "charCodeAt" ->
             true
         | _ ->

+ 2 - 0
src/generators/genphp7.ml

@@ -2305,6 +2305,8 @@ class code_writer (ctx:Common.context) hx_type_path php_name =
 				| "toString"
 				| "substring"
 				| "substr"
+				| "iterator"
+				| "keyValueIterator"
 				| "charCodeAt" ->
 					self#write ((self#use hxdynamicstr_type_path) ^ "::wrap(");
 					self#write_expr expr;

+ 1 - 1
src/generators/genpy.ml

@@ -1673,7 +1673,7 @@ module Printer = struct
 					"print(str(" ^ (print_expr pctx e) ^ "))"
 			| TField(e1,((FAnon {cf_name = (("split" | "join" | "push" | "map" | "filter") as s)}) | FDynamic (("split" | "join" | "push" | "map" | "filter") as s))), [x] ->
 				Printf.sprintf "HxOverrides.%s(%s, %s)" s (print_expr pctx e1) (print_expr pctx x)
-			| TField(e1,((FAnon {cf_name = (("iterator" | "toUpperCase" | "toLowerCase" | "pop" | "shift") as s)}) | FDynamic (("iterator" | "toUpperCase" | "toLowerCase" | "pop" | "shift") as s))), [] ->
+			| TField(e1,((FAnon {cf_name = (("iterator" | "keyValueIterator" | "toUpperCase" | "toLowerCase" | "pop" | "shift") as s)}) | FDynamic (("iterator" | "keyValueIterator" | "toUpperCase" | "toLowerCase" | "pop" | "shift") as s))), [] ->
 				Printf.sprintf "HxOverrides.%s(%s)" s (print_expr pctx e1)
 			| TField(_, (FStatic({cl_path = ["python"; "_KwArgs"], "KwArgs_Impl_"},{ cf_name="fromT" }))), [e2]  ->
 				let t = match follow call_expr.etype with

+ 3 - 1
src/macro/eval/evalHash.ml

@@ -121,8 +121,10 @@ let key_haxe_zip_Compress = hash "haxe.zip.Compress"
 let key_haxe_zip_Uncompress = hash "haxe.zip.Uncompress"
 let key_done = hash "done"
 let key_eval_toplevel = hash "eval-toplevel"
+let key_haxe_iterators_string_iterator = hash "haxe.iterators.StringIterator"
+let key_haxe_iterators_string_key_value_iterator = hash "haxe.iterators.StringKeyValueIterator"
 let key_haxe_iterators_map_key_value_iterator = hash "haxe.iterators.MapKeyValueIterator"
 let key_sys_net_Mutex = hash "sys.thread.Mutex"
 let key_sys_net_Lock = hash "sys.thread.Lock"
 let key_sys_net_Tls = hash "sys.thread.Tls"
-let key_sys_net_Deque = hash "sys.thread.Deque"
+let key_sys_net_Deque = hash "sys.thread.Deque"

+ 20 - 0
src/macro/eval/evalStdLib.ml

@@ -2142,6 +2142,24 @@ module StdString = struct
 		else vint (char_at this i)
 	)
 
+	let iterator = vifun0 (fun vthis ->
+		let ctx = get_ctx() in
+		let path = key_haxe_iterators_string_iterator in
+		let vit = encode_instance path in
+		let fnew = get_instance_constructor ctx path null_pos in
+		ignore(call_value_on vit (Lazy.force fnew) [vthis]);
+		vit
+	)
+
+	let keyValueIterator = vifun0 (fun vthis ->
+		let ctx = get_ctx() in
+		let path = key_haxe_iterators_string_key_value_iterator in
+		let vit = encode_instance path in
+		let fnew = get_instance_constructor ctx path null_pos in
+		ignore(call_value_on vit (Lazy.force fnew) [vthis]);
+		vit
+	)
+
 	let fromCharCode = vfun1 (fun i ->
 		let i = decode_int i in
 		try
@@ -3488,6 +3506,8 @@ let init_standard_library builtins =
 	] [
 		"charAt",StdString.charAt;
 		"charCodeAt",StdString.charCodeAt;
+		"iterator",StdString.iterator;
+		"keyValueIterator",StdString.keyValueIterator;
 		"indexOf",StdString.indexOf;
 		"lastIndexOf",StdString.lastIndexOf;
 		"split",StdString.split;

+ 26 - 0
std/String.hx

@@ -19,6 +19,10 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
+
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 /**
 	The basic String class.
 
@@ -73,6 +77,28 @@ extern class String {
 	**/
 	function charCodeAt( index : Int) : Null<Int>;
 
+	/**
+		Returns an iterator of the char codes.
+
+		Note that char codes may differ across platforms because of different
+		internal encoding of strings in different of runtimes.
+		For the consistent cross-platform UTF8 char codes see `haxe.iterators.StringIteratorUnicode`.
+	**/
+	@:pure @:runtime inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	/**
+		Returns an iterator of the char indexes and codes.
+
+		Note that char codes may differ across platforms because of different
+		internal encoding of strings in different of runtimes.
+		For the consistent cross-platform UTF8 char codes see `haxe.iterators.StringKeyValueIteratorUnicode`.
+	**/
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	/**
 		Returns the position of the leftmost occurrence of `str` within `this`
 		String.

+ 11 - 0
std/cs/_std/String.hx

@@ -21,6 +21,9 @@
  */
 import cs.StdTypes;
 
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi extern class String implements ArrayAccess<Char16> {
 
 	@:overload private static function Compare(s1:String, s2:String):Int;
@@ -52,6 +55,14 @@ import cs.StdTypes;
 
 	function toString() : String;
 
+	@:pure @:runtime inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	static function fromCharCode( code : Int ) : String;
 
 	private function IndexOf(value:String, startIndex:Int, comparisonType:cs.system.StringComparison):Int;

+ 14 - 0
std/cs/internal/StringExt.hx

@@ -20,7 +20,11 @@
  * DEALINGS IN THE SOFTWARE.
  */
 package cs.internal;
+
 import cs.internal.Function;
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 private typedef NativeString = cs.system.String;
 
 @:keep @:nativeGen @:native("haxe.lang.StringExt") class StringExt
@@ -193,6 +197,16 @@ private typedef NativeString = cs.system.String;
 		return cs.system.Char.ConvertFromUtf32(code);
 		// return new NativeString( cast(code,cs.StdTypes.Char16), 1 );
 	}
+
+	public static function iterator(me:String):StringIterator
+	{
+		return new StringIterator(me);
+	}
+
+	public static function keyValueIterator(me:String):StringKeyValueIterator
+	{
+		return new StringKeyValueIterator(me);
+	}
 }
 
 @:keep @:nativeGen @:native('haxe.lang.StringRefl') class StringRefl

+ 11 - 1
std/flash/Boot.hx

@@ -21,6 +21,9 @@
  */
 package flash;
 
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 #if !as3
 @:keep private class RealBoot extends Boot {
 	#if swc
@@ -242,6 +245,14 @@ class Boot extends flash.display.MovieClip {
 	}
 
 	static function __init__() untyped {
+		String.prototype.iterator = function() : StringIterator {
+			var s : String = __this__;
+			return new StringIterator(s);
+		}
+		String.prototype.keyValueIterator = function() : StringKeyValueIterator {
+			var s : String = __this__;
+			return new StringKeyValueIterator(s);
+		}
 		var aproto = Array.prototype;
 		aproto.copy = function() {
 			return __this__.slice();
@@ -337,5 +348,4 @@ class Boot extends flash.display.MovieClip {
 			return Std.int(x);
 		};
 	}
-
 }

+ 12 - 1
std/flash/_std/String.hx

@@ -20,6 +20,9 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi
 extern class String {
 
@@ -36,8 +39,16 @@ extern class String {
 	function substring( startIndex : Int, ?endIndex : Int ) : String;
 	function toString() : String;
 
+	@:pure @:runtime inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	@:pure static inline function fromCharCode( code : Int ) : String untyped {
 		return code < 0x10000 ? String["fromCharCode"](code) : flash.Boot.fromCodePoint(code);
 	}
-	
+
 }

+ 54 - 0
std/haxe/iterators/StringIterator.hx

@@ -0,0 +1,54 @@
+/*
+ * Copyright (C)2005-2018 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.
+ */
+package haxe.iterators;
+
+/**
+	This iterator can be used to iterate over char codes in a string.
+
+	Note that char codes may differ across platforms because of different
+	internal encoding of strings in different of runtimes.
+**/
+class StringIterator {
+	var offset = 0;
+	var s:String;
+
+	/**
+		Create a new `StringIterator` over String `s`.
+	**/
+	public inline function new(s:String) {
+		this.s = s;
+	}
+
+	/**
+		See `Iterator.hasNext`
+	**/
+	public inline function hasNext() {
+		return offset < s.length;
+	}
+
+	/**
+		See `Iterator.next`
+	**/
+	public inline function next() {
+		return StringTools.fastCodeAt(s, offset++);
+	}
+}

+ 54 - 0
std/haxe/iterators/StringKeyValueIterator.hx

@@ -0,0 +1,54 @@
+/*
+ * Copyright (C)2005-2018 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.
+ */
+package haxe.iterators;
+
+/**
+	This iterator can be used to iterate over char indexes and char codes in a string.
+
+	Note that char codes may differ across platforms because of different
+	internal encoding of strings in different runtimes.
+**/
+class StringKeyValueIterator {
+	var offset = 0;
+	var s:String;
+
+	/**
+		Create a new `StringKeyValueIterator` over String `s`.
+	**/
+	public inline function new(s:String) {
+		this.s = s;
+	}
+
+	/**
+		See `KeyValueIterator.hasNext`
+	**/
+	public inline function hasNext() {
+		return offset < s.length;
+	}
+
+	/**
+		See `KeyValueIterator.next`
+	**/
+	public inline function next() {
+		return { key : offset, value : StringTools.fastCodeAt(s, offset++) };
+	}
+}

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

@@ -19,6 +19,10 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
+
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi
 class String {
 
@@ -52,7 +56,7 @@ class String {
 			return null;
 		return bytes.getUI16(index << 1);
 	}
-	
+
 	inline function findChar(start:Int,len:Int,src:hl.Bytes,srcLen:Int) : Int {
 		var p = 0;
 		while( true ) {
@@ -169,6 +173,14 @@ class String {
 		return this;
 	}
 
+	public inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	public inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	public static function fromCharCode( code : Int ) : String {
 		if( code >= 0 && code < 0x10000 ) {
 			if( code >= 0xD800 && code <= 0xDFFF ) throw "Invalid unicode char " + code;

+ 11 - 0
std/java/_std/String.hx

@@ -20,6 +20,9 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi extern class String implements java.lang.CharSequence {
 
 	var length(default,null) : Int;
@@ -49,6 +52,14 @@
 
 	function toString() : String;
 
+	@:pure @:runtime inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	private function compareTo( anotherString : String ) : Int;
 
 	private function codePointAt( idx : Int ) : Int;

+ 12 - 0
std/java/internal/StringExt.hx

@@ -21,6 +21,8 @@
  */
 package java.internal;
 import java.internal.Function;
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
 
 private typedef NativeString = String;
 
@@ -199,6 +201,16 @@ private typedef NativeString = String;
 	{
 		return new String(java.lang.Character.toChars(code));
 	}
+
+	public static function iterator(me:NativeString):StringIterator
+	{
+		return new StringIterator(me);
+	}
+
+	public static function keyValueIterator(me:NativeString):StringKeyValueIterator
+	{
+		return new StringKeyValueIterator(me);
+	}
 }
 
 @:keep @:nativeGen @:native('haxe.lang.StringRefl') private class StringRefl

+ 14 - 0
std/js/_std/HxOverrides.hx

@@ -19,6 +19,10 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
+
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:noDoc
 class HxOverrides {
 
@@ -142,6 +146,16 @@ class HxOverrides {
 		};
 	}
 
+	@:ifFeature("String.iterator")
+	static function strIter( s : String ) : StringIterator {
+		return new StringIterator(s);
+	}
+
+	@:ifFeature("String.keyValueIterator")
+	static function strKVIter( s : String ) : StringKeyValueIterator {
+		return new StringKeyValueIterator(s);
+	}
+
 	static function __init__() untyped {
 #if (js_es < 5)
 		__feature__('HxOverrides.indexOf', if( Array.prototype.indexOf ) __js__("HxOverrides").indexOf = function(a,o,i) return Array.prototype.indexOf.call(a, o, i));

+ 1 - 1
std/js/_std/Reflect.hx

@@ -28,7 +28,7 @@
 
 	@:pure
 	public static function field( o : Dynamic, field : String ) : Dynamic {
-		try return o[cast field] catch( e : Dynamic ) return null;
+		return try o[cast field] catch( e : Dynamic ) null;
 	}
 
 	public inline static function setField( o : Dynamic, field : String, value : Dynamic ) : Void {

+ 16 - 2
std/js/_std/String.hx

@@ -19,6 +19,10 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
+
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi extern class String {
 	var length(default,null) : Int;
 
@@ -36,6 +40,16 @@
 		return @:privateAccess HxOverrides.cca(this, index);
 	}
 
+	@:pure @:runtime inline function iterator() : StringIterator {
+		untyped __define_feature__("String.iterator", {});
+		return new StringIterator(this);
+	}
+
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		untyped __define_feature__("String.keyValueIterator", {});
+		return new StringKeyValueIterator(this);
+	}
+
 	@:pure inline function substr( pos : Int, ?len : Int ) : String {
 		return @:privateAccess HxOverrides.substr(this, pos, len);
 	}
@@ -43,9 +57,9 @@
 	@:pure static inline function fromCharCode( code : Int ) : String {
 		return untyped __define_feature__('String.fromCharCode', js.Syntax.code("String.fromCodePoint({0})", code));
 	}
-	
+
 	static function __init__() : Void {
 		untyped __feature__('String.fromCharCode', js.Syntax.code("if( String.fromCodePoint == null ) String.fromCodePoint = function(c) { return c < 0x10000 ? String.fromCharCode(c) : String.fromCharCode((c>>10)+0xD7C0)+String.fromCharCode((c&0x3FF)+0xDC00); }"));
 	}
-	
+
 }

+ 10 - 0
std/lua/_std/String.hx

@@ -23,6 +23,8 @@
 import lua.Lua;
 import lua.Table;
 import lua.Boot;
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
 
 #if lua_vanilla
 	typedef BaseString = lua.NativeStringTools;
@@ -122,6 +124,14 @@ class String {
 		return BaseString.byte(this,index+1);
 	}
 
+	@:runtime public inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:runtime public inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	public inline function substr( pos : Int, ?len : Int ) : String {
 		if (len == null || len > pos + this.length) len = this.length;
 		else if (len < 0) len = length + len;

+ 12 - 0
std/neko/_std/String.hx

@@ -19,6 +19,10 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
+
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi final class String {
 
 	static var __is_String = true;
@@ -57,6 +61,14 @@
 		}
 	}
 
+	public inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	public inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	public function indexOf( str : String, ?startIndex : Int ) : Int {
 		untyped {
 			var p = try __dollar__sfind(this.__s,if( startIndex == null ) 0 else startIndex,str.__s) catch( e : Dynamic ) null;

+ 10 - 0
std/php/Boot.hx

@@ -22,6 +22,8 @@
 package php;
 
 import haxe.PosInfos;
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
 import haxe.extern.EitherType;
 
 using php.Global;
@@ -729,6 +731,14 @@ private class HxString {
 		return char == '' ? null : Boot.unsafeOrd(char);
 	}
 
+	public static function iterator( str:String ):StringIterator {
+		return new StringIterator(str);
+	}
+
+	public static function keyValueIterator( str:String ):StringKeyValueIterator {
+		return new StringKeyValueIterator(str);
+	}
+
 	public static function indexOf( str:String, search:String, startIndex:Int = null ) : Int {
 		if (startIndex == null) {
 			startIndex = 0;

+ 11 - 1
std/php/_std/String.hx

@@ -20,7 +20,9 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
-import php.*;
+import php.Global;
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
 
 @:coreApi extern class String {
 
@@ -42,6 +44,14 @@ import php.*;
 
 	@:pure function charCodeAt( index : Int) : Null<Int>;
 
+	@:pure @:runtime inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	@:pure function indexOf( str : String, ?startIndex : Int ) : Int;
 
 	@:pure function lastIndexOf( str : String, ?startIndex : Int ) : Int;

+ 6 - 0
std/python/Boot.hx

@@ -30,6 +30,8 @@ import python.internal.HxException;
 import python.internal.AnonObject;
 import python.internal.UBuiltins;
 import python.lib.Inspect;
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
 
 import python.Syntax;
 
@@ -311,6 +313,10 @@ class Boot {
 					createClosure(o, StringImpl.charAt);
 				case "charCodeAt":
 					createClosure(o, StringImpl.charCodeAt);
+				case "iterator":
+					createClosure(o, StringImpl.iterator);
+				case "keyValueIterator":
+					createClosure(o, StringImpl.keyValueIterator);
 				case "indexOf":
 					createClosure(o, StringImpl.indexOf);
 				case "lastIndexOf":

+ 11 - 0
std/python/_std/String.hx

@@ -22,6 +22,9 @@
 #if !macro
 import python.internal.StringImpl;
 #end
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
 @:coreApi
 @:native("str")
 extern class String {
@@ -47,6 +50,14 @@ extern class String {
 		return StringImpl.charCodeAt(this, index);
 	}
 
+	@:runtime public inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:runtime public inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
 	inline function indexOf( str : String, ?startIndex : Int ) : Int {
 		return StringImpl.indexOf(this, str, startIndex);
 	}

+ 11 - 1
std/python/internal/HxOverrides.hx

@@ -35,12 +35,22 @@ class HxOverrides {
 	// we need to modify the transformer to call Reflect directly
 
 	@:ifFeature("dynamic_read.iterator", "anon_optional_read.iterator", "anon_read.iterator")
-	static public function iterator(x) {
+	static public function iterator(x:Any):Any {
 		if (Boot.isArray(x)) {
 			return (x:Array<Dynamic>).iterator();
 		}
+		if (Boot.isString(x)) {
+			return new haxe.iterators.StringIterator(x);
+		}
 		return Syntax.callField(x, "iterator");
 	}
+	@:ifFeature("dynamic_read.keyValueIterator", "anon_optional_read.keyValueIterator", "anon_read.keyValueIterator")
+	static public function keyValueIterator(x:Any):Any {
+		if (Boot.isString(x)) {
+			return new haxe.iterators.StringKeyValueIterator(x);
+		}
+		return Syntax.callField(x, "keyValueIterator");
+	}
 	@:ifFeature("dynamic_binop_==", "dynamic_binop_!=")
 	static function eq( a:Dynamic, b:Dynamic ) : Bool {
 		if (Boot.isArray(a) || Boot.isArray(b)) {

+ 10 - 0
std/python/internal/StringImpl.hx

@@ -43,6 +43,16 @@ class StringImpl {
 		return if (index < 0 || index >= s.length) "" else Syntax.arrayAccess(s,index);
 	}
 
+	@:ifFeature("dynamic_read.iterator", "anon_optional_read.iterator", "python.internal.StringImpl.iterator")
+	public static inline function iterator(s:String) {
+		return new haxe.iterators.StringIterator(s);
+	}
+
+	@:ifFeature("dynamic_read.keyValueIterator", "anon_optional_read.keyValueIterator", "python.internal.StringImpl.keyValueIterator")
+	public static inline function keyValueIterator(s:String) {
+		return new haxe.iterators.StringKeyValueIterator(s);
+	}
+
 	@:ifFeature("dynamic_read.lastIndexOf", "anon_optional_read.lastIndexOf", "python.internal.StringImpl.lastIndexOf")
 	public static inline function lastIndexOf(s:String, str:String, ?startIndex:Int):Int {
 		if (startIndex == null) {

+ 1 - 0
tests/unit/.gitignore

@@ -0,0 +1 @@
+apache-flex-sdk-*

+ 28 - 0
tests/unit/src/unitstd/String.unit.hx

@@ -164,3 +164,31 @@ String.fromCharCode(65) == "A";
 // ensure int strings compared as strings, not parsed ints (issue #3734)
 ("3" > "11") == true;
 (" 3" < "3") == true;
+
+// iterators
+var s = 'zя𠜎';
+#if (neko || (cpp && !cppia && !hxcpp_smart_strings))
+var expectedCodes = [122, 209, 143, 240, 160, 156, 142];
+#elseif utf16
+var expectedCodes = [122, 1103, 55361, 57102];
+#else
+var expectedCodes = [122, 1103, 132878];
+#end
+var expectedKeys = [for(i in 0...expectedCodes.length) i];
+function testCodes(codes:Array<Int>, ?pos:haxe.PosInfos) {
+	aeq(expectedCodes, codes, pos);
+}
+function testKeyCodes(keyCodes:Array<Array<Int>>, ?pos:haxe.PosInfos) {
+	aeq(expectedKeys, keyCodes.map(a -> a[0]), pos);
+	aeq(expectedCodes, keyCodes.map(a -> a[1]), pos);
+}
+// iterator
+testCodes([for(c in s) c]);
+testCodes([for(c in (s:Iterable<Int>)) c]);
+var iterator:Iterator<Int> = (s:Dynamic).iterator();
+testCodes([for(c in iterator) c]);
+// keyValueIterator
+testKeyCodes([for(i => c in s) [i, c]]);
+testKeyCodes([for(i => c in (s:KeyValueIterable<Int,Int>)) [i, c]]);
+var iterator:KeyValueIterator<Int,Int> = (s:Dynamic).keyValueIterator();
+testKeyCodes([for(i => c in iterator) [i, c]]);