Browse Source

[wip] Improve parser error handling (#7912)

* [parser] start on parser error refactoring

* [parser] remove all `in_display` checks from grammar for now

* [parser] remove Parser.display_error

* [parser] start on parse_result

* [parser] remove some hard errors

* [tests] add test for broken syntax diagnostics

see #7793

* [parser] unbreak eval-debugger completion

* [parser] adjust Context.parse(InlineString)

* [parser] adjust `parse_metadata`

* [parser] adjust type_patch parsing

* [parser] adjust import.hx parsing

* [parser] adjust module parsing

* [parser] fix special } resume case

* [parser] handle module parsing errors properly

* [macro] fix bad macro display settings handling

* [tests] add toString to HaxeInvocationException

and hope that it gets called

* [submodule] try something

* [parser] fix syntax_errors handling

* [server] remove unused binding

* [parser] don't lose exact interpolation parser message

* [tests] remove unrelated change
Simon Krajewski 6 years ago
parent
commit
ef34514304

+ 1 - 1
libs

@@ -1 +1 @@
-Subproject commit 101516896900a046e19e32737b9a09a870da2f94
+Subproject commit d004775e9c6b80b5d85ac182d549d050397857f5

+ 0 - 1
src/compiler/main.ml

@@ -483,7 +483,6 @@ try
 	com.warning <- (fun msg p -> message ctx (CMWarning(msg,p)));
 	com.warning <- (fun msg p -> message ctx (CMWarning(msg,p)));
 	com.error <- error ctx;
 	com.error <- error ctx;
 	if CompilationServer.runs() then com.run_command <- run_command ctx;
 	if CompilationServer.runs() then com.run_command <- run_command ctx;
-	Parser.display_error := (fun e p -> com.error (Parser.error_msg e) p);
 	com.class_path <- get_std_class_paths ();
 	com.class_path <- get_std_class_paths ();
 	com.std_path <- List.filter (fun p -> ExtString.String.ends_with p "std/" || ExtString.String.ends_with p "std\\") com.class_path;
 	com.std_path <- List.filter (fun p -> ExtString.String.ends_with p "std/" || ExtString.String.ends_with p "std\\") com.class_path;
 	let define f = Arg.Unit (fun () -> Common.define com f) in
 	let define f = Arg.Unit (fun () -> Common.define com f) in

+ 18 - 18
src/compiler/server.ml

@@ -109,7 +109,6 @@ let ssend sock str =
 let rec wait_loop process_params verbose accept =
 let rec wait_loop process_params verbose accept =
 	if verbose then ServerMessage.enable_all ();
 	if verbose then ServerMessage.enable_all ();
 	Sys.catch_break false; (* Sys can never catch a break *)
 	Sys.catch_break false; (* Sys can never catch a break *)
-	let has_parse_error = ref false in
 	let cs = CompilationServer.create () in
 	let cs = CompilationServer.create () in
 	MacroContext.macro_enable_cache := true;
 	MacroContext.macro_enable_cache := true;
 	let current_stdin = ref None in
 	let current_stdin = ref None in
@@ -128,24 +127,26 @@ let rec wait_loop process_params verbose accept =
 				try
 				try
 					let cfile = CompilationServer.find_file cs fkey in
 					let cfile = CompilationServer.find_file cs fkey in
 					if cfile.c_time <> ftime then raise Not_found;
 					if cfile.c_time <> ftime then raise Not_found;
-					cfile.c_package,cfile.c_decls
+					Parser.ParseSuccess(cfile.c_package,cfile.c_decls)
 				with Not_found ->
 				with Not_found ->
-					has_parse_error := false;
-					let data = TypeloadParse.parse_file com2 file p in
-					let info,is_unusual = if !has_parse_error then "not cached, has parse error",true
-						else if is_display_file then "not cached, is display file",true
-						else begin try
-							(* We assume that when not in display mode it's okay to cache stuff that has #if display
-							   checks. The reasoning is that non-display mode has more information than display mode. *)
-							if not com2.display.dms_display then raise Not_found;
-							let ident = Hashtbl.find Parser.special_identifier_files ffile in
-							Printf.sprintf "not cached, using \"%s\" define" ident,true
-						with Not_found ->
-							CompilationServer.cache_file cs fkey ftime data;
-							"cached",false
-					end in
+					let parse_result = TypeloadParse.parse_file com2 file p in
+					let info,is_unusual = match parse_result with
+						| ParseError(_,_,_) -> "not cached, has parse error",true
+						| ParseDisplayFile _ -> "not cached, is display file",true
+						| ParseSuccess data ->
+							begin try
+								(* We assume that when not in display mode it's okay to cache stuff that has #if display
+								checks. The reasoning is that non-display mode has more information than display mode. *)
+								if not com2.display.dms_display then raise Not_found;
+								let ident = Hashtbl.find Parser.special_identifier_files ffile in
+								Printf.sprintf "not cached, using \"%s\" define" ident,true
+							with Not_found ->
+								CompilationServer.cache_file cs fkey ftime data;
+								"cached",false
+							end
+					in
 					if is_unusual then ServerMessage.parsed com2 "" (ffile,info);
 					if is_unusual then ServerMessage.parsed com2 "" (ffile,info);
-					data
+					parse_result
 			) () in
 			) () in
 			data
 			data
 	);
 	);
@@ -416,7 +417,6 @@ let rec wait_loop process_params verbose accept =
 				ServerMessage.defines ctx.com "";
 				ServerMessage.defines ctx.com "";
 				ServerMessage.signature ctx.com "" sign;
 				ServerMessage.signature ctx.com "" sign;
 				ServerMessage.display_position ctx.com "" (!DisplayPosition.display_position);
 				ServerMessage.display_position ctx.com "" (!DisplayPosition.display_position);
-				Parser.display_error := (fun e p -> has_parse_error := true; ctx.com.error (Parser.error_msg e) p);
 				(* Special case for diagnostics: It's not treated as a display mode, but we still want to invalidate the
 				(* Special case for diagnostics: It's not treated as a display mode, but we still want to invalidate the
 				   current file in order to run diagnostics on it again. *)
 				   current file in order to run diagnostics on it again. *)
 				if ctx.com.display.dms_display || (match ctx.com.display.dms_kind with DMDiagnostics _ -> true | _ -> false) then begin
 				if ctx.com.display.dms_display || (match ctx.com.display.dms_kind with DMDiagnostics _ -> true | _ -> false) then begin

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

@@ -80,7 +80,9 @@ exception Parse_expr_error of string
 
 
 let parse_expr ctx s p =
 let parse_expr ctx s p =
 	let error s = raise (Parse_expr_error s) in
 	let error s = raise (Parse_expr_error s) in
-	ParserEntry.parse_expr_string (ctx.curapi.get_com()).Common.defines s p error true
+	match ParserEntry.parse_expr_string (ctx.curapi.get_com()).Common.defines s p error true with
+	| ParseSuccess data | ParseDisplayFile(data,_) -> data
+	| ParseError(_,(msg,_),_) -> error (Parser.error_msg msg)
 
 
 (* Vars *)
 (* Vars *)
 
 

+ 77 - 106
src/syntax/grammar.mly

@@ -45,9 +45,12 @@ let pignore f =
 	with Stream.Error _ | Stream.Failure ->
 	with Stream.Error _ | Stream.Failure ->
 		()
 		()
 
 
