فهرست منبع

rework format strings

Dan Korostelev 8 سال پیش
والد
کامیت
c00bbd7a90

+ 5 - 0
src/generators/gencpp.ml

@@ -416,9 +416,13 @@ let join_class_path_remap path separator =
    | 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 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
       | _ :: l -> loop l
       in
@@ -430,6 +434,7 @@ let get_meta_string meta key =
 let get_meta_string_path meta key =
    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->
            (try
            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
 		| IAnonStatus -> key_haxe_macro_AnonStatus
 		| IImportMode -> key_haxe_macro_ImportMode
+		| IFormatSegmentKind -> key_haxe_macro_FormatSegmentKind
 	in
 	encode_enum_value key index (Array.of_list pl) pos
 
@@ -128,10 +129,10 @@ let encode_bytes s =
 
 let encode_int_map_direct h =
 	encode_instance key_haxe_ds_IntMap ~kind:(IIntMap h)
-	
+
 let encode_string_map_direct h =
 	encode_instance key_haxe_ds_StringMap ~kind:(IStringMap h)
-	
+
 let encode_object_map_direct 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_AnonStatus = hash_s "haxe.macro.AnonStatus"
 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___init__ = hash_s "__init__"
 let key_new = hash_s "new"

+ 33 - 9
src/macro/macroApi.ml

@@ -75,6 +75,7 @@ type enum_type =
 	| IFieldAccess
 	| IAnonStatus
 	| IImportMode
+	| IFormatSegmentKind
 
 type obj_type =
 	(* make_const *)
@@ -93,6 +94,7 @@ type obj_type =
 	| OCase
 	| OCatch
 	| OExpr
+	| OFormatSegment
 	(* Type *)
 	| OMetaAccess
 	| OTypeParameter
@@ -209,6 +211,7 @@ let enum_name = function
 	| IFieldAccess -> "FieldAccess"
 	| IAnonStatus -> "AnonStatus"
 	| IImportMode -> "ImportMode"
+	| IFormatSegmentKind -> "FormatSegmentKind"
 
 let proto_name = function
 	| O__Const -> assert false
@@ -225,6 +228,7 @@ let proto_name = function
 	| OCase -> "Case", None
 	| OCatch -> "Catch", None
 	| OExpr -> "Expr", None
+	| OFormatSegment -> "FormatSegment", None
 	| OMetaAccess -> "MetaAccess", None
 	| OTypeParameter -> "TypeParameter", None
 	| OClassType -> "ClassType", None
@@ -256,7 +260,7 @@ let proto_name = function
 	| ORef -> "Ref", None
 
 let all_enums =
-	let last = IImportMode in
+	let last = IFormatSegmentKind in
 	let rec loop i =
 		let e : enum_type = Obj.magic i in
 		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) =
 	encode_string s
 
