Просмотр исходного кода

Add methods to get Map and EReg sizes (#11961)

* Add methods to get Map and EReg sizes

* fixes

* test fixes

* hashtbl fix

* return 0

* try-catch jvm
RblSb 2 месяцев назад
Родитель
Сommit
1aad80c223
60 измененных файлов с 444 добавлено и 21 удалено
  1. 26 0
      src/macro/eval/evalStdLib.ml
  2. 2 0
      src/macro/eval/evalValue.ml
  3. 12 0
      std/EReg.hx
  4. 4 0
      std/cpp/Int64Map.hx
  5. 9 0
      std/cpp/_std/EReg.hx
  6. 4 0
      std/cpp/_std/haxe/ds/IntMap.hx
  7. 7 0
      std/cpp/_std/haxe/ds/Map.hx
  8. 4 0
      std/cpp/_std/haxe/ds/ObjectMap.hx
  9. 4 0
      std/cpp/_std/haxe/ds/StringMap.hx
  10. 4 0
      std/cpp/_std/haxe/ds/WeakMap.hx
  11. 1 0
      std/eval/_std/EReg.hx
  12. 5 0
      std/flash/_std/EReg.hx
  13. 6 0
      std/flash/_std/haxe/ds/IntMap.hx
  14. 6 0
      std/flash/_std/haxe/ds/ObjectMap.hx
  15. 6 0
      std/flash/_std/haxe/ds/StringMap.hx
  16. 6 0
      std/flash/_std/haxe/ds/UnsafeStringMap.hx
  17. 6 0
      std/flash/_std/haxe/ds/WeakMap.hx
  18. 1 0
      std/haxe/Constraints.hx
  19. 12 0
      std/haxe/ds/BalancedTree.hx
  20. 7 0
      std/haxe/ds/HashMap.hx
  21. 2 0
      std/haxe/ds/IntMap.hx
  22. 7 0
      std/haxe/ds/Map.hx
  23. 2 0
      std/haxe/ds/ObjectMap.hx
  24. 2 0
      std/haxe/ds/StringMap.hx
  25. 7 0
      std/haxe/ds/WeakMap.hx
  26. 23 0
      std/hl/_std/EReg.hx
  27. 8 0
      std/hl/_std/haxe/ds/IntMap.hx
  28. 8 0
      std/hl/_std/haxe/ds/ObjectMap.hx
  29. 8 0
      std/hl/_std/haxe/ds/StringMap.hx
  30. 7 0
      std/hl/types/BytesMap.hx
  31. 7 0
      std/hl/types/IntMap.hx
  32. 7 0
      std/hl/types/ObjectMap.hx
  33. 6 0
      std/js/_std/EReg.hx
  34. 10 0
      std/js/_std/haxe/ds/IntMap.hx
  35. 10 0
      std/js/_std/haxe/ds/ObjectMap.hx
  36. 16 0
      std/js/_std/haxe/ds/StringMap.hx
  37. 12 0
      std/jvm/_std/EReg.hx
  38. 4 0
      std/jvm/_std/haxe/ds/IntMap.hx
  39. 14 10
      std/jvm/_std/haxe/ds/ObjectMap.hx
  40. 4 0
      std/jvm/_std/haxe/ds/StringMap.hx
  41. 15 11
      std/jvm/_std/haxe/ds/WeakMap.hx
  42. 8 0
      std/lua/_std/EReg.hx
  43. 6 0
      std/lua/_std/haxe/ds/IntMap.hx
  44. 6 0
      std/lua/_std/haxe/ds/ObjectMap.hx
  45. 6 0
      std/lua/_std/haxe/ds/StringMap.hx
  46. 19 0
      std/neko/_std/EReg.hx
  47. 4 0
      std/neko/_std/haxe/ds/IntMap.hx
  48. 4 0
      std/neko/_std/haxe/ds/ObjectMap.hx
  49. 4 0
      std/neko/_std/haxe/ds/StringMap.hx
  50. 5 0
      std/php/_std/EReg.hx
  51. 4 0
      std/php/_std/haxe/ds/IntMap.hx
  52. 4 0
      std/php/_std/haxe/ds/ObjectMap.hx
  53. 4 0
      std/php/_std/haxe/ds/StringMap.hx
  54. 5 0
      std/python/_std/EReg.hx
  55. 4 0
      std/python/_std/haxe/ds/IntMap.hx
  56. 4 0
      std/python/_std/haxe/ds/ObjectMap.hx
  57. 4 0
      std/python/_std/haxe/ds/StringMap.hx
  58. 1 0
      tests/unit/.vscode/settings.json
  59. 23 0
      tests/unit/src/unitstd/EReg.unit.hx
  60. 18 0
      tests/unit/src/unitstd/Map.unit.hx

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

@@ -925,6 +925,16 @@ module StdEReg = struct
 		end
 	)
 
+	let matchedNum = vifun0 (fun vthis ->
+		let this = this vthis in
+		if Array.length this.r_groups = 0 then
+			vint 0
+		else begin
+			let substrings = this.r_groups.(0) in
+			vint (num_of_subs substrings)
+		end
+	)
+
 	let replace = vifun2 (fun vthis s by ->
 		let this = this vthis in
 		let s = decode_string s in
@@ -1554,6 +1564,10 @@ module StdIntMap = struct
 		RuntimeIntHashtbl.clear (this vthis);
 		vnull
 	)
