فهرست منبع

add haxe.macro.MacroStringTools, exposing format_string (closes #1852)

Simon Krajewski 12 سال پیش
والد
کامیت
6622272c61
7فایلهای تغییر یافته به همراه176 افزوده شده و 90 حذف شده
  1. 11 0
      interp.ml
  2. 1 1
      parser.ml
  3. 1 0
      std/haxe/macro/Context.hx
  4. 0 3
      std/haxe/macro/ExprTools.hx
  5. 70 0
      std/haxe/macro/MacroStringTools.hx
  6. 2 1
      std/haxe/macro/Tools.hx
  7. 91 85
      typer.ml

+ 11 - 0
interp.ml

@@ -119,6 +119,7 @@ type extern_api = {
 	current_module : unit -> module_def;
 	delayed_macro : int -> (unit -> (unit -> value));
 	use_cache : unit -> bool;
+	format_string : string -> Ast.pos -> Ast.expr;
 }
 
 type callstack = {
@@ -2275,6 +2276,16 @@ let macro_lib =
 		"s_type", Fun1 (fun v ->
 			VString (Type.s_type (print_context()) (decode_type v))
 		);
+		"is_fmt_string", Fun1 (fun v ->
+			match v with
+			| VAbstract (APos p) -> VBool(Lexer.is_fmt_string p)
+			| _ -> VNull
+		);
+		"format_string", Fun2 (fun s p ->
+			match s,p with
+			| VString(s),VAbstract(APos p) -> encode_expr ((get_ctx()).curapi.format_string s p)
+			| _ -> VNull
+		);
 		"display", Fun1 (fun v ->
 			match v with
 			| VString s ->

+ 1 - 1
parser.ml

@@ -411,7 +411,7 @@ let reify in_macro =
 			| Meta.Dollar "i", _ ->
 				expr "EConst" [mk_enum "Constant" "CIdent" [e1] (pos e1)]
 			| Meta.Dollar "p", _ ->
-				(ECall ((EField ((EField ((EField ((EConst (Ident "haxe"),p),"macro"),p),"ExprTools"),p),"toFieldExpr"),p),[e]),p)
+				(ECall ((EField ((EField ((EField ((EConst (Ident "haxe"),p),"macro"),p),"MacroStringTools"),p),"toFieldExpr"),p),[e]),p)
 			| Meta.Custom ":pos", [pexpr] ->
 				let old = !cur_pos in
 				cur_pos := Some pexpr;

+ 1 - 0
std/haxe/macro/Context.hx

@@ -376,6 +376,7 @@ class Context {
 	}
 
 	@:allow(haxe.macro.TypeTools)
+	@:allow(haxe.macro.MacroStringTools)
 	static function load( f, nargs ) : Dynamic {
 		#if macro
 		return neko.Lib.load("macro", f, nargs);

+ 0 - 3
std/haxe/macro/ExprTools.hx

@@ -35,9 +35,6 @@ using Lambda;
 **/
 class ExprTools {
 
-	static public function toFieldExpr ( sl : Array<String> ) : Expr
-		return sl.fold(function(s, e) return e == null ? (macro $i{s}) : (macro $e.$s), null);
-
 	/**
 		Converts expression [e] to a human-readable String representation.
 

+ 70 - 0
std/haxe/macro/MacroStringTools.hx

@@ -0,0 +1,70 @@
+/*
+ * Copyright (C)2005-2013 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.macro;
+
+import haxe.macro.Expr;
+
+/**
+	This class provides some utility methods to work with strings in macro
+	context.
+**/
+class MacroStringTools {
+	#if macro
+	
+	
+	/**
+		Formats `String` `s` using the usual interpolation rules.
+		
+		The returned expression is a concatenation of string parts and escaped
+		elements.
+	**/
+	static public function formatString(s:String, pos:Position) {
+		return Context.load("format_string", 2)(untyped s.__s, pos);
+	}
+	#end
+	
+	/**
+		Tells if `e` is a format string, i.e. uses single quotes `'` as
+		delimiters.
+		
+		This operation depends on the position of `e`.
+	**/
+	static public function isFormatExpr(e:ExprOf<String>) {
+		return Context.load("is_fmt_string", 1)(e.pos);
+	}
+	
+	/**
+		Converts an array of Strings `sl` to a field expression.
+		
+		If `sl` has no elements, the result is null.
+		
+		If `sl` has one element, the result is `EConst(CIdent(sl[0])`.
+		
+		Otherwise the result is a chain of `EField` nodes.
+		
+		If `sl` is null, the result is unspecified.
+	**/
+	static public function toFieldExpr(sl:Array<String>):Expr {
+		return Lambda.fold(sl, function(s, e) return e == null ? (macro $i{s}) : (macro $e.$s), null);
+	}
+}

+ 2 - 1
std/haxe/macro/Tools.hx

@@ -27,4 +27,5 @@ package haxe.macro;
 **/
 typedef TExprTools = ExprTools;
 typedef TComplexTypeTools = ComplexTypeTools;
-typedef TTypeTools = TypeTools;
+typedef TTypeTools = TypeTools;
+typedef TMacroStringTools = MacroStringTools;

+ 91 - 85
typer.ml

@@ -2137,6 +2137,93 @@ and with_type_error ctx with_type msg p =
 	| WithTypeResume _ -> raise (WithTypeError ([Unify_custom msg],p))
 	| _ -> display_error ctx msg p
 
+and format_string ctx s p =
+	let e = ref None in
+	let pmin = ref p.pmin in
+	let min = ref (p.pmin + 1) in
+	let add enext len =
+		let p = { p with pmin = !min; pmax = !min + len } in
+		min := !min + len;
+		match !e with
+		| None -> e := Some (enext,p)
+		| Some prev ->
+			e := Some (EBinop (OpAdd,prev,(enext,p)),punion (pos prev) p)
+	in
+	let add_sub start pos =
+		let len = pos - start in
+		if len > 0 || !e = None then add (EConst (String (String.sub s start len))) len
+	in
+	let warn_escape = Common.defined ctx.com Define.FormatWarning in
+	let warn pos len =
+		ctx.com.warning "This string is formated" { p with pmin = !pmin + 1 + pos; pmax = !pmin + 1 + pos + len }
+	in
+	let len = String.length s in
+	let rec parse start pos =
+		if pos = len then add_sub start pos else
+		let c = String.unsafe_get s pos in
+		let pos = pos + 1 in
+		if c = '\'' then begin
+			incr pmin;
+			incr min;
+		end;
+		if c <> '$' || pos = len then parse start pos else
+		match String.unsafe_get s pos with
+		| '$' ->
+			if warn_escape then warn pos 1;
+			(* double $ *)
+			add_sub start pos;
+			parse (pos + 1) (pos + 1)
+		| '{' ->
+			parse_group start pos '{' '}' "brace"
+		| 'a'..'z' | 'A'..'Z' | '_' ->
+			add_sub start (pos - 1);
+			incr min;
+			let rec loop i =
+				if i = len then i else
+				let c = String.unsafe_get s i in
+				match c with
+				| 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' -> loop (i+1)
+				| _ -> i
+			in
+			let iend = loop (pos + 1) in
+			let len = iend - pos in
+			if warn_escape then warn pos len;
+			add (EConst (Ident (String.sub s pos len))) len;
+			parse (pos + len) (pos + len)
+		| _ ->
+			(* keep as-it *)
+			parse start pos
+	and parse_group start pos gopen gclose gname =
+		add_sub start (pos - 1);
+		let rec loop groups i =
+			if i = len then
+				match groups with
+				| [] -> assert false
+				| g :: _ -> error ("Unclosed " ^ gname) { p with pmin = !pmin + g + 1; pmax = !pmin + g + 2 }
+			else
+				let c = String.unsafe_get s i in
+				if c = gopen then
+					loop (i :: groups) (i + 1)
+				else if c = gclose then begin
+					let groups = List.tl groups in
+					if groups = [] then i else loop groups (i + 1)
+				end else
+					loop groups (i + 1)
+		in
+		let send = loop [pos] (pos + 1) in
+		let slen = send - pos - 1 in
+		let scode = String.sub s (pos + 1) slen in
+		if warn_escape then warn (pos + 1) slen;
+		min := !min + 2;
+		add (fst (parse_expr_string ctx scode { p with pmin = !pmin + pos + 2; pmax = !pmin + send + 1 } true)) slen;
+		min := !min + 1;
+		parse (send + 1) (send + 1)
+	in
+	parse 0 0;
+	match !e with
+	| None -> assert false
+	| Some e -> e
+
 and type_expr ctx (e,p) (with_type:with_type) =
 	match e with
 	| EField ((EConst (String s),p),"code") ->
@@ -2173,91 +2260,7 @@ and type_expr ctx (e,p) (with_type:with_type) =
 		let t = Typeload.load_core_type ctx "EReg" in
 		mk (TNew ((match t with TInst (c,[]) -> c | _ -> assert false),[],[str;opt])) t p
 	| EConst (String s) when Lexer.is_fmt_string p ->
-		let e = ref None in
-		let pmin = ref p.pmin in
-		let min = ref (p.pmin + 1) in
-		let add enext len =
-			let p = { p with pmin = !min; pmax = !min + len } in
-			min := !min + len;
-			match !e with
-			| None -> e := Some (enext,p)
-			| Some prev ->
-				e := Some (EBinop (OpAdd,prev,(enext,p)),punion (pos prev) p)
-		in
-		let add_sub start pos =
-			let len = pos - start in
-			if len > 0 || !e = None then add (EConst (String (String.sub s start len))) len
-		in
-		let warn_escape = Common.defined ctx.com Define.FormatWarning in
-		let warn pos len =
-			ctx.com.warning "This string is formated" { p with pmin = !pmin + 1 + pos; pmax = !pmin + 1 + pos + len }
-		in
-		let len = String.length s in
-		let rec parse start pos =
-			if pos = len then add_sub start pos else
-			let c = String.unsafe_get s pos in
-			let pos = pos + 1 in
-			if c = '\'' then begin
-				incr pmin;
-				incr min;
-			end;
-			if c <> '$' || pos = len then parse start pos else
-			match String.unsafe_get s pos with
-			| '$' ->
-				if warn_escape then warn pos 1;
-				(* double $ *)
-				add_sub start pos;
-				parse (pos + 1) (pos + 1)
-			| '{' ->
-				parse_group start pos '{' '}' "brace"
-			| 'a'..'z' | 'A'..'Z' | '_' ->
-				add_sub start (pos - 1);
-				incr min;
-				let rec loop i =
-					if i = len then i else
-					let c = String.unsafe_get s i in
-					match c with
-					| 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' -> loop (i+1)
-					| _ -> i
-				in
-				let iend = loop (pos + 1) in
-				let len = iend - pos in
-				if warn_escape then warn pos len;
-				add (EConst (Ident (String.sub s pos len))) len;
-				parse (pos + len) (pos + len)
-			| _ ->
-				(* keep as-it *)
-				parse start pos
-		and parse_group start pos gopen gclose gname =
-			add_sub start (pos - 1);
-			let rec loop groups i =
-				if i = len then
-					match groups with
-					| [] -> assert false
-					| g :: _ -> error ("Unclosed " ^ gname) { p with pmin = !pmin + g + 1; pmax = !pmin + g + 2 }
-				else
-					let c = String.unsafe_get s i in
-					if c = gopen then
-						loop (i :: groups) (i + 1)
-					else if c = gclose then begin
-						let groups = List.tl groups in
-						if groups = [] then i else loop groups (i + 1)
-					end else
-						loop groups (i + 1)
-			in
-			let send = loop [pos] (pos + 1) in
-			let slen = send - pos - 1 in
-			let scode = String.sub s (pos + 1) slen in
-			if warn_escape then warn (pos + 1) slen;
-			min := !min + 2;
-			add (fst (parse_expr_string ctx scode { p with pmin = !pmin + pos + 2; pmax = !pmin + send + 1 } true)) slen;
-			min := !min + 1;
-			parse (send + 1) (send + 1)
-		in
-		parse 0 0;
-		(match !e with
-		| None -> assert false
-		| Some e -> type_expr ctx e with_type);
+		type_expr ctx (format_string ctx s p) with_type
 	| EConst c ->
 		Codegen.type_constant ctx.com c p
     | EBinop (op,e1,e2) ->
@@ -3606,6 +3609,9 @@ let make_macro_api ctx p =
 		Interp.use_cache = (fun() ->
 			!macro_enable_cache
 		);
+		Interp.format_string = (fun s p ->
+			format_string ctx s p
+		);
 	}
 
 let rec init_macro_interp ctx mctx mint =