+let encode_and_map_array convert l =
+	encode_array (List.map convert l)
+
 let rec encode_path (t,_) =
 	let fields = [
 		"pack", encode_array (List.map encode_string t.tpackage);
@@ -543,6 +550,8 @@ and encode_expr e =
 				28, [loop e; encode_ctype t]
 			| EMeta (m,e) ->
 				29, [encode_meta_entry m;loop e]
+			| EFormat parts ->
+				30, [encode_and_map_array encode_format_part parts]
 		in
 		encode_obj OExpr [
 			"pos", encode_pos p;
@@ -551,6 +560,17 @@ and encode_expr e =
 	in
 	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 =
 	match e with
 	| None ->
@@ -802,8 +822,10 @@ and decode_expr v =
 			ECheckType (loop e, (decode_ctype t))
 		| 29, [m;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
 	in
@@ -812,6 +834,14 @@ and decode_expr v =
 	with Stack_overflow ->
 		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 *)
 
@@ -820,9 +850,6 @@ let encode_pmap_array convert m =
 	PMap.iter (fun _ v -> l := !l @ [(convert v)]) m;
 	encode_array !l
 
-let encode_and_map_array convert l =
-	encode_array (List.map convert l)
-
 let vopt f v = match v with
 	| None -> vnull
 	| 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
 			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 ->
 			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
 		);
 		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 ->
 			typing_timer ctx true (fun () ->
@@ -613,7 +618,6 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
 		| _ ->
 			el,[]
 	in
-	let todo = ref [] in
 	let args =
 		(*
 			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 p = snd e in
 			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
 			with Error (Custom _,_) ->
 				(* 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)
 		) el 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 ->
 			let e, et = (match e.eexpr with
 				(* get back our index and real expression *)

+ 52 - 1
src/syntax/ast.ml

@@ -105,6 +105,7 @@ type constant =
 type token =
 	| Eof
 	| Const of constant
+	| Fmt of (fmt_token * pos) list
 	| Kwd of keyword
 	| Comment of string
 	| CommentLine of string
@@ -127,6 +128,11 @@ type token =
 	| At
 	| Dollar of string
 
+and fmt_token =
+	| Raw of string
+	| Name of string
+	| Code of (token * pos) list
+
 type unop_flag =
 	| Prefix
 	| Postfix
@@ -169,6 +175,7 @@ and placed_name = string * pos
 
 and expr_def =
 	| EConst of constant
+	| EFormat of (format_part * pos) list
 	| EArray of expr * expr
 	| EBinop of binop * expr * expr
 	| EField of expr * string
@@ -201,6 +208,11 @@ and expr_def =
 
 and expr = expr_def * pos
 
+and format_part =
+	| FmtRaw of string
+	| FmtIdent of string
+	| FmtExpr of expr
+
 and type_param = {
 	tp_name : placed_name;
 	tp_params :	type_param list;
@@ -439,9 +451,18 @@ let s_unop = function
 	| Neg -> "-"
 	| NegBits -> "~"
 
-let s_token = function
+let rec s_token = function
 	| Eof -> "<end of file>"
 	| 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
 	| Comment s -> "/*"^s^"*/"
 	| CommentLine s -> "//"^s
@@ -574,6 +595,9 @@ let map_expr loop (e,p) =
 	in
 	let e = (match e with
 	| 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) ->
 		let e1 = loop e1 in
 		let e2 = loop e2 in
@@ -681,6 +705,8 @@ let iter_expr loop (e,p) =
 			opt e;
 		) cases;
 		(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) ->
 		List.iter (fun (_,_,_,_,eo) -> opt eo) f.f_args;
 		opt f.f_expr
@@ -690,6 +716,14 @@ let s_expr e =
 	let rec s_expr_inner tabs (e,_) =
 		match e with
 		| 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 ^ "]"
 		| 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
@@ -866,4 +900,21 @@ module Expr = struct
 			| [] -> raise Not_found
 		in
 		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

+ 85 - 66
src/syntax/lexer.ml

@@ -49,7 +49,6 @@ type lexer_file = {
 	mutable lmaxline : int;
 	mutable llines : (int * int) list;
 	mutable lalines : (int * int) array;
-	mutable lstrings : int list;
 	mutable llast : int;
 	mutable llastindex : int;
 }
@@ -61,7 +60,6 @@ let make_file file =
 		lmaxline = 1;
 		llines = [0,1];
 		lalines = [|0,1|];
-		lstrings = [];
 		llast = max_int;
 		llastindex = 0;
 	}
@@ -103,37 +101,6 @@ let newline lexbuf =
 	cur.lline <- cur.lline + 1;
 	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 =
 	(* rebuild cache if we have a new line *)
 	if f.lmaxline <> f.lline then begin
@@ -233,10 +200,19 @@ let mk_ident lexbuf =
 	let s = lexeme lexbuf in
 	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 =
 	error (Invalid_character (lexeme_char lexbuf 0)) (lexeme_start lexbuf)
 
-
 let ident = [%sedlex.regexp?
 	(
 		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 whitespace = [%sedlex.regexp? Plus (Chars " \t")]
+
 let rec skip_header lexbuf =
 	match%sedlex lexbuf with
 	| 0xfeff -> skip_header lexbuf
@@ -267,7 +245,7 @@ let rec skip_header lexbuf =
 let rec token lexbuf =
 	match%sedlex lexbuf with
 	| eof -> mk lexbuf Eof
-	| Plus (Chars " \t") -> token lexbuf
+	| whitespace -> token lexbuf
 	| "\r\n" -> newline lexbuf; token lexbuf
 	| '\n' | '\r' -> newline lexbuf; token lexbuf
 	| "0x", Plus ('0'..'9'|'a'..'f'|'A'..'F') -> mk lexbuf (Const (Int (lexeme lexbuf)))
@@ -347,11 +325,8 @@ let rec token lexbuf =
 	| "'" ->
 		reset();
 		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();
 		let pmin = lexeme_start lexbuf in
@@ -390,51 +365,95 @@ and string lexbuf =
 	| Plus (Compl ('"' | '\\' | '\r' | '\n')) -> store lexbuf; string lexbuf
 	| _ -> 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
 	| 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
-		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
 
-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
 	| 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 "\"";
 		let pmin = lexeme_start lexbuf in
 		(try ignore(string lexbuf) with Exit -> error Unterminated_string pmin);
 		add "\"";
-		code_string lexbuf open_braces
+		code_string start lexbuf open_braces
 	| "'" ->
-		add "'";
+		let parts = consume() 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
 		(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
 
 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]
 		| ECheckType (e1,ct) ->
 			expr "ECheckType" [loop e1; to_type_hint ct p]
+		| EFormat parts ->
+			expr "EFormat" [to_array to_formatsegment parts p]
 		| EMeta ((m,ml,p),e1) ->
 			match m, ml with
 			| Meta.Dollar ("" | "e"), _ ->
@@ -495,6 +497,19 @@ let reify in_macro =
 				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]
+	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 =
 		to_obj [
 			"name", to_placed_name t.tp_name;
@@ -1355,6 +1370,9 @@ and expr = parser
 		parse_macro_expr p s
 	| [< '(Kwd Var,p1); v = parse_var_decl >] -> (EVars [v],p1)
 	| [< '(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 True,p); s >] -> expr_next (EConst (Ident "true"),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))
 	| [< '(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
 	| [< '(BrOpen,p1) when is_dollar_ident e1; eparam = expr; '(BrClose,p2); s >] ->
 		(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_optimize : typer -> texpr -> texpr;
 	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_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
 		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 =
 	let merge e = match e.eexpr with
 		| 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 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 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 ->
 		Codegen.type_constant ctx.com c p
 	| EBinop (op,e1,e2) ->
@@ -4449,7 +4360,6 @@ let rec create com =
 			do_load_module = Typeload.load_module;
 			do_optimize = Optimizer.reduce_expression;
 			do_build_instance = Typeload.build_instance;
-			do_format_string = format_string;
 			do_finalize = finalize;
 			do_generate = generate;
 		};

+ 1 - 1
std/cpp/NativeFile.hx

@@ -1,6 +1,6 @@
 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 @:native("_hx_std_file_open")

+ 1 - 1
std/cpp/NativeProcess.hx

@@ -1,6 +1,6 @@
 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
 {
 

+ 1 - 1
std/cpp/NativeRandom.hx

@@ -1,6 +1,6 @@
 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
 {
 

+ 1 - 1
std/cpp/NativeSocket.hx

@@ -2,7 +2,7 @@ package cpp;
 
 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 @:native("_hx_std_socket_init")

+ 1 - 1
std/cpp/NativeSsl.hx

@@ -1,6 +1,6 @@
 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 @:native("_hx_ssl_new")

+ 1 - 1
std/cpp/NativeSys.hx

@@ -1,6 +1,6 @@
 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
 {
    @:native("__hxcpp_print")

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

@@ -20,7 +20,7 @@
  * 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 {
 
 	var r : Dynamic;

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

@@ -21,7 +21,7 @@
  */
 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 {
 
 	var s : Dynamic;

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

@@ -21,7 +21,7 @@
  */
 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 {
 	var s : Dynamic;
 

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

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

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

@@ -131,7 +131,7 @@ private class MysqlConnection implements sys.db.Connection {
 		__c = c;
 	 D.set_conv_funs( cpp.Function.fromStaticFunction(D.charsToBytes),
                      cpp.Function.fromStaticFunction(D.secondsToDate) );
-    
+
 	}
 
 	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;
 }
 
-@:buildXml('<include name="${HXCPP}/src/hx/libs/mysql/Build.xml"/>')
+@:buildXml("<include name=\"${HXCPP}/src/hx/libs/mysql/Build.xml\"/>")
 @:coreApi class Mysql {
 
 	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 {
 
 	public static function open( file : String ) : Connection {

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

@@ -495,6 +495,48 @@ enum ExprDef {
 		A `@m e` expression.
 	**/
 	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)
 					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) } );
 				EFunction(name, { args: ret, ret: func.ret, params: func.params, expr: f(func.expr) } );
 			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
 class MacroStringTools {
 	#if macro
-
-
 	/**
 		Formats `String` `s` using the usual interpolation rules.
 
@@ -44,6 +42,7 @@ class MacroStringTools {
 		return Context.load("format_string", 2)(s, pos);
 		#end
 	}
+	#end
 
 	/**
 		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`.
 	**/
 	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.
 

+ 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 ECheckType(e1, ct): '(${printExpr(e1)} : ${printComplexType(ct)})';
 		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) {

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

@@ -4,8 +4,13 @@ import unit.Test;
 
 class Issue2244 extends Test{
 	function test() {
+		var name = "John", age = 42;
+		eq('Hello ${name+'$age years'} old', "Hello John42 years old");
+
 		var x = "foo";
 		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"))
+			})}
+		])));
+	}
+}