+
+	let size = vifun0 (fun vthis ->
+		vint (RuntimeIntHashtbl.size (this vthis))
+	)
 end
 
 module StdStringMap = struct
@@ -1613,6 +1627,10 @@ module StdStringMap = struct
 		RuntimeStringHashtbl.clear (this vthis);
 		vnull
 	)
+
+	let size = vifun0 (fun vthis ->
+		vint (RuntimeStringHashtbl.size (this vthis))
+	)
 end
 
 module StdObjectMap = struct
@@ -1671,6 +1689,10 @@ module StdObjectMap = struct
 		ValueHashtbl.reset (this vthis);
 		vnull
 	)
+
+	let size = vifun0 (fun vthis ->
+		vint (ValueHashtbl.length (this vthis))
+	)
 end
 
 let random = Random.State.make_self_init()
@@ -3205,6 +3227,7 @@ let init_maps builtins =
 		"set",StdIntMap.set;
 		"toString",StdIntMap.toString;
 		"clear",StdIntMap.clear;
+		"size",StdIntMap.size;
 	];
 	init_fields builtins (["haxe";"ds"],"ObjectMap") [] [
 		"copy",StdObjectMap.copy;
@@ -3217,6 +3240,7 @@ let init_maps builtins =
 		"set",StdObjectMap.set;
 		"toString",StdObjectMap.toString;
 		"clear",StdObjectMap.clear;
+		"size",StdObjectMap.size;
 	];
 	init_fields builtins (["haxe";"ds"],"StringMap") [] [
 		"copy",StdStringMap.copy;
@@ -3229,6 +3253,7 @@ let init_maps builtins =
 		"set",StdStringMap.set;
 		"toString",StdStringMap.toString;
 		"clear",StdStringMap.clear;
+		"size",StdStringMap.size;
 	]
 
 let init_constructors builtins =
@@ -3500,6 +3525,7 @@ let init_standard_library builtins =
 		"matchedPos",StdEReg.matchedPos;
 		"matchedRight",StdEReg.matchedRight;
 		"matchSub",StdEReg.matchSub;
+		"matchedNum",StdEReg.matchedNum;
 		"replace",StdEReg.replace;
 		"split",StdEReg.split;
 	];

+ 2 - 0
src/macro/eval/evalValue.ml

@@ -56,6 +56,7 @@ module RuntimeStringHashtbl = struct
 	let mem this key = StringMap.mem key.sstring !this
 	let remove this key = this := StringMap.remove key.sstring !this
 	let clear this = this := StringMap.empty
+	let size this = StringMap.cardinal !this
 end
 
 module RuntimeIntHashtbl = struct
@@ -71,6 +72,7 @@ module RuntimeIntHashtbl = struct
 	let mem this key = IntHashtbl.mem this key
 	let remove this key = IntHashtbl.remove this key
 	let clear this = IntHashtbl.clear this
+	let size this = IntHashtbl.length this
 end
 
 type vregex = {

+ 12 - 0
std/EReg.hx

@@ -134,6 +134,18 @@ class EReg {
 		return false;
 	}
 
+	/**
+		Returns the total number of groups captures by the last matched substring.
+
+		To stay consistent with `this.matched`, the matched substring is also
+		counted as a group.
+
+		Returns `0` if no substring has been matched.
+	**/
+	public function matchedNum():Int {
+		return 0;
+	}
+
 	/**
 		Splits String `s` at all substrings `this` EReg matches.
 

+ 4 - 0
std/cpp/Int64Map.hx

@@ -110,6 +110,10 @@ import haxe.Int64;
 		#end
 	}
 
+	public function size():Int {
+		return untyped __global__.__root_hash_size(h);
+	}
+
 	#if (scriptable)
 	private function setString(key:Int64, val:String):Void {
 		untyped __int64_hash_set_string(__cpp__("HX_MAP_THIS"), key, val);

+ 9 - 0
std/cpp/_std/EReg.hx

@@ -71,6 +71,12 @@
 		return p;
 	}
 
+	public function matchedNum():Int {
+		var num = _hx_regexp_matched_num(r);
+		if (num == -1) return 0;
+		return num;
+	}
+
 	public function split(s:String):Array<String> {
 		var pos = 0;
 		var len = s.length;
@@ -190,4 +196,7 @@
 
 	@:native("_hx_regexp_matched_pos")
 	extern static function _hx_regexp_matched_pos(handle:Dynamic, match:Int):{pos:Int, len:Int};
+
+	@:native("_hx_regexp_matched_num")
+	extern static function _hx_regexp_matched_num(handle:Dynamic):Int;
 }

+ 4 - 0
std/cpp/_std/haxe/ds/IntMap.hx

@@ -107,6 +107,10 @@ package haxe.ds;
 		h = null;
 		#end
 	}
+	
+	public function size():Int {
+		return untyped __global__.__root_hash_size(h);
+	}
 
 	#if (scriptable)
 	private function setString(key:Int, val:String):Void {

+ 7 - 0
std/cpp/_std/haxe/ds/Map.hx

@@ -164,6 +164,13 @@ abstract Map<K, V>(IMap<K, V>) {
 		this.clear();
 	}
 
+	/**
+		Returns size of `this` Map.
+	**/
+	public inline function size():Int {
+		return this.size();
+	}
+
 	@:arrayAccess @:noCompletion public inline function arrayWrite(k:K, v:V):V {
 		this.set(k, v);
 		return v;

+ 4 - 0
std/cpp/_std/haxe/ds/ObjectMap.hx

@@ -106,6 +106,10 @@ class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 		h = null;
 		#end
 	}
+	
+	public function size():Int {
+		return untyped __global__.__root_hash_size(h);
+	}
 
 	#if (scriptable)
 	private function setString(key:Dynamic, val:String):Void {

+ 4 - 0
std/cpp/_std/haxe/ds/StringMap.hx

@@ -107,6 +107,10 @@ package haxe.ds;
 		h = null;
 		#end
 	}
+	
+	public function size():Int {
+		return untyped __global__.__root_hash_size(h);
+	}
 
 	#if (scriptable)
 	private function setString(key:String, val:String):Void {

+ 4 - 0
std/cpp/_std/haxe/ds/WeakMap.hx

@@ -99,4 +99,8 @@ class WeakMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 		h = null;
 		#end
 	}
+	
+	public function size():Int {
+		return untyped __global__.__root_hash_size(h);
+	}
 }

