Bladeren bron

rework format strings

Dan Korostelev 8 jaren geleden
bovenliggende
commit
c00bbd7a90

+ 5 - 0
src/generators/gencpp.ml

@@ -416,9 +416,13 @@ let join_class_path_remap path separator =
    | x -> x
    | x -> x
 ;;
 ;;
 
 
+let error_single_quote_meta key pos =
+   abort (Printf.sprintf "Use double quotes for the %s argument" (Meta.to_string key)) pos
+
 let get_meta_string meta key =
 let get_meta_string meta key =
    let rec loop = function
    let rec loop = function
       | [] -> ""
       | [] -> ""
+      | (k,[Ast.EFormat _,pos],_) :: _ when k=key -> error_single_quote_meta key pos
       | (k,[Ast.EConst (Ast.String name),_],_) :: _  when k=key-> name
       | (k,[Ast.EConst (Ast.String name),_],_) :: _  when k=key-> name
       | _ :: l -> loop l
       | _ :: l -> loop l
       in
       in
@@ -430,6 +434,7 @@ let get_meta_string meta key =
 let get_meta_string_path meta key =
 let get_meta_string_path meta key =
    let rec loop = function
    let rec loop = function
       | [] -> ""
       | [] -> ""
+      | (k,[Ast.EFormat _,pos],_) :: _ when k=key -> error_single_quote_meta key pos
       | (k,[Ast.EConst (Ast.String name),_], pos) :: _  when k=key->
       | (k,[Ast.EConst (Ast.String name),_], pos) :: _  when k=key->
            (try
            (try
            if (String.sub name 0 2) = "./" then begin
            if (String.sub name 0 2) = "./" then begin

+ 3 - 2
src/macro/eval/evalEncode.ml

@@ -85,6 +85,7 @@ let encode_enum i pos index pl =
 		| IFieldAccess -> key_haxe_macro_FieldAccess
 		| IFieldAccess -> key_haxe_macro_FieldAccess
 		| IAnonStatus -> key_haxe_macro_AnonStatus
 		| IAnonStatus -> key_haxe_macro_AnonStatus
 		| IImportMode -> key_haxe_macro_ImportMode
 		| IImportMode -> key_haxe_macro_ImportMode
+		| IFormatSegmentKind -> key_haxe_macro_FormatSegmentKind
 	in
 	in
 	encode_enum_value key index (Array.of_list pl) pos
 	encode_enum_value key index (Array.of_list pl) pos
 
 
@@ -128,10 +129,10 @@ let encode_bytes s =
 
 
 let encode_int_map_direct h =
 let encode_int_map_direct h =
 	encode_instance key_haxe_ds_IntMap ~kind:(IIntMap h)
 	encode_instance key_haxe_ds_IntMap ~kind:(IIntMap h)
-	
+
 let encode_string_map_direct h =
 let encode_string_map_direct h =
 	encode_instance key_haxe_ds_StringMap ~kind:(IStringMap h)
 	encode_instance key_haxe_ds_StringMap ~kind:(IStringMap h)
-	
+
 let encode_object_map_direct h =
 let encode_object_map_direct h =
 	encode_instance key_haxe_ds_ObjectMap ~kind:(IObjectMap (Obj.magic h))
 	encode_instance key_haxe_ds_ObjectMap ~kind:(IObjectMap (Obj.magic h))
 
 

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

@@ -119,6 +119,7 @@ let key_haxe_macro_ModuleType = hash_s "haxe.macro.ModuleType"
 let key_haxe_macro_FieldAccess = hash_s "haxe.macro.FieldAccess"
 let key_haxe_macro_FieldAccess = hash_s "haxe.macro.FieldAccess"
 let key_haxe_macro_AnonStatus = hash_s "haxe.macro.AnonStatus"
 let key_haxe_macro_AnonStatus = hash_s "haxe.macro.AnonStatus"
 let key_haxe_macro_ImportMode = hash_s "haxe.macro.ImportMode"
 let key_haxe_macro_ImportMode = hash_s "haxe.macro.ImportMode"
+let key_haxe_macro_FormatSegmentKind = hash_s "haxe.macro.FormatSegmentKind"
 let key_haxe_CallStack = hash_s "haxe.CallStack"
 let key_haxe_CallStack = hash_s "haxe.CallStack"
 let key___init__ = hash_s "__init__"
 let key___init__ = hash_s "__init__"
 let key_new = hash_s "new"
 let key_new = hash_s "new"

+ 33 - 9
src/macro/macroApi.ml

@@ -75,6 +75,7 @@ type enum_type =
 	| IFieldAccess
 	| IFieldAccess
 	| IAnonStatus
 	| IAnonStatus
 	| IImportMode
 	| IImportMode
+	| IFormatSegmentKind
 
 
 type obj_type =
 type obj_type =
 	(* make_const *)
 	(* make_const *)
@@ -93,6 +94,7 @@ type obj_type =
 	| OCase
 	| OCase
 	| OCatch
 	| OCatch
 	| OExpr
 	| OExpr
+	| OFormatSegment
 	(* Type *)
 	(* Type *)
 	| OMetaAccess
 	| OMetaAccess
 	| OTypeParameter
 	| OTypeParameter
@@ -209,6 +211,7 @@ let enum_name = function
 	| IFieldAccess -> "FieldAccess"
 	| IFieldAccess -> "FieldAccess"
 	| IAnonStatus -> "AnonStatus"
 	| IAnonStatus -> "AnonStatus"
 	| IImportMode -> "ImportMode"
 	| IImportMode -> "ImportMode"
+	| IFormatSegmentKind -> "FormatSegmentKind"
 
 
 let proto_name = function
 let proto_name = function
 	| O__Const -> assert false
 	| O__Const -> assert false
@@ -225,6 +228,7 @@ let proto_name = function
 	| OCase -> "Case", None
 	| OCase -> "Case", None
 	| OCatch -> "Catch", None
 	| OCatch -> "Catch", None
 	| OExpr -> "Expr", None
 	| OExpr -> "Expr", None
+	| OFormatSegment -> "FormatSegment", None
 	| OMetaAccess -> "MetaAccess", None
 	| OMetaAccess -> "MetaAccess", None
 	| OTypeParameter -> "TypeParameter", None
 	| OTypeParameter -> "TypeParameter", None
 	| OClassType -> "ClassType", None
 	| OClassType -> "ClassType", None
@@ -256,7 +260,7 @@ let proto_name = function
 	| ORef -> "Ref", None
 	| ORef -> "Ref", None
 
 
 let all_enums =
 let all_enums =
-	let last = IImportMode in
+	let last = IFormatSegmentKind in
 	let rec loop i =
 	let rec loop i =
 		let e : enum_type = Obj.magic i in
 		let e : enum_type = Obj.magic i in
 		if e = last then [e] else e :: loop (i + 1)
 		if e = last then [e] else e :: loop (i + 1)
@@ -359,6 +363,9 @@ let encode_import (path,mode) =
 let encode_placed_name (s,p) =
 let encode_placed_name (s,p) =
 	encode_string s
 	encode_string s
 
 
+let encode_and_map_array convert l =
+	encode_array (List.map convert l)
+
 let rec encode_path (t,_) =
 let rec encode_path (t,_) =
 	let fields = [
 	let fields = [
 		"pack", encode_array (List.map encode_string t.tpackage);
 		"pack", encode_array (List.map encode_string t.tpackage);
@@ -543,6 +550,8 @@ and encode_expr e =
 				28, [loop e; encode_ctype t]
 				28, [loop e; encode_ctype t]
 			| EMeta (m,e) ->
 			| EMeta (m,e) ->
 				29, [encode_meta_entry m;loop e]
 				29, [encode_meta_entry m;loop e]
+			| EFormat parts ->
+				30, [encode_and_map_array encode_format_part parts]
 		in
 		in
 		encode_obj OExpr [
 		encode_obj OExpr [
 			"pos", encode_pos p;
 			"pos", encode_pos p;
@@ -551,6 +560,17 @@ and encode_expr e =
 	in
 	in
 	loop e
 	loop e
 
 
+and encode_format_part part =
+	let part,pos = part in
+	let tag, pl = match part with
+		| FmtRaw s -> 0, [encode_string s]
+		| FmtIdent i -> 1, [encode_string i]
+		| FmtExpr e -> 2, [encode_expr e]
+	in
+	let kind = encode_enum IFormatSegmentKind tag pl in
+	let pos = encode_pos pos in
+	encode_obj OFormatSegment ["pos",pos; "kind",kind]
+
 and encode_null_expr e =
 and encode_null_expr e =
 	match e with
 	match e with
 	| None ->
 	| None ->
@@ -802,8 +822,10 @@ and decode_expr v =
 			ECheckType (loop e, (decode_ctype t))
 			ECheckType (loop e, (decode_ctype t))
 		| 29, [m;e] ->
 		| 29, [m;e] ->
 			EMeta (decode_meta_entry m,loop e)
 			EMeta (decode_meta_entry m,loop e)
-		| 30, [e;f] ->
-			EField (loop e, decode_string f) (*** deprecated EType, keep until haxe 3 **)
+		| 30, [pl] ->
+			let pl = decode_array pl in
+			let pl = List.map decode_format_part pl in
+			EFormat pl
 		| _ ->
 		| _ ->
 			raise Invalid_expr
 			raise Invalid_expr
 	in
 	in
@@ -812,6 +834,14 @@ and decode_expr v =
 	with Stack_overflow ->
 	with Stack_overflow ->
 		raise Invalid_expr
 		raise Invalid_expr
 
 
+and decode_format_part v =
+	let p = decode_pos (field v "pos") in
+	(match decode_enum (field v "kind") with
+	| 0, [vs] -> FmtRaw (decode_string vs)
+	| 1, [vs] -> FmtIdent (decode_string vs)
+	| 2, [ve] -> FmtExpr (decode_expr ve)
+	| _ -> raise Invalid_expr),p
+
 (* ---------------------------------------------------------------------- *)
 (* ---------------------------------------------------------------------- *)
 (* TYPE ENCODING *)
 (* TYPE ENCODING *)
 
 
@@ -820,9 +850,6 @@ let encode_pmap_array convert m =
 	PMap.iter (fun _ v -> l := !l @ [(convert v)]) m;
 	PMap.iter (fun _ v -> l := !l @ [(convert v)]) m;
 	encode_array !l
 	encode_array !l
 
 
-let encode_and_map_array convert l =
-	encode_array (List.map convert l)
-
 let vopt f v = match v with
 let vopt f v = match v with
 	| None -> vnull
 	| None -> vnull
 	| Some v -> f v
 	| Some v -> f v
@@ -1578,9 +1605,6 @@ let macro_api ccom get_api =
 			let f = if decode_opt_bool b then Type.s_expr_pretty false "" false else Type.s_expr_ast true "" in
 			let f = if decode_opt_bool b then Type.s_expr_pretty false "" false else Type.s_expr_ast true "" in
 			encode_string (f (Type.s_type (print_context())) (decode_texpr v))
 			encode_string (f (Type.s_type (print_context())) (decode_texpr v))
 		);
 		);
-		"is_fmt_string", vfun1 (fun p ->
-			vbool (Lexer.is_fmt_string (decode_pos p))
-		);
 		"format_string", vfun2 (fun s p ->
 		"format_string", vfun2 (fun s p ->
 			encode_expr ((get_api()).format_string (decode_string s) (decode_pos p))
 			encode_expr ((get_api()).format_string (decode_string s) (decode_pos p))
 		);
 		);

+ 7 - 8
src/macro/macroContext.ml

@@ -337,7 +337,12 @@ let make_macro_api ctx p =
 			!macro_enable_cache
 			!macro_enable_cache
 		);
 		);
 		MacroApi.format_string = (fun s p ->
 		MacroApi.format_string = (fun s p ->
-			ctx.g.do_format_string ctx s p
+			let s = Printf.sprintf "'%s'" s in
+			let e = parse_expr_string s p true in
+			match fst e with
+			| EConst (String _) -> e
+			| EFormat parts -> Expr.format_string parts p
+			| _ -> raise MacroApi.Invalid_expr
 		);
 		);
 		MacroApi.cast_or_unify = (fun t e p ->
 		MacroApi.cast_or_unify = (fun t e p ->
 			typing_timer ctx true (fun () ->
 			typing_timer ctx true (fun () ->
@@ -613,7 +618,6 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
 		| _ ->
 		| _ ->
 			el,[]
 			el,[]
 	in
 	in
-	let todo = ref [] in
 	let args =
 	let args =
 		(*
 		(*
 			force default parameter types to haxe.macro.Expr, and if success allow to pass any value type since it will be encoded
 			force default parameter types to haxe.macro.Expr, and if success allow to pass any value type since it will be encoded
@@ -634,11 +638,7 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
 		let constants = List.map (fun e ->
 		let constants = List.map (fun e ->
 			let p = snd e in
 			let p = snd e in
 			let e = (try
 			let e = (try
-				(match Codegen.type_constant_value ctx.com e with
-				| { eexpr = TConst (TString _); epos = p } when Lexer.is_fmt_string p ->
-					Lexer.remove_fmt_string p;
-					todo := (fun() -> Lexer.add_fmt_string p) :: !todo;
-				| _ -> ());
+				ignore(Codegen.type_constant_value ctx.com e);
 				e
 				e
 			with Error (Custom _,_) ->
 			with Error (Custom _,_) ->
 				(* if it's not a constant, let's make something that is typed as haxe.macro.Expr - for nice error reporting *)
 				(* if it's not a constant, let's make something that is typed as haxe.macro.Expr - for nice error reporting *)
@@ -652,7 +652,6 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
 			(EArray ((EArrayDecl [e],p),(EConst (Int (string_of_int (!index))),p)),p)
 			(EArray ((EArrayDecl [e],p),(EConst (Int (string_of_int (!index))),p)),p)
 		) el in
 		) el in
 		let elt, _ = unify_call_args mctx constants (List.map fst eargs) t_dynamic p false false in
 		let elt, _ = unify_call_args mctx constants (List.map fst eargs) t_dynamic p false false in
-		List.iter (fun f -> f()) (!todo);
 		List.map2 (fun (_,mct) e ->
 		List.map2 (fun (_,mct) e ->
 			let e, et = (match e.eexpr with
 			let e, et = (match e.eexpr with
 				(* get back our index and real expression *)
 				(* get back our index and real expression *)

+ 52 - 1
src/syntax/ast.ml

@@ -105,6 +105,7 @@ type constant =
 type token =
 type token =
 	| Eof
 	| Eof
 	| Const of constant
 	| Const of constant
+	| Fmt of (fmt_token * pos) list
 	| Kwd of keyword
 	| Kwd of keyword
 	| Comment of string
 	| Comment of string
 	| CommentLine of string
 	| CommentLine of string
@@ -127,6 +128,11 @@ type token =
 	| At
 	| At
 	| Dollar of string
 	| Dollar of string
 
 
+and fmt_token =
+	| Raw of string
+	| Name of string
+	| Code of (token * pos) list
+
 type unop_flag =
 type unop_flag =
 	| Prefix
 	| Prefix
 	| Postfix
 	| Postfix
@@ -169,6 +175,7 @@ and placed_name = string * pos
 
 
 and expr_def =
 and expr_def =
 	| EConst of constant
 	| EConst of constant
+	| EFormat of (format_part * pos) list
 	| EArray of expr * expr
 	| EArray of expr * expr
 	| EBinop of binop * expr * expr
 	| EBinop of binop * expr * expr
 	| EField of expr * string
 	| EField of expr * string
@@ -201,6 +208,11 @@ and expr_def =
 
 
 and expr = expr_def * pos
 and expr = expr_def * pos
 
 
+and format_part =
+	| FmtRaw of string
+	| FmtIdent of string
+	| FmtExpr of expr
+
 and type_param = {
 and type_param = {
 	tp_name : placed_name;
 	tp_name : placed_name;
 	tp_params :	type_param list;
 	tp_params :	type_param list;
@@ -439,9 +451,18 @@ let s_unop = function
 	| Neg -> "-"
 	| Neg -> "-"
 	| NegBits -> "~"
 	| NegBits -> "~"
 
 
-let s_token = function
+let rec s_token = function
 	| Eof -> "<end of file>"
 	| Eof -> "<end of file>"
 	| Const c -> s_constant c
 	| Const c -> s_constant c
+	| Fmt pl ->
+		let pl = List.map (fun p -> match fst p with
+			| Raw s -> s
+			| Name s -> "$" ^ s
+			| Code tl ->
+				let tl = List.map (fun (t,_) -> s_token t) tl in
+				"${" ^ (String.concat "" tl) ^ "}"
+		) pl in
+		"'" ^ (String.concat "" pl)  ^ "'"
 	| Kwd k -> s_keyword k
 	| Kwd k -> s_keyword k
 	| Comment s -> "/*"^s^"*/"
 	| Comment s -> "/*"^s^"*/"
 	| CommentLine s -> "//"^s
 	| CommentLine s -> "//"^s
@@ -574,6 +595,9 @@ let map_expr loop (e,p) =
 	in
 	in
 	let e = (match e with
 	let e = (match e with
 	| EConst _ -> e
 	| EConst _ -> e
+	| EFormat parts ->
+		let parts = List.map (fun p -> match fst p with FmtRaw _ | FmtIdent _ -> p | FmtExpr e -> (FmtExpr (loop e), snd p)) parts in
+		EFormat parts
 	| EArray (e1,e2) ->
 	| EArray (e1,e2) ->
 		let e1 = loop e1 in
 		let e1 = loop e1 in
 		let e2 = loop e2 in
 		let e2 = loop e2 in
@@ -681,6 +705,8 @@ let iter_expr loop (e,p) =
 			opt e;
 			opt e;
 		) cases;
 		) cases;
 		(match def with None -> () | Some (e,_) -> opt e);
 		(match def with None -> () | Some (e,_) -> opt e);
+	| EFormat parts ->
+		List.iter (fun p -> match fst p with FmtRaw _ | FmtIdent _ -> () | FmtExpr e1 -> loop e1) parts
 	| EFunction(_,f) ->
 	| EFunction(_,f) ->
 		List.iter (fun (_,_,_,_,eo) -> opt eo) f.f_args;
 		List.iter (fun (_,_,_,_,eo) -> opt eo) f.f_args;
 		opt f.f_expr
 		opt f.f_expr
@@ -690,6 +716,14 @@ let s_expr e =
 	let rec s_expr_inner tabs (e,_) =
 	let rec s_expr_inner tabs (e,_) =
 		match e with
 		match e with
 		| EConst c -> s_constant c
 		| EConst c -> s_constant c
+		| EFormat parts ->
+			let parts = List.map (fun p ->
+				match fst p with
+				| FmtRaw s -> s
+				| FmtIdent i -> "$" ^ i
+				| FmtExpr e -> "${" ^ (s_expr_inner tabs e) ^ "}"
+			) parts in
+			Printf.sprintf "'%s'" (String.concat "" parts)
 		| EArray (e1,e2) -> s_expr_inner tabs e1 ^ "[" ^ s_expr_inner tabs e2 ^ "]"
 		| EArray (e1,e2) -> s_expr_inner tabs e1 ^ "[" ^ s_expr_inner tabs e2 ^ "]"
 		| EBinop (op,e1,e2) -> s_expr_inner tabs e1 ^ " " ^ s_binop op ^ " " ^ s_expr_inner tabs e2
 		| EBinop (op,e1,e2) -> s_expr_inner tabs e1 ^ " " ^ s_binop op ^ " " ^ s_expr_inner tabs e2
 		| EField (e,f) -> s_expr_inner tabs e ^ "." ^ f
 		| EField (e,f) -> s_expr_inner tabs e ^ "." ^ f
@@ -866,4 +900,21 @@ module Expr = struct
 			| [] -> raise Not_found
 			| [] -> raise Not_found
 		in
 		in
 		loop fl
 		loop fl
+
+	let format_string parts p =
+		let concat e1 e2 = (EBinop (OpAdd,e1,e2), (punion (pos e1) (pos e2))) in
+		let empty_string_expr = EConst (String ""),p in
+		let edef,_ = List.fold_left (fun expr part ->
+			let part, pos = part in
+			match part with
+			| FmtRaw s ->
+				let string_part_expr = (EConst (String s),pos) in
+				if expr == empty_string_expr then string_part_expr else concat expr string_part_expr
+			| FmtIdent i ->
+				let eident = EConst (Ident i), {pos with pmin = pos.pmin + 1 (* identifier position is after the $ *)} in
+				concat expr eident
+			| FmtExpr e ->
+				concat expr e
+		) empty_string_expr parts in
+		edef,p
 end
 end

+ 85 - 66
src/syntax/lexer.ml

@@ -49,7 +49,6 @@ type lexer_file = {
 	mutable lmaxline : int;
 	mutable lmaxline : int;
 	mutable llines : (int * int) list;
 	mutable llines : (int * int) list;
 	mutable lalines : (int * int) array;
 	mutable lalines : (int * int) array;
-	mutable lstrings : int list;
 	mutable llast : int;
 	mutable llast : int;
 	mutable llastindex : int;
 	mutable llastindex : int;
 }
 }
@@ -61,7 +60,6 @@ let make_file file =
 		lmaxline = 1;
 		lmaxline = 1;
 		llines = [0,1];
 		llines = [0,1];
 		lalines = [|0,1|];
 		lalines = [|0,1|];
-		lstrings = [];
 		llast = max_int;
 		llast = max_int;
 		llastindex = 0;
 		llastindex = 0;
 	}
 	}
@@ -103,37 +101,6 @@ let newline lexbuf =
 	cur.lline <- cur.lline + 1;
 	cur.lline <- cur.lline + 1;
 	cur.llines <- (lexeme_end lexbuf,cur.lline) :: cur.llines
 	cur.llines <- (lexeme_end lexbuf,cur.lline) :: cur.llines
 
 
-let fmt_pos p =
-	p.pmin + (p.pmax - p.pmin) * 1000000
-
-let add_fmt_string p =
-	let file = (try
-		Hashtbl.find all_files p.pfile
-	with Not_found ->
-		let f = make_file p.pfile in
-		Hashtbl.replace all_files p.pfile f;
-		f
-	) in
-	file.lstrings <- (fmt_pos p) :: file.lstrings
-
-let fast_add_fmt_string p =
-	let cur = !cur in
-	cur.lstrings <- (fmt_pos p) :: cur.lstrings
-
-let is_fmt_string p =
-	try
-		let file = Hashtbl.find all_files p.pfile in
-		List.mem (fmt_pos p) file.lstrings
-	with Not_found ->
-		false
-
-let remove_fmt_string p =
-	try
-		let file = Hashtbl.find all_files p.pfile in
-		file.lstrings <- List.filter ((<>) (fmt_pos p)) file.lstrings
-	with Not_found ->
-		()
-
 let find_line p f =
 let find_line p f =
 	(* rebuild cache if we have a new line *)
 	(* rebuild cache if we have a new line *)
 	if f.lmaxline <> f.lline then begin
 	if f.lmaxline <> f.lline then begin
@@ -233,10 +200,19 @@ let mk_ident lexbuf =
 	let s = lexeme lexbuf in
 	let s = lexeme lexbuf in
 	mk lexbuf (try Kwd (Hashtbl.find keywords s) with Not_found -> Const (Ident s))
 	mk lexbuf (try Kwd (Hashtbl.find keywords s) with Not_found -> Const (Ident s))
 
 
+(* we create simple String tokens for single-quote strings that don't contain interpolated values *)
+let mk_fmt_tok parts pmin pmax =
+	let t =
+		match parts with
+		| [] -> Const (String "")
+		| [(Raw s,_)] -> Const (String s)
+		| _ -> Fmt parts
+	in
+	mk_tok t pmin pmax
+
 let invalid_char lexbuf =
 let invalid_char lexbuf =
 	error (Invalid_character (lexeme_char lexbuf 0)) (lexeme_start lexbuf)
 	error (Invalid_character (lexeme_char lexbuf 0)) (lexeme_start lexbuf)
 
 
-
 let ident = [%sedlex.regexp?
 let ident = [%sedlex.regexp?
 	(
 	(
 		Star '_',
 		Star '_',
@@ -257,6 +233,8 @@ let idtype = [%sedlex.regexp? Star '_', 'A'..'Z', Star ('_' | 'a'..'z' | 'A'..'Z
 
 
 let integer = [%sedlex.regexp? ('1'..'9', Star ('0'..'9')) | '0']
 let integer = [%sedlex.regexp? ('1'..'9', Star ('0'..'9')) | '0']
 
 
+let whitespace = [%sedlex.regexp? Plus (Chars " \t")]
+
 let rec skip_header lexbuf =
 let rec skip_header lexbuf =
 	match%sedlex lexbuf with
 	match%sedlex lexbuf with
 	| 0xfeff -> skip_header lexbuf
 	| 0xfeff -> skip_header lexbuf
@@ -267,7 +245,7 @@ let rec skip_header lexbuf =
 let rec token lexbuf =
 let rec token lexbuf =
 	match%sedlex lexbuf with
 	match%sedlex lexbuf with
 	| eof -> mk lexbuf Eof
 	| eof -> mk lexbuf Eof
-	| Plus (Chars " \t") -> token lexbuf
+	| whitespace -> token lexbuf
 	| "\r\n" -> newline lexbuf; token lexbuf
 	| "\r\n" -> newline lexbuf; token lexbuf
 	| '\n' | '\r' -> newline lexbuf; token lexbuf
 	| '\n' | '\r' -> newline lexbuf; token lexbuf
 	| "0x", Plus ('0'..'9'|'a'..'f'|'A'..'F') -> mk lexbuf (Const (Int (lexeme lexbuf)))
 	| "0x", Plus ('0'..'9'|'a'..'f'|'A'..'F') -> mk lexbuf (Const (Int (lexeme lexbuf)))
@@ -347,11 +325,8 @@ let rec token lexbuf =
 	| "'" ->
 	| "'" ->
 		reset();
 		reset();
 		let pmin = lexeme_start lexbuf in
 		let pmin = lexeme_start lexbuf in
-		let pmax = (try string2 lexbuf with Exit -> error Unterminated_string pmin) in
-		let str = (try unescape (contents()) with Invalid_escape_sequence(c,i) -> error (Invalid_escape c) (pmin + i)) in
-		let t = mk_tok (Const (String str)) pmin pmax in
-		fast_add_fmt_string (snd t);
-		t
+		let parts,pmax = (try string2 (pmin+1) [] lexbuf with Exit -> error Unterminated_string pmin) in
+		mk_fmt_tok (List.rev parts) pmin pmax
 	| "~/" ->
 	| "~/" ->
 		reset();
 		reset();
 		let pmin = lexeme_start lexbuf in
 		let pmin = lexeme_start lexbuf in
@@ -390,51 +365,95 @@ and string lexbuf =
 	| Plus (Compl ('"' | '\\' | '\r' | '\n')) -> store lexbuf; string lexbuf
 	| Plus (Compl ('"' | '\\' | '\r' | '\n')) -> store lexbuf; string lexbuf
 	| _ -> assert false
 	| _ -> assert false
 
 
-and string2 lexbuf =
+and string2 pmin parts lexbuf =
+	let consume_part() =
+		let contents = contents() in
+		reset();
+		if contents = "" then
+			parts
+		else begin
+			let pmax = lexeme_start lexbuf in
+			let str = (try unescape contents with Invalid_escape_sequence(c,i) -> error (Invalid_escape c) (pmin + i)) in
+			let part = mk_tok (Raw str) pmin pmax in
+			part :: parts
+		end
+	in
 	match%sedlex lexbuf with
 	match%sedlex lexbuf with
 	| eof -> raise Exit
 	| eof -> raise Exit
-	| '\n' | '\r' | "\r\n" -> newline lexbuf; store lexbuf; string2 lexbuf
-	| '\\' -> store lexbuf; string2 lexbuf
-	| "\\\\" -> store lexbuf; string2 lexbuf
-	| "\\'" -> store lexbuf; string2 lexbuf
-	| "'" -> lexeme_end lexbuf
-	| "$$" | "\\$" | '$' -> store lexbuf; string2 lexbuf
+	| '\n' | '\r' | "\r\n" -> newline lexbuf; store lexbuf; string2 pmin parts lexbuf
+	| '\\' -> store lexbuf; string2 pmin parts lexbuf
+	| "\\\\" -> store lexbuf; string2 pmin parts lexbuf
+	| "\\'" -> store lexbuf; string2 pmin parts lexbuf
+	| "'" ->
+		consume_part(), lexeme_end lexbuf
+	| "$$" -> add "$"; string2 pmin parts lexbuf
+	| '$', ident ->
+		let parts = consume_part() in
+		let pmin = lexeme_start lexbuf in
+		let pmax = lexeme_end lexbuf in
+		let ident =
+			let v = lexeme lexbuf in
+			String.sub v 1 (String.length v - 1)
+		in
+		let part = mk_tok (Name ident) pmin pmax in
+		string2 pmax (part :: parts) lexbuf
+	| "\\$" | '$' -> store lexbuf; string2 pmin parts lexbuf
 	| "${" ->
 	| "${" ->
+		let parts = consume_part() in
 		let pmin = lexeme_start lexbuf in
 		let pmin = lexeme_start lexbuf in
-		store lexbuf;
-		(try code_string lexbuf 0 with Exit -> error Unclosed_code pmin);
-		string2 lexbuf;
-	| Plus (Compl ('\'' | '\\' | '\r' | '\n' | '$')) -> store lexbuf; string2 lexbuf
+		let start = lexeme_end lexbuf in
+		let tokens,pmax = (try code_string start lexbuf 0 with Exit -> error Unclosed_code pmin) in
+		let part = mk_tok (Code tokens) pmin pmax in
+		let parts = part :: parts in
+		string2 pmax parts lexbuf
+	| Plus (Compl ('\'' | '\\' | '\r' | '\n' | '$')) -> store lexbuf; string2 pmin parts lexbuf
 	| _ -> assert false
 	| _ -> assert false
 
 
-and code_string lexbuf open_braces =
+and code_string start lexbuf open_braces =
+	let consume() =
+		let code = contents() in
+		let code_lexbuf = Sedlexing.Utf8.from_string code in
+		let rec loop acc =
+			match token code_lexbuf with
+			| (Eof,_) -> acc
+			| (tok,p) ->
+				let tok = tok, {p with pmin = p.pmin + start; pmax = p.pmax + start} in
+				loop (tok :: acc)
+		in
+		let r = List.rev (loop []) in
+		reset();
+		r
+	in
 	match%sedlex lexbuf with
 	match%sedlex lexbuf with
 	| eof -> raise Exit
 	| eof -> raise Exit
-	| '\n' | '\r' | "\r\n" -> newline lexbuf; store lexbuf; code_string lexbuf open_braces
-	| '{' -> store lexbuf; code_string lexbuf (open_braces + 1)
-	| '/' -> store lexbuf; code_string lexbuf open_braces
+	| whitespace -> code_string start lexbuf open_braces
+	| '\n' | '\r' | "\r\n" -> newline lexbuf; store lexbuf; code_string start lexbuf open_braces
+	| '{' -> store lexbuf; code_string start lexbuf (open_braces + 1)
+	| '/' -> store lexbuf; code_string start lexbuf open_braces
 	| '}' ->
 	| '}' ->
-		store lexbuf;
-		if open_braces > 0 then code_string lexbuf (open_braces - 1)
+		if open_braces > 0 then
+			(store lexbuf; code_string start lexbuf (open_braces - 1))
+		else
+			consume(),lexeme_end lexbuf
 	| '"' ->
 	| '"' ->
 		add "\"";
 		add "\"";
 		let pmin = lexeme_start lexbuf in
 		let pmin = lexeme_start lexbuf in
 		(try ignore(string lexbuf) with Exit -> error Unterminated_string pmin);
 		(try ignore(string lexbuf) with Exit -> error Unterminated_string pmin);
 		add "\"";
 		add "\"";
-		code_string lexbuf open_braces
+		code_string start lexbuf open_braces
 	| "'" ->
 	| "'" ->
-		add "'";
+		let parts = consume() in
 		let pmin = lexeme_start lexbuf in
 		let pmin = lexeme_start lexbuf in
-		let pmax = (try string2 lexbuf with Exit -> error Unterminated_string pmin) in
-		add "'";
-		fast_add_fmt_string { pfile = !cur.lfile; pmin = pmin; pmax = pmax };
-		code_string lexbuf open_braces
+		let tokens,pmax = (try string2 (pmin+1) [] lexbuf with Exit -> error Unterminated_string pmin) in
+		let t = mk_fmt_tok (List.rev tokens) pmin pmax in
+		let rest,pmax = code_string start lexbuf open_braces in
+		parts @ (t :: rest),pmax
 	| "/*" ->
 	| "/*" ->
 		let pmin = lexeme_start lexbuf in
 		let pmin = lexeme_start lexbuf in
 		(try ignore(comment lexbuf) with Exit -> error Unclosed_comment pmin);
 		(try ignore(comment lexbuf) with Exit -> error Unclosed_comment pmin);
-		code_string lexbuf open_braces
-	| "//", Star (Compl ('\n' | '\r')) -> store lexbuf; code_string lexbuf open_braces
-	| Plus (Compl ('/' | '"' | '\'' | '{' | '}' | '\n' | '\r')) -> store lexbuf; code_string lexbuf open_braces
+		code_string start lexbuf open_braces
+	| "//", Star (Compl ('\n' | '\r')) -> store lexbuf; code_string start lexbuf open_braces
+	| Plus (Compl ('/' | '"' | '\'' | '{' | '}' | '\n' | '\r')) -> store lexbuf; code_string start lexbuf open_braces
 	| _ -> assert false
 	| _ -> assert false
 
 
 and regexp lexbuf =
 and regexp lexbuf =

+ 30 - 0
src/syntax/parser.mly

@@ -467,6 +467,8 @@ let reify in_macro =
 			expr "ETernary" [loop e1;loop e2;loop e3]
 			expr "ETernary" [loop e1;loop e2;loop e3]
 		| ECheckType (e1,ct) ->
 		| ECheckType (e1,ct) ->
 			expr "ECheckType" [loop e1; to_type_hint ct p]
 			expr "ECheckType" [loop e1; to_type_hint ct p]
+		| EFormat parts ->
+			expr "EFormat" [to_array to_formatsegment parts p]
 		| EMeta ((m,ml,p),e1) ->
 		| EMeta ((m,ml,p),e1) ->
 			match m, ml with
 			match m, ml with
 			| Meta.Dollar ("" | "e"), _ ->
 			| Meta.Dollar ("" | "e"), _ ->
@@ -495,6 +497,19 @@ let reify in_macro =
 				e
 				e
 			| _ ->
 			| _ ->
 				expr "EMeta" [to_obj [("name",to_string (Meta.to_string m) p);("params",to_expr_array ml p);("pos",to_pos p)] p;loop e1]
 				expr "EMeta" [to_obj [("name",to_string (Meta.to_string m) p);("params",to_expr_array ml p);("pos",to_pos p)] p;loop e1]
+	and to_formatsegment part _ =
+		let f name arg pos =
+			let kind = mk_enum "FormatSegmentKind" name [arg] pos in
+			to_obj [
+				"kind",kind;
+				"pos",to_pos pos;
+			] pos
+		in
+		let part,p = part in
+		match part with
+		| FmtRaw s -> f "FRaw" (to_string s p) p
+		| FmtIdent s -> f "FIdent" (to_string s p) p
+		| FmtExpr e -> f "FExpr" (to_expr e p) p
 	and to_tparam_decl p t =
 	and to_tparam_decl p t =
 		to_obj [
 		to_obj [
 			"name", to_placed_name t.tp_name;
 			"name", to_placed_name t.tp_name;
@@ -1355,6 +1370,9 @@ and expr = parser
 		parse_macro_expr p s
 		parse_macro_expr p s
 	| [< '(Kwd Var,p1); v = parse_var_decl >] -> (EVars [v],p1)
 	| [< '(Kwd Var,p1); v = parse_var_decl >] -> (EVars [v],p1)
 	| [< '(Const c,p); s >] -> expr_next (EConst c,p) s
 	| [< '(Const c,p); s >] -> expr_next (EConst c,p) s
+	| [< '(Fmt parts,p); s >] ->
+		let parts = parse_format_parts parts in
+		expr_next (EFormat parts,p) s
 	| [< '(Kwd This,p); s >] -> expr_next (EConst (Ident "this"),p) s
 	| [< '(Kwd This,p); s >] -> expr_next (EConst (Ident "this"),p) s
 	| [< '(Kwd True,p); s >] -> expr_next (EConst (Ident "true"),p) s
 	| [< '(Kwd True,p); s >] -> expr_next (EConst (Ident "true"),p) s
 	| [< '(Kwd False,p); s >] -> expr_next (EConst (Ident "false"),p) s
 	| [< '(Kwd False,p); s >] -> expr_next (EConst (Ident "false"),p) s
@@ -1461,6 +1479,18 @@ and expr = parser
 	| [< '(Kwd Untyped,p1); e = expr >] -> (EUntyped e,punion p1 (pos e))
 	| [< '(Kwd Untyped,p1); e = expr >] -> (EUntyped e,punion p1 (pos e))
 	| [< '(Dollar v,p); s >] -> expr_next (EConst (Ident ("$"^v)),p) s
 	| [< '(Dollar v,p); s >] -> expr_next (EConst (Ident ("$"^v)),p) s
 
 
+and parse_format_parts parts =
+	List.map (fun (t,p) -> (match t with
+		| Raw s -> FmtRaw s
+		| Name s -> FmtIdent s
+		| Code [] -> FmtRaw ""
+		| Code tokens ->
+			let s = Stream.of_list tokens in
+			let r = FmtExpr (expr s) in
+			(match Stream.peek s with None -> () | Some (tk,p) -> error (Unexpected tk) p);
+			r
+	),p) parts
+
 and expr_next e1 = parser
 and expr_next e1 = parser
 	| [< '(BrOpen,p1) when is_dollar_ident e1; eparam = expr; '(BrClose,p2); s >] ->
 	| [< '(BrOpen,p1) when is_dollar_ident e1; eparam = expr; '(BrClose,p2); s >] ->
 		(match fst e1 with
 		(match fst e1 with

+ 0 - 1
src/typing/typecore.ml

@@ -88,7 +88,6 @@ type typer_globals = {
 	do_load_module : typer -> path -> pos -> module_def;
 	do_load_module : typer -> path -> pos -> module_def;
 	do_optimize : typer -> texpr -> texpr;
 	do_optimize : typer -> texpr -> texpr;
 	do_build_instance : typer -> module_type -> pos -> ((string * t) list * path * (t list -> t));
 	do_build_instance : typer -> module_type -> pos -> ((string * t) list * path * (t list -> t));
-	do_format_string : typer -> string -> pos -> Ast.expr;
 	do_finalize : typer -> unit;
 	do_finalize : typer -> unit;
 	do_generate : typer -> (texpr option * module_type list * module_def list);
 	do_generate : typer -> (texpr option * module_type list * module_def list);
 }
 }

+ 9 - 99
src/typing/typer.ml

@@ -2687,102 +2687,6 @@ and type_vars ctx vl p =
 		let e = mk (TBlock (List.map (fun (v,e) -> (mk (TVar (v,e)) ctx.t.tvoid p)) vl)) ctx.t.tvoid p in
 		let e = mk (TBlock (List.map (fun (v,e) -> (mk (TVar (v,e)) ctx.t.tvoid p)) vl)) ctx.t.tvoid p in
 		mk (TMeta((Meta.MergeBlock,[],p), e)) e.etype e.epos
 		mk (TMeta((Meta.MergeBlock,[],p), e)) e.etype e.epos
 
 
-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_expr (enext,p) len =
-		min := !min + len;
-		let enext = if ctx.in_display && Display.is_display_position p then
-			Display.ExprPreprocessing.process_expr ctx.com (enext,p)
-		else
-			enext,p
-		in
-		match !e with
-		| None -> e := Some enext
-		| Some prev ->
-			e := Some (EBinop (OpAdd,prev,enext),punion (pos prev) p)
-	in
-	let add enext len =
-		let p = { p with pmin = !min; pmax = !min + len } in
-		add_expr (enext,p) len
-	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;
-		if slen > 0 then
-			add_expr (Parser.parse_expr_string ctx.com scode { p with pmin = !pmin + pos + 2; pmax = !pmin + send + 1 } error 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_block ctx el with_type p =
 and type_block ctx el with_type p =
 	let merge e = match e.eexpr with
 	let merge e = match e.eexpr with
 		| TMeta((Meta.MergeBlock,_,_), {eexpr = TBlock el}) ->
 		| TMeta((Meta.MergeBlock,_,_), {eexpr = TBlock el}) ->
@@ -3324,8 +3228,15 @@ and type_expr ctx (e,p) (with_type:with_type) =
 		let opt = mk (TConst (TString opt)) ctx.t.tstring p in
 		let opt = mk (TConst (TString opt)) ctx.t.tstring p in
 		let t = Typeload.load_core_type ctx "EReg" in
 		let t = Typeload.load_core_type ctx "EReg" in
 		mk (TNew ((match t with TInst (c,[]) -> c | _ -> assert false),[],[str;opt])) t p
 		mk (TNew ((match t with TInst (c,[]) -> c | _ -> assert false),[],[str;opt])) t p
-	| EConst (String s) when s <> "" && Lexer.is_fmt_string p ->
-		type_expr ctx (format_string ctx s p) with_type
+	| EFormat parts ->
+		let econcat = Expr.format_string parts p in
+		let econcat =
+			if ctx.in_display && Display.is_display_position p then
+				Display.ExprPreprocessing.process_expr ctx.com econcat
+			else
+				econcat
+		in
+		type_expr ctx econcat with_type
 	| EConst c ->
 	| EConst c ->
 		Codegen.type_constant ctx.com c p
 		Codegen.type_constant ctx.com c p
 	| EBinop (op,e1,e2) ->
 	| EBinop (op,e1,e2) ->
@@ -4449,7 +4360,6 @@ let rec create com =
 			do_load_module = Typeload.load_module;
 			do_load_module = Typeload.load_module;
 			do_optimize = Optimizer.reduce_expression;
 			do_optimize = Optimizer.reduce_expression;
 			do_build_instance = Typeload.build_instance;
 			do_build_instance = Typeload.build_instance;
-			do_format_string = format_string;
 			do_finalize = finalize;
 			do_finalize = finalize;
 			do_generate = generate;
 			do_generate = generate;
 		};
 		};

+ 1 - 1
std/cpp/NativeFile.hx

@@ -1,6 +1,6 @@
 package cpp;
 package cpp;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/std/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/std/Build.xml\"/>")
 extern class NativeFile
 extern class NativeFile
 {
 {
    @:extern @:native("_hx_std_file_open")
    @:extern @:native("_hx_std_file_open")

+ 1 - 1
std/cpp/NativeProcess.hx

@@ -1,6 +1,6 @@
 package cpp;
 package cpp;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/std/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/std/Build.xml\"/>")
 extern class NativeProcess
 extern class NativeProcess
 {
 {
 
 

+ 1 - 1
std/cpp/NativeRandom.hx

@@ -1,6 +1,6 @@
 package cpp;
 package cpp;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/std/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/std/Build.xml\"/>")
 extern class NativeRandom
 extern class NativeRandom
 {
 {
 
 

+ 1 - 1
std/cpp/NativeSocket.hx

@@ -2,7 +2,7 @@ package cpp;
 
 
 import sys.net.Socket;
 import sys.net.Socket;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/std/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/std/Build.xml\"/>")
 extern class NativeSocket
 extern class NativeSocket
 {
 {
    @:extern @:native("_hx_std_socket_init")
    @:extern @:native("_hx_std_socket_init")

+ 1 - 1
std/cpp/NativeSsl.hx

@@ -1,6 +1,6 @@
 package cpp;
 package cpp;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/ssl/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/ssl/Build.xml\"/>")
 extern class NativeSsl
 extern class NativeSsl
 {
 {
    @:extern @:native("_hx_ssl_new")
    @:extern @:native("_hx_ssl_new")

+ 1 - 1
std/cpp/NativeSys.hx

@@ -1,6 +1,6 @@
 package cpp;
 package cpp;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/std/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/std/Build.xml\"/>")
 extern class NativeSys
 extern class NativeSys
 {
 {
    @:native("__hxcpp_print")
    @:native("__hxcpp_print")

+ 1 - 1
std/cpp/_std/EReg.hx

@@ -20,7 +20,7 @@
  * DEALINGS IN THE SOFTWARE.
  * DEALINGS IN THE SOFTWARE.
  */
  */
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/regexp/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/regexp/Build.xml\"/>")
 @:coreApi class EReg {
 @:coreApi class EReg {
 
 
 	var r : Dynamic;
 	var r : Dynamic;

+ 1 - 1
std/cpp/_std/haxe/zip/Compress.hx

@@ -21,7 +21,7 @@
  */
  */
 package haxe.zip;
 package haxe.zip;
 
 
-@:coreApi @:buildXml('<include name="${HXCPP}/src/hx/libs/zlib/Build.xml" />')
+@:coreApi @:buildXml("<include name=\"${HXCPP}/src/hx/libs/zlib/Build.xml\" />")
 class Compress {
 class Compress {
 
 
 	var s : Dynamic;
 	var s : Dynamic;

+ 1 - 1
std/cpp/_std/haxe/zip/Uncompress.hx

@@ -21,7 +21,7 @@
  */
  */
 package haxe.zip;
 package haxe.zip;
 
 
-@:coreApi @:buildXml('<include name="${HXCPP}/src/hx/libs/zlib/Build.xml"/>')
+@:coreApi @:buildXml("<include name=\"${HXCPP}/src/hx/libs/zlib/Build.xml\"/>")
 class Uncompress {
 class Uncompress {
 	var s : Dynamic;
 	var s : Dynamic;
 
 

+ 1 - 1
std/cpp/_std/sys/FileSystem.hx

@@ -23,7 +23,7 @@ package sys;
 
 
 import cpp.NativeSys;
 import cpp.NativeSys;
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/std/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/std/Build.xml\"/>")
 @:coreApi
 @:coreApi
 class FileSystem {
 class FileSystem {
 
 

+ 2 - 2
std/cpp/_std/sys/db/Mysql.hx

@@ -131,7 +131,7 @@ private class MysqlConnection implements sys.db.Connection {
 		__c = c;
 		__c = c;
 	 D.set_conv_funs( cpp.Function.fromStaticFunction(D.charsToBytes),
 	 D.set_conv_funs( cpp.Function.fromStaticFunction(D.charsToBytes),
                      cpp.Function.fromStaticFunction(D.secondsToDate) );
                      cpp.Function.fromStaticFunction(D.secondsToDate) );
-    
+
 	}
 	}
 
 
 	public function request( s : String ) : sys.db.ResultSet {
 	public function request( s : String ) : sys.db.ResultSet {
@@ -194,7 +194,7 @@ private class MysqlConnection implements sys.db.Connection {
 	private static var __use_date = Date;
 	private static var __use_date = Date;
 }
 }
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/mysql/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/mysql/Build.xml\"/>")
 @:coreApi class Mysql {
 @:coreApi class Mysql {
 
 
 	public static function connect( params : {
 	public static function connect( params : {

+ 1 - 1
std/cpp/_std/sys/db/Sqlite.hx

@@ -197,7 +197,7 @@ private class SqliteResultSet implements ResultSet {
 
 
 }
 }
 
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/sqlite/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/sqlite/Build.xml\"/>")
 @:coreApi class Sqlite {
 @:coreApi class Sqlite {
 
 
 	public static function open( file : String ) : Connection {
 	public static function open( file : String ) : Connection {

+ 42 - 0
std/haxe/macro/Expr.hx

@@ -495,6 +495,48 @@ enum ExprDef {
 		A `@m e` expression.
 		A `@m e` expression.
 	**/
 	**/
 	EMeta( s : MetadataEntry, e : Expr );
 	EMeta( s : MetadataEntry, e : Expr );
+
+	/**
+		An interpolated string expression, e.g. `'hello $name!'`.
+
+		@see https://haxe.org/manual/lf-string-interpolation.html
+	**/
+	EFormat( s : Array<FormatSegment> );
+}
+
+/**
+	Represents a segment within an interpolated format string.
+**/
+typedef FormatSegment = {
+	/**
+		The segment kind.
+	**/
+	var kind:FormatSegmentKind;
+
+	/**
+		The position of the segment.
+	**/
+	var pos:Position;
+}
+
+/**
+	Represents the kind of an interpolated format string segment.
+**/
+enum FormatSegmentKind {
+	/**
+		Raw (non-interpolated) string content.
+	**/
+	FRaw(s:String);
+
+	/**
+		Single identifier interpolation, e.g. `$name`.
+	**/
+	FIdent(i:String);
+
+	/**
+		Expression interpolation, e.g. `${name.toUpperCase()}`.
+	**/
+	FExpr(e:Expr);
 }
 }
 
 
 /**
 /**

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

@@ -128,6 +128,11 @@ class ExprTools {
 				}
 				}
 				if (edef != null && edef.expr != null)
 				if (edef != null && edef.expr != null)
 					f(edef);
 					f(edef);
+			case EFormat(parts):
+				for (part in parts) switch part.kind {
+					case FRaw(_) | FIdent(_):
+					case FExpr(e): f(e);
+				}
 		}
 		}
 	}
 	}
 
 
@@ -208,6 +213,11 @@ class ExprTools {
 					ret.push( { name: arg.name, opt: arg.opt, type: arg.type, value: opt(arg.value, f) } );
 					ret.push( { name: arg.name, opt: arg.opt, type: arg.type, value: opt(arg.value, f) } );
 				EFunction(name, { args: ret, ret: func.ret, params: func.params, expr: f(func.expr) } );
 				EFunction(name, { args: ret, ret: func.ret, params: func.params, expr: f(func.expr) } );
 			case EMeta(m, e): EMeta(m, f(e));
 			case EMeta(m, e): EMeta(m, f(e));
+			case EFormat(parts):
+				EFormat([for (part in parts) switch part.kind {
+					case FRaw(_) | FIdent(_): part;
+					case FExpr(e): {kind: FExpr(f(e)), pos: part.pos};
+				}]);
 		}};
 		}};
 	}
 	}
 
 

+ 5 - 15
std/haxe/macro/MacroStringTools.hx

@@ -31,8 +31,6 @@ import haxe.macro.Expr;
 #if hl @:hlNative("macro") #end
 #if hl @:hlNative("macro") #end
 class MacroStringTools {
 class MacroStringTools {
 	#if macro
 	#if macro
-
-
 	/**
 	/**
 		Formats `String` `s` using the usual interpolation rules.
 		Formats `String` `s` using the usual interpolation rules.
 
 
@@ -44,6 +42,7 @@ class MacroStringTools {
 		return Context.load("format_string", 2)(s, pos);
 		return Context.load("format_string", 2)(s, pos);
 		#end
 		#end
 	}
 	}
+	#end
 
 
 	/**
 	/**
 		Tells if `e` is a format string, i.e. uses single quotes `'` as
 		Tells if `e` is a format string, i.e. uses single quotes `'` as
@@ -56,21 +55,12 @@ class MacroStringTools {
 		This operation depends on the position of `e`.
 		This operation depends on the position of `e`.
 	**/
 	**/
 	static public function isFormatExpr(e:ExprOf<String>) : Bool {
 	static public function isFormatExpr(e:ExprOf<String>) : Bool {
-		#if (neko || eval)
-		return Context.load("is_fmt_string", 1)(e.pos);
-		#else
-		return isFmtString(e.pos);
-		#end
+		return switch e.expr {
+			case EFormat(_): true;
+			case _: false;
+		}
 	}
 	}
 
 
-	#if !neko
-	static function isFmtString(p:Position) : Bool {
-		return false;
-	}
-	#end
-
-	#end
-
 	/**
 	/**
 		Converts an array of Strings `sl` to a field expression.
 		Converts an array of Strings `sl` to a field expression.
 
 

+ 5 - 0
std/haxe/macro/Printer.hx

@@ -229,6 +229,11 @@ class Printer {
 		case ETernary(econd, eif, eelse): '${printExpr(econd)} ? ${printExpr(eif)} : ${printExpr(eelse)}';
 		case ETernary(econd, eif, eelse): '${printExpr(econd)} ? ${printExpr(eif)} : ${printExpr(eelse)}';
 		case ECheckType(e1, ct): '(${printExpr(e1)} : ${printComplexType(ct)})';
 		case ECheckType(e1, ct): '(${printExpr(e1)} : ${printComplexType(ct)})';
 		case EMeta(meta, e1): printMetadata(meta) + " " +printExpr(e1);
 		case EMeta(meta, e1): printMetadata(meta) + " " +printExpr(e1);
+		case EFormat(parts): [for (part in parts) switch part.kind {
+			case FRaw(s): s;
+			case FIdent(s): '$s';
+			case FExpr(e): '$${${printExpr(e)}}';
+		}].join("");
 	}
 	}
 
 
 	public function printExprs(el:Array<Expr>, sep:String) {
 	public function printExprs(el:Array<Expr>, sep:String) {

+ 6 - 1
tests/unit/src/unit/issues/Issue2244.hx

@@ -4,8 +4,13 @@ import unit.Test;
 
 
 class Issue2244 extends Test{
 class Issue2244 extends Test{
 	function test() {
 	function test() {
+		var name = "John", age = 42;
+		eq('Hello ${name+'$age years'} old', "Hello John42 years old");
+
 		var x = "foo";
 		var x = "foo";
 		var y = '${ '${x}'}';
 		var y = '${ '${x}'}';
-		eq("foo", y);
+		eq(y, "foo");
+
+		eq('${'\''}', "'");
 	}
 	}
 }
 }

+ 19 - 0
tests/unit/src/unit/issues/Issue5803.hx

@@ -0,0 +1,19 @@
+package unit.issues;
+
+class Issue5803 extends Test {
+	function test() {
+		var expr = macro 'a: $a';
+		t(expr.expr.match(EFormat([
+			{kind: FRaw("a: ")},
+			{kind: FIdent("a")}
+		])));
+
+		var expr = macro 'a: ${a}';
+		t(expr.expr.match(EFormat([
+			{kind: FRaw("a: ")},
+			{kind: FExpr({
+				expr: EConst(CIdent("a"))
+			})}
+		])));
+	}
+}