-let expect_unless_resume_p f = parser
-	| [< p = f >] -> p
-	| [< s >] -> if !in_display then pos (next_token s) else serror()
+let expect_unless_resume_p t s = match Stream.peek s with
+	| Some (t',p) when t' = t ->
+		Stream.junk s;
+		p
+	| _ ->
+		syntax_error (Expected [s_token t]) s (next_pos s)
 
 
 let ident = parser
 let ident = parser
 	| [< '(Const (Ident i),p) >] -> i,p
 	| [< '(Const (Ident i),p) >] -> i,p
@@ -83,24 +86,9 @@ let questionable_dollar_ident s =
 		| None ->
 		| None ->
 			false,(name,p)
 			false,(name,p)
 		| Some p' ->
 		| Some p' ->
-			if p.pmin <> p'.pmax then error (Custom (Printf.sprintf "Invalid usage of ?, use ?%s instead" name)) p';
+			if p.pmin <> p'.pmax then syntax_error (Custom (Printf.sprintf "Invalid usage of ?, use ?%s instead" name)) s ~pos:(Some p') ();
 			true,(name,p)
 			true,(name,p)
 
 
-let bropen = parser
-	| [< '(BrOpen,_) >] -> ()
-
-let comma = parser
-	| [< '(Comma,_) >] -> ()
-
-let colon = parser
-	| [< '(DblDot,p) >] -> p
-
-let pclose = parser
-	| [< '(PClose,p) >] -> p
-
-let bkclose = parser
-	| [< '(BkClose,p) >] -> p
-
 let question_mark = parser
 let question_mark = parser
 	| [< '(Question,p) >] -> p
 	| [< '(Question,p) >] -> p
 
 
@@ -113,8 +101,7 @@ let semicolon s =
 		match s with parser
 		match s with parser
 		| [< '(Semicolon,p) >] -> p
 		| [< '(Semicolon,p) >] -> p
 		| [< s >] ->
 		| [< s >] ->
-			let pos = snd (last_token s) in
-			if !in_display then pos else error Missing_semicolon pos
+			syntax_error Missing_semicolon s (next_pos s)
 
 
 let parsing_macro_cond = ref false
 let parsing_macro_cond = ref false
 
 
@@ -151,15 +138,17 @@ and parse_type_decls mode pmax pack acc s =
 			| _ -> ()
 			| _ -> ()
 		) acc;
 		) acc;
 		raise (TypePath (pack,Some(name,true),b,p))
 		raise (TypePath (pack,Some(name,true),b,p))
-	| Stream.Error _ when !in_display ->
-		ignore(resume false false s);
-		parse_type_decls mode (last_pos s).pmax pack acc s
+	| Stream.Error msg when !in_display_file ->
+		syntax_error (StreamError msg) s (
+			ignore(resume false false s);
+			parse_type_decls mode (last_pos s).pmax pack acc s
+		)
 
 
 and parse_abstract doc meta flags = parser
 and parse_abstract doc meta flags = parser
 	| [< '(Kwd Abstract,p1); name = type_name; tl = parse_constraint_params; st = parse_abstract_subtype; sl = plist parse_abstract_relations; s >] ->
 	| [< '(Kwd Abstract,p1); name = type_name; tl = parse_constraint_params; st = parse_abstract_subtype; sl = plist parse_abstract_relations; s >] ->
 		let fl,p2 = match s with parser
 		let fl,p2 = match s with parser
 			| [< '(BrOpen,_); fl, p2 = parse_class_fields false p1 >] -> fl,p2
 			| [< '(BrOpen,_); fl, p2 = parse_class_fields false p1 >] -> fl,p2
-			| [< >] -> if !in_display then [],last_pos s else serror()
+			| [< >] -> syntax_error (Expected ["{"]) s ([],last_pos s)
 		in
 		in
 		let flags = List.map decl_flag_to_abstract_flag flags in
 		let flags = List.map decl_flag_to_abstract_flag flags in
 		let flags = (match st with None -> flags | Some t -> AbOver t :: flags) in
 		let flags = (match st with None -> flags | Some t -> AbOver t :: flags) in
@@ -208,10 +197,8 @@ and parse_type_decl mode s =
 					check_display p0 {p1 with pmin = p0.pmax; pmax = p1.pmin};
 					check_display p0 {p1 with pmin = p0.pmax; pmax = p1.pmin};
 					List.rev acc
 					List.rev acc
 				| [< >] ->
 				| [< >] ->
-					if not (!in_display) then serror() else begin
-						check_display p0 {p1 with pmin = p0.pmax; pmax = (next_pos s).pmax};
-						List.rev acc
-					end
+					check_display p0 {p1 with pmin = p0.pmax; pmax = (next_pos s).pmax};
+					syntax_error (Expected ["extends";"implements";"{"]) s (List.rev acc)
 			in
 			in
 			let hl = loop false (last_pos s) [] in
 			let hl = loop false (last_pos s) [] in
 			let fl, p2 = parse_class_fields false p1 s in
 			let fl, p2 = parse_class_fields false p1 s in
@@ -272,10 +259,8 @@ and parse_import s p1 =
 			| [< '(Binop OpMult,_); '(Semicolon,p2) >] ->
 			| [< '(Binop OpMult,_); '(Semicolon,p2) >] ->
 				p2, List.rev acc, IAll
 				p2, List.rev acc, IAll
 			| [< >] ->
 			| [< >] ->
-				if !in_display then begin
-					ignore(popt semicolon s);
-					p,List.rev acc,INormal
-				end else serror()
+				ignore(popt semicolon s);
+				syntax_error (Expected ["identifier"]) s (p,List.rev acc,INormal)
 			end
 			end
 		| [< '(Semicolon,p2) >] ->
 		| [< '(Semicolon,p2) >] ->
 			p2, List.rev acc, INormal
 			p2, List.rev acc, INormal
@@ -284,11 +269,11 @@ and parse_import s p1 =
 		| [< '(Const (Ident "as"),_); '(Const (Ident name),pname); '(Semicolon,p2) >] ->
 		| [< '(Const (Ident "as"),_); '(Const (Ident name),pname); '(Semicolon,p2) >] ->
 			p2, List.rev acc, IAsName(name,pname)
 			p2, List.rev acc, IAsName(name,pname)
 		| [< >] ->
 		| [< >] ->
-			if !in_display then (last_pos s),List.rev acc,INormal else serror()
+			syntax_error (Expected [".";";";"as"]) s ((last_pos s),List.rev acc,INormal)
 	in
 	in
 	let p2, path, mode = (match s with parser
 	let p2, path, mode = (match s with parser
 		| [< '(Const (Ident name),p) >] -> loop p [name,p]
 		| [< '(Const (Ident name),p) >] -> loop p [name,p]
-		| [< >] -> if would_skip_display_position p1 s then p1, [], INormal else serror()
+		| [< >] -> syntax_error (Expected ["identifier"]) s (p1,[],INormal)
 	) in
 	) in
 	(EImport (path,mode),punion p1 p2)
 	(EImport (path,mode),punion p1 p2)
 
 
@@ -305,16 +290,16 @@ and parse_using s p1 =
 			| [< '(Kwd Extern,p) >] ->
 			| [< '(Kwd Extern,p) >] ->
 				loop pn (("extern",p) :: acc)
 				loop pn (("extern",p) :: acc)
 			| [< >] ->
 			| [< >] ->
-				if !in_display then p,List.rev acc else serror()
+				syntax_error (Expected ["identifier"]) s (p,List.rev acc);
 			end
 			end
 		| [< '(Semicolon,p2) >] ->
 		| [< '(Semicolon,p2) >] ->
 			p2,List.rev acc
 			p2,List.rev acc
 		| [< >] ->
 		| [< >] ->
-			if !in_display then (last_pos s),List.rev acc else serror()
+			syntax_error (Expected [".";";"]) s ((last_pos s),List.rev acc)
 	in
 	in
 	let p2, path = (match s with parser
 	let p2, path = (match s with parser
 		| [< '(Const (Ident name),p) >] -> loop p [name,p]
 		| [< '(Const (Ident name),p) >] -> loop p [name,p]
-		| [< >] -> if would_skip_display_position p1 s then p1, [] else serror()
+		| [< >] -> syntax_error (Expected ["identifier"]) s (p1,[])
 	) in
 	) in
 	(EUsing path,punion p1 p2)
 	(EUsing path,punion p1 p2)
 
 
@@ -342,7 +327,9 @@ and parse_class_fields tdecl p1 s =
 	let l = parse_class_field_resume tdecl s in
 	let l = parse_class_field_resume tdecl s in
 	let p2 = (match s with parser
 	let p2 = (match s with parser
 		| [< '(BrClose,p2) >] -> p2
 		| [< '(BrClose,p2) >] -> p2
-		| [< >] -> if !in_display then pos (last_token s) else serror()
+		| [< >] ->
+			(* We don't want to register this as a syntax error because it's part of the logic in display mode *)
+			if !in_display then (pos (last_token s)) else error (Expected ["}"]) (next_pos s)
 	) in
 	) in
 	l, p2
 	l, p2
 
 
@@ -409,7 +396,7 @@ and resume tdecl fdecl s =
 	loop 1
 	loop 1
 
 
 and parse_class_field_resume tdecl s =
 and parse_class_field_resume tdecl s =
-	if not (!in_display) then
+	if not (!in_display_file) then
 		plist (parse_class_field tdecl) s
 		plist (parse_class_field tdecl) s
 	else try
 	else try
 		let c = parse_class_field tdecl s in
 		let c = parse_class_field tdecl s in
@@ -438,7 +425,7 @@ and parse_meta_argument_expr s =
 
 
 and parse_meta_params pname s = match s with parser
 and parse_meta_params pname s = match s with parser
 	| [< '(POpen,p) when p.pmin = pname.pmax; params = psep Comma parse_meta_argument_expr; >] ->
 	| [< '(POpen,p) when p.pmin = pname.pmax; params = psep Comma parse_meta_argument_expr; >] ->
-		ignore(expect_unless_resume_p pclose s);
+		ignore(expect_unless_resume_p PClose s);
 		params
 		params
 	| [< >] -> []
 	| [< >] -> []
 
 
@@ -525,7 +512,7 @@ and parse_structural_extension = parser
 		| [< t = parse_type_path >] ->
 		| [< t = parse_type_path >] ->
 			begin match s with parser
 			begin match s with parser
 				| [< '(Comma,_) >] -> t
 				| [< '(Comma,_) >] -> t
-				| [< >] -> if !in_display then t else serror()
+				| [< >] -> syntax_error (Expected [","]) s t
 			end;
 			end;
 		| [< >] ->
 		| [< >] ->
 			if would_skip_display_position p1 s then begin
 			if would_skip_display_position p1 s then begin
@@ -594,8 +581,7 @@ and parse_type_path2 p0 pack name p1 s =
 				begin match s with parser
 				begin match s with parser
 				| [<'(Binop OpGt,p2) >] -> l,p2
 				| [<'(Binop OpGt,p2) >] -> l,p2
 				| [< >] ->
 				| [< >] ->
-					if !in_display then l,pos (last_token s)
-					else serror()
+					syntax_error (Expected [">"]) s (l,pos (last_token s))
 				end
 				end
 			| [< >] -> [],p2
 			| [< >] -> [],p2
 		) in
 		) in
@@ -607,9 +593,9 @@ and parse_type_path2 p0 pack name p1 s =
 		},punion (match p0 with None -> p1 | Some p -> p) p2
 		},punion (match p0 with None -> p1 | Some p -> p) p2
 
 
 and type_name = parser
 and type_name = parser
-	| [< '(Const (Ident name),p) >] ->
+	| [< '(Const (Ident name),p); s >] ->
 		if is_lower_ident name then
 		if is_lower_ident name then
-			error (Custom "Type name should start with an uppercase letter") p
+			syntax_error (Custom "Type name should start with an uppercase letter") ~pos:(Some p) s (name,p)
 		else
 		else
 			name,p
 			name,p
 	| [< '(Dollar name,p) >] -> "$" ^ name,p
 	| [< '(Dollar name,p) >] -> "$" ^ name,p
@@ -622,7 +608,7 @@ and parse_type_path_or_const = parser
 	| [< '(Kwd True,p) >] -> TPExpr (EConst (Ident "true"),p)
 	| [< '(Kwd True,p) >] -> TPExpr (EConst (Ident "true"),p)
 	| [< '(Kwd False,p) >] -> TPExpr (EConst (Ident "false"),p)
 	| [< '(Kwd False,p) >] -> TPExpr (EConst (Ident "false"),p)
 	| [< e = expr >] -> TPExpr e
 	| [< e = expr >] -> TPExpr e
-	| [< >] -> if !in_display then raise Stream.Failure else serror()
+	| [< >] -> if !in_display_file then raise Stream.Failure else serror()
 
 
 and parse_complex_type_next (t : type_hint) s =
 and parse_complex_type_next (t : type_hint) s =
 	let make_fun t2 p2 = match t2 with
 	let make_fun t2 p2 = match t2 with
@@ -696,8 +682,7 @@ and parse_type_anonymous s =
 			| [< l,p2 = parse_type_anonymous >] -> next l,punion p1 p2
 			| [< l,p2 = parse_type_anonymous >] -> next l,punion p1 p2
 			| [< >] -> serror());
 			| [< >] -> serror());
 		| [< >] ->
 		| [< >] ->
-			if !in_display then next [],p2
-			else serror()
+			syntax_error (Expected [",";"}"]) s (next [],p2)
 		end
 		end
 	| [< >] ->
 	| [< >] ->
 		if p0 = None then raise Stream.Failure else serror()
 		if p0 = None then raise Stream.Failure else serror()
@@ -734,7 +719,7 @@ and parse_function_field doc meta al = parser
 	| [< '(Kwd Function,p1); name = parse_fun_name; pl = parse_constraint_params; '(POpen,_); args = psep Comma parse_fun_param; '(PClose,_); t = popt parse_type_hint; s >] ->
 	| [< '(Kwd Function,p1); name = parse_fun_name; pl = parse_constraint_params; '(POpen,_); args = psep Comma parse_fun_param; '(PClose,_); t = popt parse_type_hint; s >] ->
 		let e, p2 = (match s with parser
 		let e, p2 = (match s with parser
 			| [< e = expr; s >] ->
 			| [< e = expr; s >] ->
-				(try ignore(semicolon s) with Error (Missing_semicolon,p) -> !display_error Missing_semicolon p);
+				ignore(semicolon s);
 				Some e, pos e
 				Some e, pos e
 			| [< p = semicolon >] -> None, p
 			| [< p = semicolon >] -> None, p
 			| [< >] -> serror()
 			| [< >] -> serror()
@@ -770,7 +755,7 @@ and parse_class_field tdecl s =
 	| [< meta = parse_meta; al = plist parse_cf_rights; s >] ->
 	| [< meta = parse_meta; al = plist parse_cf_rights; s >] ->
 		let check_optional opt name =
 		let check_optional opt name =
 			if opt then begin
 			if opt then begin
-				if not tdecl then error (Custom "?var syntax is only allowed in structures") (pos name);
+				if not tdecl then syntax_error (Custom "?var syntax is only allowed in structures") ~pos:(Some (pos name)) s ();
 				(Meta.Optional,[],null_pos) :: meta
 				(Meta.Optional,[],null_pos) :: meta
 			end else
 			end else
 				meta
 				meta
@@ -935,12 +920,9 @@ and block_with_pos' acc f p s =
 	with
 	with
 		| Stream.Failure ->
 		| Stream.Failure ->
 			List.rev acc,p
 			List.rev acc,p
-		| Stream.Error _ when !in_display ->
-			let tk , pos = next_token s in
-			(!display_error) (Unexpected tk) pos;
-			block_with_pos acc pos s
-		| Error (e,p) ->
-			(!display_error) e p;
+		| Stream.Error msg when !in_display_file ->
+			syntax_error (StreamError msg) s (block_with_pos acc (next_pos s) s)
+		| Error (e,p) when !in_display_file ->
 			block_with_pos acc p s
 			block_with_pos acc p s
 
 
 and block_with_pos acc p s =
 and block_with_pos acc p s =
@@ -981,8 +963,7 @@ and parse_obj_decl name e p0 s =
 				| [< '(DblDot,_) >] ->
 				| [< '(DblDot,_) >] ->
 					next_expr key
 					next_expr key
 				| [< >] ->
 				| [< >] ->
-					if !in_display then next_expr key
-					else serror()
+					syntax_error (Expected [":"]) s (next_expr key)
 			in
 			in
 			begin match s with parser
 			begin match s with parser
 				| [< name,p = ident >] -> next (name,p,NoQuotes)
 				| [< name,p = ident >] -> next (name,p,NoQuotes)
@@ -997,10 +978,10 @@ and parse_obj_decl name e p0 s =
 
 
 and parse_array_decl p1 s =
 and parse_array_decl p1 s =
 	let resume_or_fail p1 =
 	let resume_or_fail p1 =
-		if !in_display then begin
+		syntax_error (Expected ["expr";"]"]) s (
 			let p = punion_next p1 s in
 			let p = punion_next p1 s in
 			[mk_null_expr p],p
 			[mk_null_expr p],p
-		end else serror()
+		)
 	in
 	in
 	let el,p2 = match s with parser
 	let el,p2 = match s with parser
 		| [< '(BkClose,p2) >] -> [],p2
 		| [< '(BkClose,p2) >] -> [],p2
@@ -1011,11 +992,11 @@ and parse_array_decl p1 s =
 						| [< '(BkClose,p2) >] -> acc,p2
 						| [< '(BkClose,p2) >] -> acc,p2
 						| [< e = secure_expr >] -> loop (e :: acc)
 						| [< e = secure_expr >] -> loop (e :: acc)
 						| [< >] ->
 						| [< >] ->
-							if !in_display then acc,pk else serror()
+							syntax_error (Expected ["expr";"]"]) s (acc,pk)
 					end
 					end
 				| [< '(BkClose,p2) >] -> acc,p2
 				| [< '(BkClose,p2) >] -> acc,p2
 				| [< >] ->
 				| [< >] ->
-					if !in_display then acc,next_pos s else serror()
+					syntax_error (Expected [",";"]"]) s (acc,next_pos s)
 			in
 			in
 			loop [e0]
 			loop [e0]
 		| [< >] -> resume_or_fail p1
 		| [< >] -> resume_or_fail p1
@@ -1027,7 +1008,7 @@ and parse_var_decl_head final = parser
 
 
 and parse_var_assignment = parser
 and parse_var_assignment = parser
 	| [< '(Binop OpAssign,p1); s >] ->
 	| [< '(Binop OpAssign,p1); s >] ->
-		Some (expr_or_fail (fun () -> error (Custom "expression expected after =") p1) s)
+		Some (secure_expr s)
 	| [< >] -> None
 	| [< >] -> None
 
 
 and parse_var_assignment_resume final vl name pn t s =
 and parse_var_assignment_resume final vl name pn t s =
@@ -1099,10 +1080,10 @@ and arrow_ident_checktype e = (match e with
 	| ECheckType((EConst(Ident n),p),(t,pt)),_ -> (n,p),(Some (t,pt))
 	| ECheckType((EConst(Ident n),p),(t,pt)),_ -> (n,p),(Some (t,pt))
 	| _ -> serror())
 	| _ -> serror())
 
 
-and arrow_first_param e =
+and arrow_first_param e s =
 	(match fst e with
 	(match fst e with
 	| EConst(Ident ("true" | "false" | "null" | "this" | "super")) ->
 	| EConst(Ident ("true" | "false" | "null" | "this" | "super")) ->
-		error (Custom "Invalid argument name") (pos e)
+		syntax_error (Custom "Invalid argument name") ~pos:(Some (pos e)) s (("",null_pos),false,[],None,None)
 	| EConst(Ident n) ->
 	| EConst(Ident n) ->
 		(n,snd e),false,[],None,None
 		(n,snd e),false,[],None,None
 	| EBinop(OpAssign,e1,e2)
 	| EBinop(OpAssign,e1,e2)
@@ -1131,7 +1112,7 @@ and expr = parser
 				| [< '(BrClose,p2) >] -> p2
 				| [< '(BrClose,p2) >] -> p2
 				| [< >] ->
 				| [< >] ->
 					(* Ignore missing } if we are resuming and "guess" the last position. *)
 					(* Ignore missing } if we are resuming and "guess" the last position. *)
-					if !in_display then pos (next_token s) else serror()
+					syntax_error (Expected ["}"]) s (pos (next_token s))
 			in
 			in
 			let e = (b,punion p1 p2) in
 			let e = (b,punion p1 p2) in
 			(match b with
 			(match b with
@@ -1176,8 +1157,7 @@ and expr = parser
 		begin match s with parser
 		begin match s with parser
 		| [< '(POpen,po); e = parse_call_params (fun el p2 -> (ENew(t,el)),punion p1 p2) po >] -> expr_next e s
 		| [< '(POpen,po); e = parse_call_params (fun el p2 -> (ENew(t,el)),punion p1 p2) po >] -> expr_next e s
 		| [< >] ->
 		| [< >] ->
-			if !in_display then (ENew(t,[]),punion p1 (pos t))
-			else serror()
+			syntax_error (Expected ["("]) s (ENew(t,[]),punion p1 (pos t))
 		end
 		end
 	| [< '(POpen,p1); s >] -> (match s with parser
 	| [< '(POpen,p1); s >] -> (match s with parser
 		| [< '(PClose,p2); er = arrow_expr; >] ->
 		| [< '(PClose,p2); er = arrow_expr; >] ->
@@ -1188,7 +1168,7 @@ and expr = parser
 		| [<  e = expr; s >] -> (match s with parser
 		| [<  e = expr; s >] -> (match s with parser
 			| [< '(PClose,p2); s >] -> expr_next (EParenthesis e, punion p1 p2) s
 			| [< '(PClose,p2); s >] -> expr_next (EParenthesis e, punion p1 p2) s
 			| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
 			| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
-				arrow_function p1 ((arrow_first_param e) :: al) er
+				arrow_function p1 ((arrow_first_param e s) :: al) er
 			| [< t,pt = parse_type_hint; s >] -> (match s with parser
 			| [< t,pt = parse_type_hint; s >] -> (match s with parser
 				| [< '(PClose,p2); s >] -> expr_next (EParenthesis (ECheckType(e,(t,pt)),punion p1 p2), punion p1 p2) s
 				| [< '(PClose,p2); s >] -> expr_next (EParenthesis (ECheckType(e,(t,pt)),punion p1 p2), punion p1 p2) s
 				| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
 				| [< '(Comma,pc); al = psep Comma parse_fun_param; '(PClose,_); er = arrow_expr; >] ->
@@ -1209,8 +1189,7 @@ and expr = parser
 				| [< >] -> serror())
 				| [< >] -> serror())
 			| [< '(Const (Ident "is"),p_is); t = parse_type_path; '(PClose,p2); >] -> expr_next (make_is e t (punion p1 p2) p_is) s
 			| [< '(Const (Ident "is"),p_is); t = parse_type_path; '(PClose,p2); >] -> expr_next (make_is e t (punion p1 p2) p_is) s
 			| [< >] ->
 			| [< >] ->
-				if !in_display then expr_next (EParenthesis e, punion p1 (pos e)) s
-				else serror())
+				syntax_error (Expected [")";",";":";"is"]) s (expr_next (EParenthesis e, punion p1 (pos e)) s))
 		)
 		)
 	| [< '(BkOpen,p1); e = parse_array_decl p1; s >] -> expr_next e s
 	| [< '(BkOpen,p1); e = parse_array_decl p1; s >] -> expr_next e s
 	| [< '(Kwd Function,p1); e = parse_function p1 false; >] -> e
 	| [< '(Kwd Function,p1); e = parse_function p1 false; >] -> e
@@ -1228,16 +1207,14 @@ and expr = parser
 		let e = match s with parser
 		let e = match s with parser
 			| [< '(PClose,_); e = secure_expr >] -> e
 			| [< '(PClose,_); e = secure_expr >] -> e
 			| [< >] ->
 			| [< >] ->
-				if !in_display then mk_null_expr (pos it)
-				else serror()
+				syntax_error (Expected [")"]) s (mk_null_expr (pos it))
 		in
 		in
 		(EFor (it,e),punion p (pos e))
 		(EFor (it,e),punion p (pos e))
 	| [< '(Kwd If,p); '(POpen,_); cond = secure_expr; s >] ->
 	| [< '(Kwd If,p); '(POpen,_); cond = secure_expr; s >] ->
 		let e1 = match s with parser
 		let e1 = match s with parser
 			| [< '(PClose,_); e1 = secure_expr >] -> e1
 			| [< '(PClose,_); e1 = secure_expr >] -> e1
 			| [< >] ->
 			| [< >] ->
-				if !in_display then mk_null_expr (pos cond)
-				else serror()
+				syntax_error (Expected [")"]) s (mk_null_expr (pos cond))
 		in
 		in
 		let e2 = (match s with parser
 		let e2 = (match s with parser
 			| [< '(Kwd Else,_); e2 = secure_expr; s >] -> Some e2
 			| [< '(Kwd Else,_); e2 = secure_expr; s >] -> Some e2
@@ -1264,25 +1241,22 @@ and expr = parser
 		let e = match s with parser
 		let e = match s with parser
 			| [< '(PClose,_); e = secure_expr >] -> e
 			| [< '(PClose,_); e = secure_expr >] -> e
 			| [< >] ->
 			| [< >] ->
-				if !in_display then mk_null_expr (pos cond)
-				else serror()
+				syntax_error (Expected [")"]) s (mk_null_expr (pos cond))
 		in
 		in
 		(EWhile (cond,e,NormalWhile),punion p1 (pos e))
 		(EWhile (cond,e,NormalWhile),punion p1 (pos e))
 	| [< '(Kwd Do,p1); e = secure_expr; s >] ->
 	| [< '(Kwd Do,p1); e = secure_expr; s >] ->
 		begin match s with parser
 		begin match s with parser
 			| [< '(Kwd While,_); '(POpen,_); cond = secure_expr; s >] ->
 			| [< '(Kwd While,_); '(POpen,_); cond = secure_expr; s >] ->
-				let p2 = expect_unless_resume_p pclose s in
+				let p2 = expect_unless_resume_p PClose s in
 				(EWhile (cond,e,DoWhile),punion p1 p2)
 				(EWhile (cond,e,DoWhile),punion p1 p2)
 			| [< >] ->
 			| [< >] ->
-				if !in_display then e (* ignore do *)
-				else serror()
+				syntax_error (Expected ["while"]) s e (* ignore do *)
 		end
 		end
 	| [< '(Kwd Switch,p1); e = secure_expr; s >] ->
 	| [< '(Kwd Switch,p1); e = secure_expr; s >] ->
 		begin match s with parser
 		begin match s with parser
 			| [< '(BrOpen,_); cases , def = parse_switch_cases e []; '(BrClose,p2); s >] -> (ESwitch (e,cases,def),punion p1 p2)
 			| [< '(BrOpen,_); cases , def = parse_switch_cases e []; '(BrClose,p2); s >] -> (ESwitch (e,cases,def),punion p1 p2)
 			| [< >] ->
 			| [< >] ->
-				if !in_display then (ESwitch(e,[],None),punion p1 (pos e))
-				else serror()
+				syntax_error (Expected ["{"]) s (ESwitch(e,[],None),punion p1 (pos e))
 		end
 		end
 	| [< '(Kwd Try,p1); e = secure_expr; cl,p2 = parse_catches e [] (pos e) >] -> (ETry (e,cl),punion p1 p2)
 	| [< '(Kwd Try,p1); e = secure_expr; cl,p2 = parse_catches e [] (pos e) >] -> (ETry (e,cl),punion p1 p2)
 	| [< '(IntInterval i,p1); e2 = expr >] -> make_binop OpInterval (EConst (Int i),p1) e2
 	| [< '(IntInterval i,p1); e2 = expr >] -> make_binop OpInterval (EConst (Int i),p1) e2
@@ -1293,8 +1267,8 @@ and expr = parser
 and expr_next e1 s =
 and expr_next e1 s =
 	try
 	try
 		expr_next' e1 s
 		expr_next' e1 s
-	with Stream.Error _ when !in_display ->
-		e1
+	with Stream.Error msg ->
+		syntax_error (StreamError msg) s e1
 
 
 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 >] ->
@@ -1304,12 +1278,12 @@ and expr_next' e1 = parser
 	| [< '(Dot,p); e = parse_field e1 p >] -> e
 	| [< '(Dot,p); e = parse_field e1 p >] -> e
 	| [< '(POpen,p1); e = parse_call_params (fun el p2 -> (ECall(e1,el)),punion (pos e1) p2) p1; s >] -> expr_next e s
 	| [< '(POpen,p1); e = parse_call_params (fun el p2 -> (ECall(e1,el)),punion (pos e1) p2) p1; s >] -> expr_next e s
 	| [< '(BkOpen,p1); e2 = secure_expr; s >] ->
 	| [< '(BkOpen,p1); e2 = secure_expr; s >] ->
-		let p2 = expect_unless_resume_p bkclose s in
+		let p2 = expect_unless_resume_p BkClose s in
 		let e2 = check_signature_mark e2 p1 p2 in
 		let e2 = check_signature_mark e2 p1 p2 in
 		expr_next (EArray (e1,e2), punion (pos e1) p2) s
 		expr_next (EArray (e1,e2), punion (pos e1) p2) s
 	| [< '(Arrow,pa); s >] ->
 	| [< '(Arrow,pa); s >] ->
 		let er = expr s in
 		let er = expr s in
-		arrow_function (snd e1) [arrow_first_param e1] er
+		arrow_function (snd e1) [arrow_first_param e1 s] er
 	| [< '(Binop OpGt,p1); s >] ->
 	| [< '(Binop OpGt,p1); s >] ->
 		(match s with parser
 		(match s with parser
 		| [< '(Binop OpGt,p2) when p1.pmax = p2.pmin; s >] ->
 		| [< '(Binop OpGt,p2) when p1.pmax = p2.pmin; s >] ->
@@ -1330,7 +1304,7 @@ and expr_next' e1 = parser
 	| [< '(Question,_); e2 = expr; s >] ->
 	| [< '(Question,_); e2 = expr; s >] ->
 		begin match s with parser
 		begin match s with parser
 		| [< '(DblDot,_); e3 = expr >] -> (ETernary (e1,e2,e3),punion (pos e1) (pos e3))
 		| [< '(DblDot,_); e3 = expr >] -> (ETernary (e1,e2,e3),punion (pos e1) (pos e3))
-		| [< >] -> if !in_display then e2 else serror()
+		| [< >] -> syntax_error (Expected [":"]) s e2
 		end
 		end
 	| [< '(Kwd In,_); e2 = expr >] ->
 	| [< '(Kwd In,_); e2 = expr >] ->
 		make_binop OpIn e1 e2
 		make_binop OpIn e1 e2
@@ -1369,13 +1343,13 @@ and parse_switch_cases eswitch cases = parser
 			| _ -> let p = punion p1 p2 in Some ((EBlock b,p)),p
 			| _ -> let p = punion p1 p2 in Some ((EBlock b,p)),p
 		in
 		in
 		let l , def = parse_switch_cases eswitch cases s in
 		let l , def = parse_switch_cases eswitch cases s in
-		(match def with None -> () | Some _ -> error Duplicate_default p1);
+		(match def with None -> () | Some _ -> syntax_error Duplicate_default ~pos:(Some p1) s ());
 		l , Some b
 		l , Some b
 	| [< '(Kwd Case,p1); el = psep Comma expr_or_var; eg = popt parse_guard; s >] ->
 	| [< '(Kwd Case,p1); el = psep Comma expr_or_var; eg = popt parse_guard; s >] ->
-		let pdot = expect_unless_resume_p colon s in
+		let pdot = expect_unless_resume_p DblDot s in
 		if !was_auto_triggered then check_resume pdot (fun () -> ()) (fun () -> ());
 		if !was_auto_triggered then check_resume pdot (fun () -> ()) (fun () -> ());
 		(match el with
 		(match el with
-		| [] -> error (Custom "case without a pattern is not allowed") p1
+		| [] -> syntax_error (Custom "case without a pattern is not allowed") ~pos:(Some p1) s ([],None)
 		| _ ->
 		| _ ->
 			let b,p2 = (block_with_pos [] p1 s) in
 			let b,p2 = (block_with_pos [] p1 s) in
 			let b,p = match b with
 			let b,p = match b with
@@ -1430,25 +1404,22 @@ and parse_call_params f p1 s =
 		| [< >] -> parse_next_param [] p1
 		| [< >] -> parse_next_param [] p1
 	end
 	end
 
 
-and secure_expr s =
-	expr_or_fail serror s
-
 (* Tries to parse a toplevel expression and defaults to a null expression when in display mode.
 (* Tries to parse a toplevel expression and defaults to a null expression when in display mode.
    This function always accepts in display mode and should only be used for expected expressions,
    This function always accepts in display mode and should only be used for expected expressions,
    not accepted ones! *)
    not accepted ones! *)
-and expr_or_fail fail s =
-	match s with parser
+and secure_expr = parser
 	| [< e = expr >] -> e
 	| [< e = expr >] -> e
-	| [< >] -> if !in_display then begin
-		let last = last_token s in
-		let plast = pos last in
-		let offset = match fst last with
-			| Const _ | Kwd _ | Dollar _ -> 1
-			| _ -> 0
-		in
-		let plast = {plast with pmin = plast.pmax + offset} in
-		mk_null_expr (punion_next plast s)
-	end else fail()
+	| [< s >] ->
+		syntax_error (Expected ["expression"]) s (
+			let last = last_token s in
+			let plast = pos last in
+			let offset = match fst last with
+				| Const _ | Kwd _ | Dollar _ -> 1
+				| _ -> 0
+			in
+			let plast = {plast with pmin = plast.pmax + offset} in
+			mk_null_expr (punion_next plast s)
+		)
 
 
 let rec validate_macro_cond e = match fst e with
 let rec validate_macro_cond e = match fst e with
 	| EConst (Ident _)
 	| EConst (Ident _)

+ 38 - 16
src/syntax/parser.ml

@@ -30,6 +30,8 @@ type error_msg =
 	| Unclosed_macro
 	| Unclosed_macro
 	| Unimplemented
 	| Unimplemented
 	| Missing_type
 	| Missing_type
+	| Expected of string list
+	| StreamError of string
 	| Custom of string
 	| Custom of string
 
 
 type decl_flag =
 type decl_flag =
@@ -60,13 +62,26 @@ let error_msg = function
 	| Unclosed_macro -> "Unclosed macro"
 	| Unclosed_macro -> "Unclosed macro"
 	| Unimplemented -> "Not implemented for current platform"
 	| Unimplemented -> "Not implemented for current platform"
 	| Missing_type -> "Missing type declaration"
 	| Missing_type -> "Missing type declaration"
+	| Expected sl -> "Expected " ^ (String.concat " or " sl)
+	| StreamError s -> s
 	| Custom s -> s
 	| Custom s -> s
 
 
+type parse_data = string list * (type_def * pos) list
+
+type parse_error = (error_msg * pos)
+
+type 'a parse_result =
+	(* Parsed display file. There can be errors. *)
+	| ParseDisplayFile of 'a * parse_error list
+	(* Parsed non-display-file without errors. *)
+	| ParseSuccess of 'a
+	(* Parsed non-display file with errors *)
+	| ParseError of 'a * parse_error * parse_error list
+
 let syntax_completion kind p =
 let syntax_completion kind p =
 	raise (SyntaxCompletion(kind,p))
 	raise (SyntaxCompletion(kind,p))
 
 
 let error m p = raise (Error (m,p))
 let error m p = raise (Error (m,p))
-let display_error : (error_msg -> pos -> unit) ref = ref (fun _ _ -> assert false)
 
 
 let special_identifier_files : (string,string) Hashtbl.t = Hashtbl.create 0
 let special_identifier_files : (string,string) Hashtbl.t = Hashtbl.create 0
 
 
@@ -95,6 +110,22 @@ module TokenCache = struct
 		(fun () -> cache := old_cache)
 		(fun () -> cache := old_cache)
 end
 end
 
 
+let last_token s =
+	let n = Stream.count s in
+	TokenCache.get (if n = 0 then 0 else n - 1)
+
+let last_pos s = pos (last_token s)
+
+let next_token s = match Stream.peek s with
+	| Some (Eof,p) ->
+		(Eof,{p with pmax = max_int})
+	| Some tk -> tk
+	| None ->
+		let last_pos = pos (last_token s) in
+		(Eof,{last_pos with pmax = max_int})
+
+let next_pos s = pos (next_token s)
+
 (* Global state *)
 (* Global state *)
 
 
 let in_display = ref false
 let in_display = ref false
@@ -119,12 +150,13 @@ let reset_state () =
 
 
 let in_display_file = ref false
 let in_display_file = ref false
 let last_doc : (string * int) option ref = ref None
 let last_doc : (string * int) option ref = ref None
+let syntax_errors = ref []
 
 
-let last_token s =
-	let n = Stream.count s in
-	TokenCache.get (if n = 0 then 0 else n - 1)
-
-let last_pos s = pos (last_token s)
+let syntax_error error_msg ?(pos=None) s v =
+	let p = (match pos with Some p -> p | None -> next_pos s) in
+	if not !in_display then error error_msg p;
+	syntax_errors := (error_msg,p) :: !syntax_errors;
+	v
 
 
 let get_doc s =
 let get_doc s =
 	(* do the peek first to make sure we fetch the doc *)
 	(* do the peek first to make sure we fetch the doc *)
@@ -232,16 +264,6 @@ let handle_xml_literal p1 =
 	let e = make_meta Meta.Markup [] e p1 in
 	let e = make_meta Meta.Markup [] e p1 in
 	e
 	e
 
 
-let next_token s = match Stream.peek s with
-	| Some (Eof,p) ->
-		(Eof,{p with pmax = max_int})
-	| Some tk -> tk
-	| None ->
-		let last_pos = pos (last_token s) in
-		(Eof,{last_pos with pmax = max_int})
-
-let next_pos s = pos (next_token s)
-
 let punion_next p1 s =
 let punion_next p1 s =
 	let _,p2 = next_token s in
 	let _,p2 = next_token s in
 	{
 	{

+ 23 - 11
src/syntax/parserEntry.ml

@@ -81,6 +81,7 @@ let parse ctx code file =
 	code_ref := code;
 	code_ref := code;
 	in_display := !display_position <> null_pos;
 	in_display := !display_position <> null_pos;
 	in_display_file := !in_display && Path.unique_full_path file = !display_position.pfile;
 	in_display_file := !in_display && Path.unique_full_path file = !display_position.pfile;
+	syntax_errors := [];
 	let restore =
 	let restore =
 		(fun () ->
 		(fun () ->
 			restore_cache ();
 			restore_cache ();
@@ -159,7 +160,7 @@ let parse ctx code file =
 		| Sharp "if" ->
 		| Sharp "if" ->
 			skip_tokens_loop p test (skip_tokens p false)
 			skip_tokens_loop p test (skip_tokens p false)
 		| Eof ->
 		| Eof ->
-			if !in_display then tk else error Unclosed_macro p
+			syntax_error Unclosed_macro ~pos:(Some p) sraw tk
 		| _ ->
 		| _ ->
 			skip_tokens p test
 			skip_tokens p test
 
 
@@ -173,10 +174,16 @@ let parse ctx code file =
 	) in
 	) in
 	try
 	try
 		let l = parse_file s in
 		let l = parse_file s in
-		(match !mstack with p :: _ when not !in_display -> error Unclosed_macro p | _ -> ());
+		(match !mstack with p :: _ -> syntax_error Unclosed_macro ~pos:(Some p) sraw () | _ -> ());
+		let was_display_file = !in_display_file in
 		restore();
 		restore();
 		Lexer.restore old;
 		Lexer.restore old;
-		l
+		if was_display_file then
+			ParseDisplayFile(l,!syntax_errors)
+		else begin match List.rev !syntax_errors with
+			| [] -> ParseSuccess l
+			| error :: errors -> ParseError(l,error,errors)
+		end
 	with
 	with
 		| Stream.Error _
 		| Stream.Error _
 		| Stream.Failure ->
 		| Stream.Failure ->
@@ -193,8 +200,9 @@ let parse_string com s p error inlined =
 	let old = Lexer.save() in
 	let old = Lexer.save() in
 	let old_file = (try Some (Hashtbl.find Lexer.all_files p.pfile) with Not_found -> None) in
 	let old_file = (try Some (Hashtbl.find Lexer.all_files p.pfile) with Not_found -> None) in
 	let old_display = !display_position in
 	let old_display = !display_position in
-	let old_de = !display_error in
 	let old_in_display_file = !in_display_file in
 	let old_in_display_file = !in_display_file in
+	let old_syntax_errors = !syntax_errors in
+	syntax_errors := [];
 	let restore() =
 	let restore() =
 		(match old_file with
 		(match old_file with
 		| None -> ()
 		| None -> ()
@@ -203,16 +211,15 @@ let parse_string com s p error inlined =
 			display_position := old_display;
 			display_position := old_display;
 			in_display_file := old_in_display_file;
 			in_display_file := old_in_display_file;
 		end;
 		end;
-		Lexer.restore old;
-		display_error := old_de
+		syntax_errors := old_syntax_errors;
+		Lexer.restore old
 	in
 	in
 	Lexer.init p.pfile true;
 	Lexer.init p.pfile true;
-	display_error := (fun e p -> raise (Error (e,p)));
 	if not inlined then begin
 	if not inlined then begin
 		display_position := null_pos;
 		display_position := null_pos;
 		in_display_file := false;
 		in_display_file := false;
 	end;
 	end;
-	let pack, decls = try
+	let result = try
 		parse com (Sedlexing.Utf8.from_string s) p.pfile
 		parse com (Sedlexing.Utf8.from_string s) p.pfile
 	with Error (e,pe) ->
 	with Error (e,pe) ->
 		restore();
 		restore();
@@ -222,12 +229,17 @@ let parse_string com s p error inlined =
 		error (Lexer.error_msg e) (if inlined then pe else p)
 		error (Lexer.error_msg e) (if inlined then pe else p)
 	in
 	in
 	restore();
 	restore();
-	pack,decls
+	result
 
 
 let parse_expr_string com s p error inl =
 let parse_expr_string com s p error inl =
 	let head = "class X{static function main() " in
 	let head = "class X{static function main() " in
 	let head = (if p.pmin > String.length head then head ^ String.make (p.pmin - String.length head) ' ' else head) in
 	let head = (if p.pmin > String.length head then head ^ String.make (p.pmin - String.length head) ' ' else head) in
 	let rec loop e = let e = Ast.map_expr loop e in (fst e,p) in
 	let rec loop e = let e = Ast.map_expr loop e in (fst e,p) in
+	let extract_expr (_,decls) = match decls with
+		| [EClass { d_data = [{ cff_name = "main",null_pos; cff_kind = FFun { f_expr = Some e } }]},_] -> (if inl then e else loop e)
+		| _ -> raise Exit
+	in
 	match parse_string com (head ^ s ^ ";}") p error inl with
 	match parse_string com (head ^ s ^ ";}") p error inl with
-	| _,[EClass { d_data = [{ cff_name = "main",null_pos; cff_kind = FFun { f_expr = Some e } }]},_] -> if inl then e else loop e
-	| _ -> raise Exit
+	| ParseSuccess data -> ParseSuccess(extract_expr data)
+	| ParseError(data,error,errors) -> ParseError(extract_expr data,error,errors)
+	| ParseDisplayFile(data,errors) -> ParseDisplayFile(extract_expr data,errors)

+ 27 - 8
src/typing/macroContext.ml

@@ -125,12 +125,23 @@ let load_macro_ref : (typer -> bool -> path -> string -> pos -> (typer * ((strin
 
 
 let make_macro_api ctx p =
 let make_macro_api ctx p =
 	let parse_expr_string s p inl =
 	let parse_expr_string s p inl =
-		typing_timer ctx false (fun() -> try ParserEntry.parse_expr_string ctx.com.defines s p error inl with Exit -> raise MacroApi.Invalid_expr)
+		typing_timer ctx false (fun() ->
+			try
+				begin match ParserEntry.parse_expr_string ctx.com.defines s p error inl with
+					| ParseSuccess data -> data
+					| ParseDisplayFile(data,_) when inl -> data (* ignore errors when inline-parsing in display file *)
+					| ParseDisplayFile _ -> assert false (* cannot happen because ParserEntry.parse_string sets `display_position := null_pos;` *)
+					| ParseError _ -> raise MacroApi.Invalid_expr
+				end
+			with Exit ->
+				raise MacroApi.Invalid_expr)
 	in
 	in
 	let parse_metadata s p =
 	let parse_metadata s p =
 		try
 		try
 			match ParserEntry.parse_string ctx.com.defines (s ^ " typedef T = T") null_pos error false with
 			match ParserEntry.parse_string ctx.com.defines (s ^ " typedef T = T") null_pos error false with
-			| _,[ETypedef t,_] -> t.d_meta
+			| ParseSuccess(_,[ETypedef t,_]) -> t.d_meta
+			| ParseDisplayFile _ -> assert false (* cannot happen because null_pos is used *)
+			| ParseError(_,_,_) -> error "Malformed metadata string" p
 			| _ -> assert false
 			| _ -> assert false
 		with _ ->
 		with _ ->
 			error "Malformed metadata string" p
 			error "Malformed metadata string" p
@@ -223,7 +234,9 @@ let make_macro_api ctx p =
 			typing_timer ctx false (fun() ->
 			typing_timer ctx false (fun() ->
 				let v = (match v with None -> None | Some s ->
 				let v = (match v with None -> None | Some s ->
 					match ParserEntry.parse_string ctx.com.defines ("typedef T = " ^ s) null_pos error false with
 					match ParserEntry.parse_string ctx.com.defines ("typedef T = " ^ s) null_pos error false with
-					| _,[ETypedef { d_data = ct },_] -> Some ct
+					| ParseSuccess(_,[ETypedef { d_data = ct },_]) -> Some ct
+					| ParseDisplayFile _ -> assert false (* cannot happen because null_pos is used *)
+					| ParseError(_,(msg,p),_) -> Parser.error msg p (* p is null_pos, but we don't have anything else here... *)
 					| _ -> assert false
 					| _ -> assert false
 				) in
 				) in
 				let tp = get_type_patch ctx t (Some (f,s)) in
 				let tp = get_type_patch ctx t (Some (f,s)) in
@@ -470,7 +483,8 @@ let get_macro_context ctx p =
 		ctx.com.get_macros <- (fun() -> Some com2);
 		ctx.com.get_macros <- (fun() -> Some com2);
 		com2.package_rules <- PMap.empty;
 		com2.package_rules <- PMap.empty;
 		com2.main_class <- None;
 		com2.main_class <- None;
-		com2.display <- DisplayTypes.DisplayMode.create DMNone;
+		(* Inherit most display settings, but require normal typing. *)
+		com2.display <- {ctx.com.display with dms_kind = DMNone; dms_display = false; dms_full_typing = true };
 		List.iter (fun p -> com2.defines.Define.values <- PMap.remove (Globals.platform_name p) com2.defines.Define.values) Globals.platforms;
 		List.iter (fun p -> com2.defines.Define.values <- PMap.remove (Globals.platform_name p) com2.defines.Define.values) Globals.platforms;
 		com2.defines.Define.defines_signature <- None;
 		com2.defines.Define.defines_signature <- None;
 		com2.class_path <- List.filter (fun s -> not (ExtString.String.exists s "/_std/")) com2.class_path;
 		com2.class_path <- List.filter (fun s -> not (ExtString.String.exists s "/_std/")) com2.class_path;
@@ -491,6 +505,7 @@ let load_macro_module ctx cpath display p =
 	let api, mctx = get_macro_context ctx p in
 	let api, mctx = get_macro_context ctx p in
 	let m = (try Hashtbl.find ctx.g.types_module cpath with Not_found -> cpath) in
 	let m = (try Hashtbl.find ctx.g.types_module cpath with Not_found -> cpath) in
 	(* Temporarily enter display mode while typing the macro. *)
 	(* Temporarily enter display mode while typing the macro. *)
+	let old = mctx.com.display in
 	if display then mctx.com.display <- ctx.com.display;
 	if display then mctx.com.display <- ctx.com.display;
 	let mloaded = TypeloadModule.load_module mctx m p in
 	let mloaded = TypeloadModule.load_module mctx m p in
 	api.MacroApi.current_macro_module <- (fun() -> mloaded);
 	api.MacroApi.current_macro_module <- (fun() -> mloaded);
@@ -502,7 +517,7 @@ let load_macro_module ctx cpath display p =
 		wildcard_packages = [];
 		wildcard_packages = [];
 		module_imports = [];
 		module_imports = [];
 	};
 	};
-	mloaded
+	mloaded,(fun () -> mctx.com.display <- old)
 
 
 let load_macro ctx display cpath f p =
 let load_macro ctx display cpath f p =
 	let api, mctx = get_macro_context ctx p in
 	let api, mctx = get_macro_context ctx p in
@@ -513,7 +528,7 @@ let load_macro ctx display cpath f p =
 	) in
 	) in
 	let (meth,mloaded) = try Hashtbl.find mctx.com.cached_macros (cpath,f) with Not_found ->
 	let (meth,mloaded) = try Hashtbl.find mctx.com.cached_macros (cpath,f) with Not_found ->
 		let t = macro_timer ctx ["typing";s_type_path cpath ^ "." ^ f] in
 		let t = macro_timer ctx ["typing";s_type_path cpath ^ "." ^ f] in
-		let mloaded = load_macro_module ctx cpath display p in
+		let mloaded,restore = load_macro_module ctx cpath display p in
 		let mt = Typeload.load_type_def mctx p { tpackage = fst cpath; tname = snd cpath; tparams = []; tsub = sub } in
 		let mt = Typeload.load_type_def mctx p { tpackage = fst cpath; tname = snd cpath; tparams = []; tsub = sub } in
 		let cl, meth = (match mt with
 		let cl, meth = (match mt with
 			| TClassDecl c ->
 			| TClassDecl c ->
@@ -525,7 +540,7 @@ let load_macro ctx display cpath f p =
 		if not (Common.defined ctx.com Define.NoDeprecationWarnings) then
 		if not (Common.defined ctx.com Define.NoDeprecationWarnings) then
 			DeprecationCheck.check_cf mctx.com meth p;
 			DeprecationCheck.check_cf mctx.com meth p;
 		let meth = (match follow meth.cf_type with TFun (args,ret) -> (args,ret,cl,meth),mloaded | _ -> error "Macro call should be a method" p) in
 		let meth = (match follow meth.cf_type with TFun (args,ret) -> (args,ret,cl,meth),mloaded | _ -> error "Macro call should be a method" p) in
-		mctx.com.display <- DisplayTypes.DisplayMode.create DMNone;
+		restore();
 		if not ctx.in_macro then flush_macro_context mint ctx;
 		if not ctx.in_macro then flush_macro_context mint ctx;
 		Hashtbl.add mctx.com.cached_macros (cpath,f) meth;
 		Hashtbl.add mctx.com.cached_macros (cpath,f) meth;
 		mctx.m <- {
 		mctx.m <- {
@@ -734,7 +749,11 @@ let call_macro ctx path meth args p =
 let call_init_macro ctx e =
 let call_init_macro ctx e =
 	let p = { pfile = "--macro " ^ e; pmin = -1; pmax = -1 } in
 	let p = { pfile = "--macro " ^ e; pmin = -1; pmax = -1 } in
 	let e = try
 	let e = try
-		ParserEntry.parse_expr_string ctx.com.defines e p error false
+		begin match ParserEntry.parse_expr_string ctx.com.defines e p error false with
+		| ParseSuccess data -> data
+		| ParseError(_,(msg,p),_) -> (Parser.error msg p)
+		| ParseDisplayFile _ -> assert false (* cannot happen *)
+		end
 	with err ->
 	with err ->
 		display_error ctx ("Could not parse `" ^ e ^ "`") p;
 		display_error ctx ("Could not parse `" ^ e ^ "`") p;
 		raise err
 		raise err

+ 5 - 1
src/typing/typeloadModule.ml

@@ -899,7 +899,11 @@ let handle_import_hx ctx m decls p =
 			r
 			r
 		with Not_found ->
 		with Not_found ->
 			if Sys.file_exists path then begin
 			if Sys.file_exists path then begin
-				let _,r = TypeloadParse.parse_file ctx.com path p in
+				let _,r = match TypeloadParse.parse_file ctx.com path p with
+					| ParseSuccess data -> data
+					| ParseDisplayFile(data,_) -> data
+					| ParseError(_,(msg,p),_) -> Parser.error msg p
+				in
 				List.iter (fun (d,p) -> match d with EImport _ | EUsing _ -> () | _ -> error "Only import and using is allowed in import.hx files" p) r;
 				List.iter (fun (d,p) -> match d with EImport _ | EUsing _ -> () | _ -> error "Only import and using is allowed in import.hx files" p) r;
 				add_dependency m (make_import_module path r);
 				add_dependency m (make_import_module path r);
 				r
 				r

+ 30 - 8
src/typing/typeloadParse.ml

@@ -21,6 +21,7 @@
 
 
 open Globals
 open Globals
 open Ast
 open Ast
+open DisplayTypes.DiagnosticsSeverity
 open DisplayTypes.DisplayMode
 open DisplayTypes.DisplayMode
 open Common
 open Common
 open Typecore
 open Typecore
@@ -30,7 +31,7 @@ let parse_file_from_lexbuf com file p lexbuf =
 	let t = Timer.timer ["parsing"] in
 	let t = Timer.timer ["parsing"] in
 	Lexer.init file true;
 	Lexer.init file true;
 	incr stats.s_files_parsed;
 	incr stats.s_files_parsed;
-	let data = try
+	let parse_result = try
 		ParserEntry.parse com.defines lexbuf file
 		ParserEntry.parse com.defines lexbuf file
 	with
 	with
 		| Sedlexing.MalFormed ->
 		| Sedlexing.MalFormed ->
@@ -40,9 +41,9 @@ let parse_file_from_lexbuf com file p lexbuf =
 			t();
 			t();
 			raise e
 			raise e
 	in
 	in
-	begin match !Parser.display_mode with
-		| DMModuleSymbols (Some "") -> ()
-		| DMModuleSymbols filter when filter = None && DisplayPosition.is_display_file file ->
+	begin match !Parser.display_mode,parse_result with
+		| DMModuleSymbols (Some ""),_ -> ()
+		| DMModuleSymbols filter,(ParseSuccess data | ParseDisplayFile(data,_)) when filter = None && DisplayPosition.is_display_file file ->
 			let ds = DocumentSymbols.collect_module_symbols (filter = None) data in
 			let ds = DocumentSymbols.collect_module_symbols (filter = None) data in
 			DisplayException.raise_module_symbols (DocumentSymbols.Printer.print_module_symbols com [file,ds] filter);
 			DisplayException.raise_module_symbols (DocumentSymbols.Printer.print_module_symbols com [file,ds] filter);
 		| _ ->
 		| _ ->
@@ -50,7 +51,7 @@ let parse_file_from_lexbuf com file p lexbuf =
 	end;
 	end;
 	t();
 	t();
 	Common.log com ("Parsed " ^ file);
 	Common.log com ("Parsed " ^ file);
-	data
+	parse_result
 
 
 let parse_file_from_string com file p string =
 let parse_file_from_string com file p string =
 	parse_file_from_lexbuf com file p (Sedlexing.Utf8.from_string string)
 	parse_file_from_lexbuf com file p (Sedlexing.Utf8.from_string string)
@@ -111,7 +112,7 @@ let resolve_module_file com m remap p =
 		if List.exists (fun path -> ExtString.String.starts_with file (try Path.unique_full_path path with _ -> path)) com.std_path then raise Not_found;
 		if List.exists (fun path -> ExtString.String.starts_with file (try Path.unique_full_path path with _ -> path)) com.std_path then raise Not_found;
 	| _ -> ());
 	| _ -> ());
 	if !forbid then begin
 	if !forbid then begin
-		let _, decls = (!parse_hook) com file p in
+		let parse_result = (!parse_hook) com file p in
 		let rec loop decls = match decls with
 		let rec loop decls = match decls with
 			| ((EImport _,_) | (EUsing _,_)) :: decls -> loop decls
 			| ((EImport _,_) | (EUsing _,_)) :: decls -> loop decls
 			| (EClass d,_) :: _ -> d.d_meta
 			| (EClass d,_) :: _ -> d.d_meta
@@ -120,7 +121,10 @@ let resolve_module_file com m remap p =
 			| (ETypedef d,_) :: _ -> d.d_meta
 			| (ETypedef d,_) :: _ -> d.d_meta
 			| [] -> []
 			| [] -> []
 		in
 		in
-		let meta = loop decls in
+		let meta =  match parse_result with
+			| ParseSuccess(_,decls) | ParseDisplayFile((_,decls),_) -> loop decls
+			| ParseError _ -> []
+		in
 		if not (Meta.has Meta.NoPackageRestrict meta) then begin
 		if not (Meta.has Meta.NoPackageRestrict meta) then begin
 			let x = (match fst m with [] -> assert false | x :: _ -> x) in
 			let x = (match fst m with [] -> assert false | x :: _ -> x) in
 			raise (Forbid_package ((x,m,p),[],platform_name_macro com));
 			raise (Forbid_package ((x,m,p),[],platform_name_macro com));
@@ -143,7 +147,25 @@ let resolve_module_file com m remap p =
 let parse_module' com m p =
 let parse_module' com m p =
 	let remap = ref (fst m) in
 	let remap = ref (fst m) in
 	let file = resolve_module_file com m remap p in
 	let file = resolve_module_file com m remap p in
-	let pack, decls = (!parse_hook) com file p in
+	let handle_parser_error msg p =
+		let msg = Parser.error_msg msg in
+		match com.display.dms_error_policy with
+			| EPShow -> error msg p
+			| EPIgnore -> ()
+			| EPCollect -> add_diagnostics_message com msg p Error
+	in
+	let pack,decls = match (!parse_hook) com file p with
+		| ParseSuccess data -> data
+		| ParseDisplayFile(data,errors) ->
+			begin match errors with
+			| (msg,p) :: _ -> handle_parser_error msg p
+			| [] -> ()
+			end;
+			data
+		| ParseError(data,(msg,p),_) ->
+			handle_parser_error msg p;
+			data
+	in
 	file,remap,pack,decls
 	file,remap,pack,decls
 
 
 let parse_module ctx m p =
 let parse_module ctx m p =

+ 7 - 2
src/typing/typer.ml

@@ -1540,8 +1540,13 @@ and format_string ctx s p =
 		if slen > 0 then begin
 		if slen > 0 then begin
 			let e =
 			let e =
 				let ep = { p with pmin = !pmin + pos + 2; pmax = !pmin + send + 1 } in
 				let ep = { p with pmin = !pmin + pos + 2; pmax = !pmin + send + 1 } in
-				try ParserEntry.parse_expr_string ctx.com.defines scode ep error true
-				with Exit -> error "Invalid interpolated expression" ep
+				try
+					begin match ParserEntry.parse_expr_string ctx.com.defines scode ep error true with
+						| ParseSuccess data | ParseDisplayFile(data,_) -> data
+						| ParseError(_,(msg,p),_) -> error (Parser.error_msg msg) p
+					end
+				with Exit ->
+					error "Invalid interpolated expression" ep
 			in
 			in
 			add_expr e slen
 			add_expr e slen
 		end;
 		end;

+ 4 - 0
tests/display/src/DisplayTestContext.hx

@@ -14,6 +14,10 @@ class HaxeInvocationException {
 		this.arguments = arguments;
 		this.arguments = arguments;
 		this.source = source;
 		this.source = source;
 	}
 	}
+
+	public function toString() {
+		return 'HaxeInvocationException($message, $fieldName, $arguments, $source])';
+	}
 }
 }
 
 
 class DisplayTestContext {
 class DisplayTestContext {

+ 1 - 2
tests/misc/projects/Issue3699/compile-var-fail.hxml.stderr

@@ -1,2 +1 @@
-MainVar.hx:3: characters 11-12 : expression expected after =
-MainVar.hx:3: characters 13-14 : Unexpected ;
+MainVar.hx:3: characters 13-14 : Expected expression

+ 2 - 3
tests/runci/targets/Macro.hx

@@ -10,9 +10,8 @@ class Macro {
 	static public function run(args:Array<String>) {
 	static public function run(args:Array<String>) {
 		runCommand("haxe", ["compile-macro.hxml"].concat(args));
 		runCommand("haxe", ["compile-macro.hxml"].concat(args));
 
 
-		// TODO: enable this again at some point
-		// changeDirectory(displayDir);
-		// runCommand("haxe", ["build.hxml"]);
+		changeDirectory(displayDir);
+		runCommand("haxe", ["build.hxml"]);
 
 
 		changeDirectory(sourcemapsDir);
 		changeDirectory(sourcemapsDir);
 		runCommand("haxe", ["run.hxml"]);
 		runCommand("haxe", ["run.hxml"]);

+ 16 - 1
tests/server/src/HaxeServerTestCase.hx

@@ -9,6 +9,7 @@ using Lambda;
 
 
 class TestContext {
 class TestContext {
 	public var messages:Array<String> = []; // encapsulation is overrated
 	public var messages:Array<String> = []; // encapsulation is overrated
+	public var errorMessages = [];
 	public var displayServerConfig:DisplayServerConfigBase;
 	public var displayServerConfig:DisplayServerConfigBase;
 
 
 	public function new(config:DisplayServerConfigBase) {
 	public function new(config:DisplayServerConfigBase) {
@@ -54,6 +55,7 @@ class HaxeServerTestCase implements ITest {
 
 
 	function runHaxe(args:Array<String>, storeTypes = false, done:Void->Void) {
 	function runHaxe(args:Array<String>, storeTypes = false, done:Void->Void) {
 		context.messages = [];
 		context.messages = [];
+		context.errorMessages = [];
 		storedTypes = [];
 		storedTypes = [];
 		if (storeTypes) {
 		if (storeTypes) {
 			args = args.concat(['--display', '{ "method": "typer/compiledTypes", "id": 1 }']);
 			args = args.concat(['--display', '{ "method": "typer/compiledTypes", "id": 1 }']);
@@ -64,7 +66,7 @@ class HaxeServerTestCase implements ITest {
 			}
 			}
 			done();
 			done();
 		}, function(message) {
 		}, function(message) {
-			Assert.fail(message);
+			context.errorMessages.push(message);
 			done();
 			done();
 		});
 		});
 	}
 	}
@@ -82,6 +84,15 @@ class HaxeServerTestCase implements ITest {
 		return false;
 		return false;
 	}
 	}
 
 
+	function hasErrorMessage<T>(msg:String) {
+		for (message in context.errorMessages) {
+			if (message.endsWith(msg)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	function getStoredType(typePackage:String, typeName:String) {
 	function getStoredType(typePackage:String, typeName:String) {
 		for (type in storedTypes) {
 		for (type in storedTypes) {
 			if (type.pack.join(".") == typePackage && type.name == typeName) {
 			if (type.pack.join(".") == typePackage && type.name == typeName) {
@@ -91,6 +102,10 @@ class HaxeServerTestCase implements ITest {
 		return null;
 		return null;
 	}
 	}
 
 
+	function assertErrorMessage(message:String, ?p:haxe.PosInfos) {
+		Assert.isTrue(hasErrorMessage(message), p);
+	}
+
 	function assertHasPrint(line:String, ?p:haxe.PosInfos) {
 	function assertHasPrint(line:String, ?p:haxe.PosInfos) {
 		Assert.isTrue(hasMessage("Haxe print: " + line), null, p);
 		Assert.isTrue(hasMessage("Haxe print: " + line), null, p);
 	}
 	}

+ 11 - 0
tests/server/src/Main.hx

@@ -77,6 +77,17 @@ class ServerTests extends HaxeServerTestCase {
 		assertSkipping("BuiltClass", "BuildMacro");
 		assertSkipping("BuiltClass", "BuildMacro");
 		assertSkipping("BuildMacro");
 		assertSkipping("BuildMacro");
 	}
 	}
+
+	function testBrokenSyntaxDiagnostics() {
+		vfs.putContent("BrokenSyntax.hx", getTemplate("BrokenSyntax.hx"));
+		vfs.putContent("Empty.hx", getTemplate("Empty.hx"));
+		var args = ["-main", "BrokenSyntax.hx", "--interp", "--no-output"];
+		runHaxe(args);
+		assertErrorMessage("Expected }");
+		runHaxe(args.concat(["--display", "Empty.hx@0@diagnostics"]));
+		runHaxe(args);
+		assertErrorMessage("Expected }");
+	}
 }
 }
 
 
 class Main {
 class Main {

+ 6 - 0
tests/server/test/templates/BrokenSyntax.hx

@@ -0,0 +1,6 @@
+class BrokenSyntax {
+	static public function main() {
+		{}.foo();
+		trace("ok");
+	}
+}