+ 1 - 0
std/eval/_std/EReg.hx

@@ -30,6 +30,7 @@ extern class EReg {
 	function matchedRight():String;
 	function matchedPos():{pos:Int, len:Int};
 	function matchSub(s:String, pos:Int, len:Int = -1):Bool;
+	function matchedNum():Int;
 	function split(s:String):Array<String>;
 	function replace(s:String, by:String):String;
 	function map(s:String, f:EReg->String):String;

+ 5 - 0
std/flash/_std/EReg.hx

@@ -59,6 +59,11 @@
 		return {pos: result.index, len: (result[0] : String).length};
 	}
 
+	public function matchedNum():Int {
+		if (result == null) return 0;
+		return result.length;
+	}
+
 	public function matchSub(s:String, pos:Int, len:Int = -1):Bool {
 		return if (r.global) {
 			r.lastIndex = pos;

+ 6 - 0
std/flash/_std/haxe/ds/IntMap.hx

@@ -85,6 +85,12 @@ package haxe.ds;
 	public inline function clear():Void {
 		h = new flash.utils.Dictionary();
 	}
+	
+	public function size():Int {
+		var s = 0;
+		for(_ in keys()) s++;
+		return s;
+	}
 }
 
 // this version uses __has_next__/__forin__ special SWF opcodes for iteration with no allocation

+ 6 - 0
std/flash/_std/haxe/ds/ObjectMap.hx

@@ -80,6 +80,12 @@ class ObjectMap<K:{}, V> extends flash.utils.Dictionary implements haxe.Constrai
 		for (i in keys())
 			untyped __delete__(this, i);
 	}
+	
+	public function size():Int {
+		var s = 0;
+		for(_ in keys()) s++;
+		return s;
+	}
 }
 
 private class NativePropertyIterator {

+ 6 - 0
std/flash/_std/haxe/ds/StringMap.hx

@@ -124,6 +124,12 @@ package haxe.ds;
 		h = {};
 		rh = null;
 	}
+	
+	public function size():Int {
+		var s = 0;
+		for(_ in keys()) s++;
+		return s;
+	}
 }
 
 // this version uses __has_next__/__forin__ special SWF opcodes for iteration with no allocation

+ 6 - 0
std/flash/_std/haxe/ds/UnsafeStringMap.hx

@@ -90,6 +90,12 @@ class UnsafeStringMap<T> implements haxe.Constraints.IMap<String, T> {
 	public inline function clear():Void {
 		h = new flash.utils.Dictionary();
 	}
+	
+	public function size():Int {
+		var s = 0;
+		for(_ in keys()) s++;
+		return s;
+	}
 }
 
 // this version uses __has_next__/__forin__ special SWF opcodes for iteration with no allocation

+ 6 - 0
std/flash/_std/haxe/ds/WeakMap.hx

@@ -80,6 +80,12 @@ class WeakMap<K:{}, V> extends flash.utils.Dictionary implements haxe.Constraint
 		for (i in keys())
 			untyped __delete__(this, i);
 	}
+	
+	public function size():Int {
+		var s = 0;
+		for(_ in keys()) s++;
+		return s;
+	}
 }
 
 private class NativePropertyIterator {

+ 1 - 0
std/haxe/Constraints.hx

@@ -65,6 +65,7 @@ abstract NotVoid(Dynamic) { }
 abstract Constructible<T>(Dynamic) {}
 
 interface IMap<K, V> {
+	function size():Int;
 	function get(k:K):Null<V>;
 	function set(k:K, v:V):Void;
 	function exists(k:K):Bool;

+ 12 - 0
std/haxe/ds/BalancedTree.hx

@@ -185,6 +185,14 @@ class BalancedTree<K, V> implements haxe.Constraints.IMap<K, V> {
 		}
 	}
 
+	static function sizeLoop<K,V>(node:TreeNode<K, V>):Int {
+		if (node != null) {
+			return sizeLoop(node.left) + 1 + sizeLoop(node.right);
+		} else {
+			return 0;
+		}
+	}
+
 	function merge(t1, t2) {
 		if (t1 == null)
 			return t2;
@@ -236,6 +244,10 @@ class BalancedTree<K, V> implements haxe.Constraints.IMap<K, V> {
 	public function clear():Void {
 		root = null;
 	}
+
+	public function size():Int {
+		return sizeLoop(root);
+	}
 }
 
 /**

+ 7 - 0
std/haxe/ds/HashMap.hx

@@ -107,6 +107,13 @@ abstract HashMap<K:{function hashCode():Int;}, V>(HashMapData<K, V>) {
 		this.keys.clear();
 		this.values.clear();
 	}
+
+	/**
+		See `Map.size`
+	**/
+	public inline function size():Int {
+		return this.keys.size();
+	}
 }
 
 private class HashMapData<K:{function hashCode():Int;}, V> {

+ 2 - 0
std/haxe/ds/IntMap.hx

@@ -96,4 +96,6 @@ extern class IntMap<T> implements haxe.Constraints.IMap<Int, T> {
 		See `Map.clear`
 	**/
 	function clear():Void;
+	
+	function size():Int;
 }

+ 7 - 0
std/haxe/ds/Map.hx

@@ -160,6 +160,13 @@ abstract Map<K, V>(IMap<K, V>) {
 		this.clear();
 	}
 
+	/**
+		Returns size of `this` Map.
+	**/
+	public inline function size():Int {
+		return this.size();
+	}
+
 	@:arrayAccess @:noCompletion public inline function arrayWrite(k:K, v:V):V {
 		this.set(k, v);
 		return v;

+ 2 - 0
std/haxe/ds/ObjectMap.hx

@@ -99,4 +99,6 @@ extern class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 		See `Map.clear`
 	**/
 	function clear():Void;
+	
+	public function size():Int;
 }

+ 2 - 0
std/haxe/ds/StringMap.hx

@@ -96,4 +96,6 @@ extern class StringMap<T> implements haxe.Constraints.IMap<String, T> {
 		See `Map.clear`
 	**/
 	function clear():Void;
+	
+	public function size():Int;
 }

+ 7 - 0
std/haxe/ds/WeakMap.hx

@@ -104,4 +104,11 @@ class WeakMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 		See `Map.clear`
 	**/
 	public function clear():Void {}
+
+	/**
+		See `Map.size`
+	**/
+	public function size():Int {
+		return 0;
+	}
 }

+ 23 - 0
std/hl/_std/EReg.hx

@@ -69,6 +69,23 @@ private typedef ERegValue = hl.Abstract<"ereg">;
 		return {pos: p, len: len};
 	}
 
+	public function matchedNum():Int {
+		if(last == null) return 0;
+		#if (hl_ver >= version("1.12.0"))
+		return regexp_matched_num(r);
+		#else
+		var i = 0;
+		var num = 0;
+		try {
+			while (true) {
+				if (regexp_matched_pos(r, i, null) >= 0) num++;
+				i++;
+			}
+		} catch (_:String) {}
+		return num;
+		#end
+	}
+
 	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 (p)
@@ -199,4 +216,10 @@ private typedef ERegValue = hl.Abstract<"ereg">;
 	@:hlNative("std", "regexp_matched_pos") static function regexp_matched_pos(r:ERegValue, n:Int, size:hl.Ref<Int>):Int {
 		return 0;
 	}
+
+	#if (hl_ver >= version("1.12.0"))
+	@:hlNative("std", "regexp_matched_num") static function regexp_matched_num(r:ERegValue):Int {
+		return 0;
+	}
+	#end
 }

+ 8 - 0
std/hl/_std/haxe/ds/IntMap.hx

@@ -88,4 +88,12 @@ class IntMap<T> implements haxe.Constraints.IMap<Int, T> {
 		h = new hl.types.IntMap();
 		#end
 	}
+	
+	public function size():Int {
+		#if (hl_ver >= version("1.12.0"))
+		return h.size();
+		#else
+		return h.keysArray().length;
+		#end
+	}
 }

+ 8 - 0
std/hl/_std/haxe/ds/ObjectMap.hx

@@ -88,4 +88,12 @@ class ObjectMap<K:{}, T> implements haxe.Constraints.IMap<K, T> {
 		h = new hl.types.ObjectMap();
 		#end
 	}
+	
+	public function size():Int {
+		#if (hl_ver >= version("1.12.0"))
+		return h.size();
+		#else
+		return h.keysArray().length;
+		#end
+	}
 }

+ 8 - 0
std/hl/_std/haxe/ds/StringMap.hx

@@ -116,4 +116,12 @@ class StringMap<T> implements haxe.Constraints.IMap<String, T> {
 		h = new hl.types.BytesMap();
 		#end
 	}
+	
+	public function size():Int {
+		#if (hl_ver >= version("1.12.0"))
+		return h.size();
+		#else
+		return h.keysArray().length;
+		#end
+	}
 }

+ 7 - 0
std/hl/types/BytesMap.hx

@@ -65,6 +65,13 @@ abstract BytesMap(BytesMapData) {
 	@:hlNative("std", "hbclear")
 	public function clear():Void {}
 	#end
+	
+	#if (hl_ver >= version("1.12.0"))
+	@:hlNative("std", "hbsize")
+	public function size():Int {
+		return 0;
+	}
+	#end
 
 	extern public inline function iterator() {
 		return new NativeArray.NativeArrayIterator<Dynamic>(valuesArray());

+ 7 - 0
std/hl/types/IntMap.hx

@@ -65,6 +65,13 @@ abstract IntMap(IntMapData) {
 	@:hlNative("std", "hiclear")
 	public function clear():Void {}
 	#end
+	
+	#if (hl_ver >= version("1.12.0"))
+	@:hlNative("std", "hisize")
+	public function size():Int {
+		return 0;
+	}
+	#end
 
 	extern public inline function iterator() {
 		return new NativeArray.NativeArrayIterator<Dynamic>(valuesArray());

+ 7 - 0
std/hl/types/ObjectMap.hx

@@ -65,6 +65,13 @@ abstract ObjectMap(ObjectMapData) {
 	@:hlNative("std", "hoclear")
 	public function clear():Void {}
 	#end
+	
+	#if (hl_ver >= version("1.12.0"))
+	@:hlNative("std", "hosize")
+	public function size():Int {
+		return 0;
+	}
+	#end
 
 	extern public inline function iterator() {
 		return new NativeArray.NativeArrayIterator<Dynamic>(valuesArray());

+ 6 - 0
std/js/_std/EReg.hx

@@ -57,6 +57,12 @@
 		return {pos: r.m.index, len: r.m[0].length};
 	}
 
+	public function matchedNum():Int {
+		if (r.m == null)
+			return 0;
+		return r.m.length;
+	}
+
 	public function matchSub(s:String, pos:Int, len:Int = -1):Bool {
 		return if (r.global) {
 			r.lastIndex = pos;

+ 10 - 0
std/js/_std/haxe/ds/IntMap.hx

@@ -82,6 +82,10 @@ package haxe.ds;
 	public inline function clear():Void {
 		m.clear();
 	}
+
+	public inline function size():Int {
+		return m.size;
+	}
 }
 #else
 @:coreApi class IntMap<T> implements haxe.Constraints.IMap<Int, T> {
@@ -159,5 +163,11 @@ package haxe.ds;
 	public inline function clear():Void {
 		h = {};
 	}
+
+	public inline function size():Int {
+		var s:Any = 0;
+		js.Syntax.code("for( var key in {0} ) if({0}.hasOwnProperty(key)) {1}++", h, s);
+		return s;
+	}
 }
 #end

+ 10 - 0
std/js/_std/haxe/ds/ObjectMap.hx

@@ -86,6 +86,10 @@ class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 	public inline function clear():Void {
 		m.clear();
 	}
+
+	public inline function size():Int {
+		return m.size;
+	}
 }
 #else
 @:coreApi
@@ -184,5 +188,11 @@ class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 	public inline function clear():Void {
 		h = {__keys__: {}};
 	}
+
+	public inline function size():Int {
+		var s:Any = 0;
+		js.Syntax.code("for( var key in {0} ) if({0}.hasOwnProperty(key)) {1}++", h.__keys__, s);
+		return s;
+	}
 }
 #end

+ 16 - 0
std/js/_std/haxe/ds/StringMap.hx

@@ -86,6 +86,10 @@ import haxe.DynamicAccess;
 	public inline function clear():Void {
 		m.clear();
 	}
+
+	public inline function size():Int {
+		return m.size;
+	}
 }
 #elseif (js_es == 5)
 @:coreApi class StringMap<T> implements IMap<String, T> {
@@ -139,6 +143,12 @@ import haxe.DynamicAccess;
 		return stringify(h);
 	}
 
+	public inline function size():Int {
+		var s:Any = 0;
+		js.Syntax.code("for( var key in {0} ) {1}++", h, s);
+		return s;
+	}
+
 	// impl
 	static function createCopy<T>(h:Dynamic):StringMap<T> {
 		var copy = new StringMap();
@@ -344,6 +354,12 @@ private class StringMapIterator<T> {
 		rh = null;
 	}
 
+	public inline function size():Int {
+		var s:Any = 0;
+		js.Syntax.code("for( var key in {0} ) if({0}.hasOwnProperty(key)) {1}++", h, s);
+		return s;
+	}
+
 	static function __init__():Void {
 		js.Syntax.code("var __map_reserved = {};");
 	}

+ 12 - 0
std/jvm/_std/EReg.hx

@@ -71,6 +71,18 @@ using StringTools;
 		return {pos: start, len: matcher.end() - start};
 	}
 
+	public function matchedNum():Int {
+		try {
+			if (matcher.group() == null) {
+				return 0;
+			} else {
+				return matcher.groupCount() + 1;
+			}
+		} catch (e) {
+			return 0;
+		}
+	}
+
 	public function matchSub(s:String, pos:Int, len:Int = -1):Bool {
 		matcher = matcher.reset(len < 0 ? s : s.substr(0, pos + len));
 		cur = s;

+ 4 - 0
std/jvm/_std/haxe/ds/IntMap.hx

@@ -88,4 +88,8 @@ class IntMap<T> implements haxe.Constraints.IMap<Int, T> {
 	public function clear():Void {
 		hashMap.clear();
 	}
+
+	public function size():Int {
+		return hashMap.size();
+	}
 }

+ 14 - 10
std/jvm/_std/haxe/ds/ObjectMap.hx

@@ -43,7 +43,7 @@ import java.NativeArray;
 	private var vals:NativeArray<V>;
 
 	private var nBuckets:Int;
-	private var size:Int;
+	private var _size:Int;
 	private var nOccupied:Int;
 	private var upperBound:Int;
 
@@ -68,7 +68,7 @@ import java.NativeArray;
 	public function set(key:K, value:V):Void {
 		var x:Int, k:Int;
 		if (nOccupied >= upperBound) {
-			if (nBuckets > (size << 1))
+			if (nBuckets > (_size << 1))
 				resize(nBuckets - 1); // clear "deleted" elements
 			else
 				resize(nBuckets + 2);
@@ -117,13 +117,13 @@ import java.NativeArray;
 			keys[x] = key;
 			vals[x] = value;
 			hashes[x] = k;
-			size++;
+			_size++;
 			nOccupied++;
 		} else if (isDel(flag)) {
 			keys[x] = key;
 			vals[x] = value;
 			hashes[x] = k;
-			size++;
+			_size++;
 		} else {
 			assert(keys[x] == key);
 			vals[x] = value;
@@ -171,10 +171,10 @@ import java.NativeArray;
 			newNBuckets = roundUp(newNBuckets);
 			if (newNBuckets < 4)
 				newNBuckets = 4;
-			if (size >= (newNBuckets * HASH_UPPER + 0.5))
-				/* requested size is too small */ {
+			if (_size >= (newNBuckets * HASH_UPPER + 0.5))
+				/* requested _size is too small */ {
 				j = 0;
-			} else { /* hash table size to be changed (shrink or expand); rehash */
+			} else { /* hash table _size to be changed (shrink or expand); rehash */
 				var nfSize = newNBuckets;
 				newHash = new NativeArray(nfSize);
 				if (nBuckets < newNBuckets) // expand
@@ -263,7 +263,7 @@ import java.NativeArray;
 
 			this.hashes = newHash;
 			this.nBuckets = newNBuckets;
-			this.nOccupied = size;
+			this.nOccupied = _size;
 			this.upperBound = Std.int(newNBuckets * HASH_UPPER + .5);
 		}
 	}
@@ -351,7 +351,7 @@ import java.NativeArray;
 			hashes[idx] = FLAG_DEL;
 			_keys[idx] = null;
 			vals[idx] = null;
-			--size;
+			--_size;
 
 			return true;
 		}
@@ -396,7 +396,7 @@ import java.NativeArray;
 		_keys = null;
 		vals = null;
 		nBuckets = 0;
-		size = 0;
+		_size = 0;
 		nOccupied = 0;
 		upperBound = 0;
 		#if !no_map_cache
@@ -410,6 +410,10 @@ import java.NativeArray;
 		maxProbe = 0;
 		#end
 	}
+	
+	public inline function size():Int {
+		return _size;
+	}
 
 	extern private static inline function roundUp(x:Int):Int {
 		--x;

+ 4 - 0
std/jvm/_std/haxe/ds/StringMap.hx

@@ -88,4 +88,8 @@ class StringMap<T> implements haxe.Constraints.IMap<String, T> {
 	public function clear():Void {
 		hashMap.clear();
 	}
+	
+	public inline function size():Int {
+		return hashMap.size();
+	}
 }

+ 15 - 11
std/jvm/_std/haxe/ds/WeakMap.hx

@@ -47,7 +47,7 @@ import java.lang.ref.ReferenceQueue;
 	private var queue:ReferenceQueue<K>;
 
 	private var nBuckets:Int;
-	private var size:Int;
+	private var _size:Int;
 	private var nOccupied:Int;
 	private var upperBound:Int;
 
@@ -96,7 +96,7 @@ import java.lang.ref.ReferenceQueue;
 					#end
 					entries[i] = null;
 					hashes[i] = FLAG_DEL;
-					--size;
+					--_size;
 				}
 			}
 		}
@@ -106,7 +106,7 @@ import java.lang.ref.ReferenceQueue;
 		cleanupRefs();
 		var x:Int, k:Int;
 		if (nOccupied >= upperBound) {
-			if (nBuckets > (size << 1))
+			if (nBuckets > (_size << 1))
 				resize(nBuckets - 1); // clear "deleted" elements
 			else
 				resize(nBuckets + 2);
@@ -154,12 +154,12 @@ import java.lang.ref.ReferenceQueue;
 		if (isEmpty(flag)) {
 			entries[x] = entry;
 			hashes[x] = k;
-			size++;
+			_size++;
 			nOccupied++;
 		} else if (isDel(flag)) {
 			entries[x] = entry;
 			hashes[x] = k;
-			size++;
+			_size++;
 		} else {
 			assert(entries[x].keyEquals(key));
 			entries[x] = entry;
@@ -207,10 +207,10 @@ import java.lang.ref.ReferenceQueue;
 			newNBuckets = roundUp(newNBuckets);
 			if (newNBuckets < 4)
 				newNBuckets = 4;
-			if (size >= (newNBuckets * HASH_UPPER + 0.5))
-				/* requested size is too small */ {
+			if (_size >= (newNBuckets * HASH_UPPER + 0.5))
+				/* requested _size is too small */ {
 				j = 0;
-			} else { /* hash table size to be changed (shrink or expand); rehash */
+			} else { /* hash table _size to be changed (shrink or expand); rehash */
 				var nfSize = newNBuckets;
 				newHash = new NativeArray(nfSize);
 				if (nBuckets < newNBuckets) // expand
@@ -279,7 +279,7 @@ import java.lang.ref.ReferenceQueue;
 
 			this.hashes = newHash;
 			this.nBuckets = newNBuckets;
-			this.nOccupied = size;
+			this.nOccupied = _size;
 			this.upperBound = Std.int(newNBuckets * HASH_UPPER + .5);
 		}
 	}
@@ -375,7 +375,7 @@ import java.lang.ref.ReferenceQueue;
 
 			hashes[idx] = FLAG_DEL;
 			entries[idx] = null;
-			--size;
+			--_size;
 
 			return true;
 		}
@@ -422,7 +422,7 @@ import java.lang.ref.ReferenceQueue;
 		entries = null;
 		queue = new ReferenceQueue();
 		nBuckets = 0;
-		size = 0;
+		_size = 0;
 		nOccupied = 0;
 		upperBound = 0;
 		#if !no_map_cache
@@ -436,6 +436,10 @@ import java.lang.ref.ReferenceQueue;
 		maxProbe = 0;
 		#end
 	}
+	
+	public inline function size():Int {
+		return _size;
+	}
 
 	extern private static inline function roundUp(x:Int):Int {
 		--x;

+ 8 - 0
std/lua/_std/EReg.hx

@@ -111,6 +111,14 @@ class EReg {
 		}
 	}
 
+	public function matchedNum():Int {
+		if (m == null) return 0;
+		else if (m[1] == null)
+			return 0;
+		else
+			return 1 + untyped __lua_length__(m[3]) / 2;
+	}
+
 	public function matchSub(s:String, pos:Int, len:Int = -1):Bool {
 		var ss = s.substr(0, len < 0 ? s.length : pos + len);
 

+ 6 - 0
std/lua/_std/haxe/ds/IntMap.hx

@@ -112,4 +112,10 @@ class IntMap<T> implements haxe.Constraints.IMap<Int, T> {
 	public inline function clear():Void {
 		h = lua.Table.create();
 	}
+	
+	public function size():Int {
+		var s = 0;
+		untyped __lua__("for _ in pairs({0}) do s = s + 1 end", h);
+		return s;
+	}
 }

+ 6 - 0
std/lua/_std/haxe/ds/ObjectMap.hx

@@ -117,4 +117,10 @@ class ObjectMap<A, B> implements haxe.Constraints.IMap<A, B> {
 		h = lua.Table.create();
 		k = lua.Table.create();
 	}
+	
+	public function size():Int {
+		var s = 0;
+		untyped __lua__("for _ in pairs({0}) do s = s + 1 end", h);
+		return s;
+	}
 }

+ 6 - 0
std/lua/_std/haxe/ds/StringMap.hx

@@ -116,4 +116,10 @@ class StringMap<T> implements haxe.Constraints.IMap<String, T> {
 	public inline function clear():Void {
 		h = lua.Table.create();
 	}
+	
+	public function size():Int {
+		var s = 0;
+		untyped __lua__("for _ in pairs({0}) do s = s + 1 end", h);
+		return s;
+	}
 }

+ 19 - 0
std/neko/_std/EReg.hx

@@ -70,6 +70,12 @@
 		return p;
 	}
 
+	public function matchedNum():Int {
+		var num = regexp_matched_num(r);
+		if(last == null || num == -1) return 0;
+		return num;
+	}
+
 	public function split(s:String):Array<String> {
 		var pos = 0;
 		var len = s.length;
@@ -206,4 +212,17 @@
 	static var regexp_match = neko.Lib.load("regexp", "regexp_match", 4);
 	static var regexp_matched = neko.Lib.load("regexp", "regexp_matched", 2);
 	static var regexp_matched_pos:Dynamic->Int->{pos: Int, len: Int} = neko.Lib.load("regexp", "regexp_matched_pos", 2);
+	static var regexp_matched_num = try neko.Lib.load("regexp", "regexp_matched_num", 1) catch (_:Dynamic) fallback_matched_num;
+
+	private static function fallback_matched_num(r:Dynamic):Int {
+		var i = 0;
+		var num = 0;
+		try {
+			while (true) {
+				if (regexp_matched(r, i) != null) num++;
+				i++;
+			}
+		} catch (_:Dynamic) {}
+		return num;
+	}
 }

+ 4 - 0
std/neko/_std/haxe/ds/IntMap.hx

@@ -90,4 +90,8 @@ package haxe.ds;
 	public inline function clear():Void {
 		h = untyped __dollar__hnew(0);
 	}
+	
+	public inline function size():Int {
+		return untyped __dollar__hcount(h);
+	}
 }

+ 4 - 0
std/neko/_std/haxe/ds/ObjectMap.hx

@@ -111,4 +111,8 @@ class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 		h = untyped __dollar__hnew(0);
 		k = untyped __dollar__hnew(0);
 	}
+	
+	public inline function size():Int {
+		return untyped __dollar__hcount(k);
+	}
 }

+ 4 - 0
std/neko/_std/haxe/ds/StringMap.hx

@@ -90,4 +90,8 @@ package haxe.ds;
 	public inline function clear():Void {
 		h = untyped __dollar__hnew(0);
 	}
+	
+	public inline function size():Int {
+		return untyped __dollar__hcount(h);
+	}
 }

+ 5 - 0
std/php/_std/EReg.hx

@@ -109,6 +109,11 @@ import php.*;
 		};
 	}
 
+	public function matchedNum():Int {
+		if(matches == null) return 0;
+		return Global.count(matches);
+	}
+
 	public function matchSub(s:String, pos:Int, len:Int = -1):Bool {
 		var subject = len < 0 ? s : s.substr(0, pos + len);
 		var p = Global.preg_match(reUnicode, subject, matches, Const.PREG_OFFSET_CAPTURE, pos);

+ 4 - 0
std/php/_std/haxe/ds/IntMap.hx

@@ -85,4 +85,8 @@ import php.NativeIndexedArray;
 	public inline function clear():Void {
 		data = new NativeIndexedArray();
 	}
+	
+	public inline function size():Int {
+		return Global.count(data);
+	}
 }

+ 4 - 0
std/php/_std/haxe/ds/ObjectMap.hx

@@ -94,4 +94,8 @@ class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 		_keys = new NativeAssocArray();
 		_values = new NativeAssocArray();
 	}
+	
+	public inline function size():Int {
+		return Global.count(_keys);
+	}
 }

+ 4 - 0
std/php/_std/haxe/ds/StringMap.hx

@@ -86,4 +86,8 @@ import haxe.Constraints;
 	public inline function clear():Void {
 		data = new NativeAssocArray();
 	}
+	
+	public inline function size():Int {
+		return Global.count(data);
+	}
 }

+ 5 - 0
std/python/_std/EReg.hx

@@ -72,6 +72,11 @@ class EReg {
 		return {pos: matchObj.start(), len: matchObj.end() - matchObj.start()};
 	}
 
+	public function matchedNum():Int {
+		if (matchObj == null) return 0;
+		return (matchObj.lastindex ?? 0) + 1;
+	}
+
 	public function matchSub(s:String, pos:Int, len:Int = -1):Bool {
 		if (len != -1) {
 			matchObj = pattern.search(s, pos, pos + len);

+ 4 - 0
std/python/_std/haxe/ds/IntMap.hx

@@ -88,4 +88,8 @@ class IntMap<T> implements haxe.Constraints.IMap<Int, T> {
 	public inline function clear():Void {
 		h.clear();
 	}
+	
+	public inline function size():Int {
+		return h.length;
+	}
 }

+ 4 - 0
std/python/_std/haxe/ds/ObjectMap.hx

@@ -87,4 +87,8 @@ class ObjectMap<K:{}, V> implements haxe.Constraints.IMap<K, V> {
 	public inline function clear():Void {
 		h.clear();
 	}
+	
+	public inline function size():Int {
+		return h.length;
+	}
 }

+ 4 - 0
std/python/_std/haxe/ds/StringMap.hx

@@ -89,4 +89,8 @@ class StringMap<T> implements haxe.Constraints.IMap<String, T> {
 	public inline function clear():Void {
 		h.clear();
 	}
+	
+	public inline function size():Int {
+		return h.length;
+	}
 }

+ 1 - 0
tests/unit/.vscode/settings.json

@@ -5,6 +5,7 @@
 		{"label": "JVM", "args": ["compile-jvm-only.hxml", "-cmd", "java -jar bin/unit.jar"]},
 		{"label": "Interp", "args": ["compile-macro.hxml"]},
 		{"label": "Lua", "args": ["compile-lua.hxml", "-cmd", "lua bin/unit.lua"]},
+		{"label": "Python", "args": ["compile-python.hxml", "-cmd", "python3 bin/unit.py"]},
 	],
 	"[haxe]": {
 		"editor.formatOnSave": true,

+ 23 - 0
tests/unit/src/unitstd/EReg.unit.hx

@@ -43,6 +43,29 @@ var pos = rg2.matchedPos();
 pos.pos == 1;
 pos.len == 2;
 
+// matched num
+var rg3 = ~/a/;
+var rg4 = ~/a(b)/;
+var rg5 = ~/a(b)(c)/;
+var rg6 = ~/a(b)(c)?/;
+
+rg3.match("a") == true;
+rg3.matchedNum() == 1;
+rg3.match("b") == false;
+rg3.matchedNum() == 0;
+
+rg4.match("a") == false;
+rg4.matchedNum() == 0;
+rg4.match("ab") == true;
+rg4.matchedNum() == 2;
+
+rg5.match("a") == false;
+rg5.matchedNum() == 0;
+rg5.match("abc") == true;
+rg5.matchedNum() == 3;
+rg6.match("ab") == true;
+rg5.matchedNum() == 3;
+
 // split
 ~/a/.split("") == [""];
 ~/a/.split("a") == ["",""];

+ 18 - 0
tests/unit/src/unitstd/Map.unit.hx

@@ -327,3 +327,21 @@ var it:KeyValueIterator<String,String> = cast it;
 var keys = [for(kv in it) kv.key];
 keys[0] in ["1a","1b"];
 keys[1] in ["1a","1b"];
+
+// Test size
+
+new Map<Int, String>().size() == 0;
+[1 => "a"].size() == 1;
+[1 => "a", 3 => "c"].size() == 2;
+
+new Map<String, Int>().size() == 0;
+["a" => 1].size() == 1;
+["a" => 1, "b" => 3].size() == 2;
+
+new Map<{a: Int}, String>().size() == 0;
+[{a: 1} => "a"].size() == 1;
+[{a: 1} => "a", {a: 3} => "c"].size() == 2;
+
+new Map<unit.MyAbstract.ClassWithHashCode, Int>().size() == 0;
+[new unit.MyAbstract.ClassWithHashCode(1) => 1].size() == 1;
+[new unit.MyAbstract.ClassWithHashCode(1) => 1, new unit.MyAbstract.ClassWithHashCode(3) => 3].size() == 2;