Browse Source

Fix error reporting.

Fixes #129.
Andreas Stenius 11 years ago
parent
commit
c8632206e3
4 changed files with 144 additions and 72 deletions
  1. 31 18
      src/erlydtl_compiler.erl
  2. 25 22
      src/erlydtl_scanner.erl
  3. 27 27
      src/erlydtl_scanner.slex
  4. 61 5
      tests/src/erlydtl_unittests.erl

+ 31 - 18
src/erlydtl_compiler.erl

@@ -600,7 +600,9 @@ check_scan({error, Err, State}, Context) ->
             check_scan(apply(Context#dtl_context.scanner_module, resume, [NewState]), Context);
         ExtRes ->
             ExtRes
-    end.
+    end;
+check_scan({error, _}=Error, _Context) ->
+    Error.
 
 check_parse({ok, _}=Ok, [], _Context) -> Ok;
 check_parse({ok, Parsed}, Acc, _Context) -> {ok, Acc ++ Parsed};
@@ -1949,30 +1951,41 @@ add_warnings(Warnings, Context) ->
       Context, Warnings).
 
 get_error_item(Report, Prefix, File, Error) ->
-    case Error of
-        {Line, ErrorDesc}
-          when is_integer(Line) ->
-            new_error_item(Report, Prefix, File, Line, ?MODULE, ErrorDesc);
-        {Line, Module, ErrorDesc}
-          when is_integer(Line), is_atom(Module) ->
-            new_error_item(Report, Prefix, File, Line, Module, ErrorDesc);
-        {_, InfoList} when is_list(InfoList) -> Error;
-        ErrorDesc ->
-            new_error_item(Report, Prefix, File, none, ?MODULE, ErrorDesc)
+    case compose_error_desc(Error) of
+        {Pos, Module, ErrorDesc} ->
+            new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc);
+        ErrorItem ->
+            ErrorItem
     end.
-    
-new_error_item(Report, Prefix, File, Line, Module, ErrorDesc) ->
+
+compose_error_desc({Line, ErrorDesc})
+  when is_integer(Line) ->
+    {Line, ?MODULE, ErrorDesc};
+compose_error_desc({{Line, Col}, Module, _}=ErrorDesc)
+  when is_integer(Line), is_integer(Col), is_atom(Module) ->
+    ErrorDesc;
+compose_error_desc({Line, Module, _}=ErrorDesc)
+  when is_integer(Line), is_atom(Module) ->
+    ErrorDesc;
+compose_error_desc({_, InfoList}=ErrorDesc)
+  when is_list(InfoList) -> ErrorDesc;
+compose_error_desc(ErrorDesc) ->
+    {none, ?MODULE, ErrorDesc}.
+
+new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc) ->
     if Report  ->
             io:format("~s:~s~s~s~n",
-                      [File, line_info(Line), Prefix,
+                      [File, pos_info(Pos), Prefix,
                        Module:format_error(ErrorDesc)]);
        true -> nop
     end,
-    {File, [{Line, Module, ErrorDesc}]}.
+    {File, [{Pos, Module, ErrorDesc}]}.
 
-line_info(none) -> " ";
-line_info(Line) when is_integer(Line) ->
-    io_lib:format("~b: ", [Line]).
+pos_info(none) -> " ";
+pos_info(Line) when is_integer(Line) ->
+    io_lib:format("~b: ", [Line]);
+pos_info({Line, Col}) when is_integer(Line), is_integer(Col) ->
+    io_lib:format("~b:~b ", [Line, Col]).
 
 pack_error_list(Es) ->
     collect_error_info([], Es, []).

+ 25 - 22
src/erlydtl_scanner.erl

@@ -36,15 +36,15 @@
 %%%-------------------------------------------------------------------
 -module(erlydtl_scanner).
 
-%% This file was generated 2013-12-02 15:52:41 UTC by slex 0.1.0-2-gb81b78b.
+%% This file was generated 2014-02-15 07:03:20 UTC by slex 0.2.0-2-g8e71a02.
 %% http://github.com/erlydtl/slex
--slex_source("src/erlydtl_scanner.slex").
+-slex_source(["src/erlydtl_scanner.slex"]).
 
 -export([scan/1, scan/4]).
 
 -compile(nowarn_unused_vars).
 
--export([resume/1]).
+-export([resume/1, format_error/1]).
 
 -record(scanner_state,
 	{template = [], scanned = [], pos = {1, 1},
@@ -54,6 +54,14 @@ resume(#scanner_state{template = Template,
 		      scanned = Scanned, pos = Pos, state = State}) ->
     scan(Template, Scanned, Pos, State).
 
+return_error(Error, P, T, S, St) ->
+    {error, {P, erlydtl_scanner, Error},
+     #scanner_state{template = T,
+		    scanned = post_process(S, err), pos = P, state = St}}.
+
+return_error(Error, P) ->
+    {error, {P, erlydtl_scanner, Error}}.
+
 to_atom(L) when is_list(L) -> list_to_atom(L).
 
 to_keyword(L, P) -> {to_atom(L ++ "_keyword"), P, L}.
@@ -130,6 +138,15 @@ is_keyword(open, "blocktrans") -> true;
 is_keyword(open, "endblocktrans") -> true;
 is_keyword(_, _) -> false.
 
+format_error({illegal_char, C}) ->
+    io_lib:format("Illegal character '~s'", [[C]]);
+format_error({eof, Where}) ->
+    io_lib:format("Unexpected end of file ~s",
+		  [format_where(Where)]).
+
+format_where(in_comment) -> "in comment";
+format_where(in_code) -> "in code block".
+
 scan(Template) when is_list(Template) ->
     scan(Template, [], {1, 1}, in_text).
 
@@ -465,11 +482,7 @@ scan([H | T], S, {R, C} = P, {in_code, E})
 	 end,
 	 {in_number, E});
 scan([H | T], S, {R, C} = P, {in_code, E} = St) ->
-    {error,
-     {R, erlydtl_scanner,
-      lists:concat(["Illegal character in column ", C])},
-     #scanner_state{template = [H | T],
-		    scanned = post_process(S, err), pos = P, state = St}};
+    return_error({illegal_char, H}, P, [H | T], S, St);
 scan([H | T], S, {R, C} = P, {in_number, E} = St)
     when H >= $0 andalso H =< $9 ->
     scan(T,
@@ -486,12 +499,7 @@ scan([H | T], S, {R, C} = P, {in_number, E} = St)
 	 end,
 	 St);
 scan([H | T], S, {R, C} = P, {in_number, E} = St) ->
-    {error,
-     {R, erlydtl_scanner,
-      lists:concat(["Illegal character in column ", C])},
-     #scanner_state{template = [H | T],
-		    scanned = post_process(S, err), pos = P,
-		    state = {in_code, E}}};
+    return_error({illegal_char, H}, P, [H | T], S, St);
 scan([H | T], S, {R, C} = P, {in_identifier, E})
     when H >= $a andalso H =< $z orelse
 	   H >= $A andalso H =< $Z orelse
@@ -509,18 +517,13 @@ scan([H | T], S, {R, C} = P, {in_identifier, E})
 	 end,
 	 {in_identifier, E});
 scan([H | T], S, {R, C} = P, {in_identifier, E} = St) ->
-    {error,
-     {R, erlydtl_scanner,
-      lists:concat(["Illegal character in column ", C])},
-     #scanner_state{template = [H | T],
-		    scanned = post_process(S, err), pos = P,
-		    state = {in_code, E}}};
+    return_error({illegal_char, H}, P, [H | T], S, St);
 scan([], S, {R, C} = P, in_text = St) ->
     {ok, lists:reverse(post_process(S, eof))};
 scan([], S, {R, C} = P, {in_comment, E} = St) ->
-    {error, "Reached end of file inside a comment."};
+    return_error({eof, in_comment}, P);
 scan([], S, {R, C} = P, {_, E} = St) ->
-    {error, "Reached end of file inside a code block."}.
+    return_error({eof, in_code}, P).
 
 post_process(_, {string, _, L} = T, _) ->
     setelement(3, T, begin L1 = lists:reverse(L), L1 end);

+ 27 - 27
src/erlydtl_scanner.slex

@@ -37,7 +37,7 @@
 -function scan.
 -init_state in_text.
 form -compile(nowarn_unused_vars) end.
-form -export([resume/1]) end.
+form -export([resume/1, format_error/1]) end.
 form \
 -record(scanner_state, { \
           template=[], \
@@ -230,24 +230,10 @@ end.
     (H >= $0 andalso H =< $9) orelse H == $- \
   end: number_literal, in_number.
 
-124 any in_code:
-  expr \
-    {error, {R, erlydtl_scanner, \
-             lists:concat(["Illegal character in column ", C])}, \
-     #scanner_state{ template=[H|T], scanned=post_process(S, err), \
-                     pos=P, state=St } \
-    } \
-  end.
+124 any in_code: expr return_error({illegal_char, H}, P, [H|T], S, St) end.
 
 130 any in_number, expr H >= $0 andalso H =< $9 end: +number_literal.
-132 any in_number:
-  expr \
-    {error, {R, erlydtl_scanner, \
-             lists:concat(["Illegal character in column ", C])}, \
-     #scanner_state{ template=[H|T], scanned=post_process(S, err), \
-                     pos=P, state={in_code, E} } \
-    } \
-  end.
+132 any in_number: expr return_error({illegal_char, H}, P, [H|T], S, St) end.
 
 140 any in_identifier,
   expr \
@@ -256,22 +242,15 @@ end.
     (H >= $0 andalso H =< $9) orelse \
      H == $_ \
   end: +identifier, in_identifier.
-142 any in_identifier:
-  expr \
-    {error, {R, erlydtl_scanner, \
-             lists:concat(["Illegal character in column ", C])}, \
-     #scanner_state{ template=[H|T], scanned=post_process(S, err), \
-                     pos=P, state={in_code, E} } \
-    } \
-  end.
+142 any in_identifier: expr return_error({illegal_char, H}, P, [H|T], S, St) end.
 
 200 : in_text- :
   expr \
     {ok, lists:reverse(post_process(S,eof))} \
   end.
 
-202 : in_comment : expr {error, "Reached end of file inside a comment."} end.
-204 : any : expr {error, "Reached end of file inside a code block."} end.
+202 : in_comment : expr return_error({eof, in_comment}, P) end.
+204 : any : expr return_error({eof, in_code}, P) end.
 
 
 %% Process tokens as we parse them
@@ -292,6 +271,17 @@ identifier: expr is_keyword(any, T) end.
 
 %% Utility functions
 
+form return_error(Error, P, T, S, St) -> \
+  {error, \
+    {P, erlydtl_scanner, Error}, \
+    #scanner_state{ template=T, \
+                    scanned=post_process(S, err), \
+                    pos=P, state=St } \
+  } \
+end.
+
+form return_error(Error, P) -> {error, {P, erlydtl_scanner, Error}} end.
+
 form to_atom(L) when is_list(L) -> list_to_atom(L) end.
 form to_keyword(L, P) -> {to_atom(L ++ "_keyword"), P, L} end.
 form atomize(L, T) -> setelement(3, T, to_atom(L)) end.
@@ -371,3 +361,13 @@ form \
   is_keyword(open, "endblocktrans") -> true; \
   is_keyword(_, _) -> false \
 end.
+
+form format_error({illegal_char, C}) -> \
+       io_lib:format("Illegal character '~s'", [[C]]); \
+     format_error({eof, Where}) -> \
+       io_lib:format("Unexpected end of file ~s", [format_where(Where)]) \
+end.
+
+form format_where(in_comment) -> "in comment"; \
+     format_where(in_code) -> "in code block" \
+end.

+ 61 - 5
tests/src/erlydtl_unittests.erl

@@ -4,6 +4,13 @@
 
 -record(testrec, {foo, bar, baz}).
 
+-ifndef(GRP_ERROR_REPORTING_COMPILER_OPTS).
+-define(GRP_ERROR_REPORTING_COMPILER_OPTS,[]).
+%%-define(GRP_ERROR_REPORTING_COMPILER_OPTS,[report]).
+%% define GRP_ERROR_REPORTING_COMPILER_OPTS to [report] to print
+%% tested error messages.
+-endif.
+
 tests() ->
     [
      %% {"scanner",
@@ -1239,9 +1246,54 @@ tests() ->
       ]},
      {"error reporting",
       [{"no out dir warning",
-        <<"foo bar">>, [], [], [], <<"foo bar">>, [error_info([no_out_dir])]},
+        <<"foo bar">>,
+        [], [], %% Vars, RenderOpts
+        %%[report], %% CompilerOpts
+        ?GRP_ERROR_REPORTING_COMPILER_OPTS,
+        <<"foo bar">>, %% Output
+        [error_info([no_out_dir])] %% Warnings
+       },
        {"warnings as errors",
-        <<"foo bar">>, [], [], [warnings_as_errors], {error, [error_info([no_out_dir])], []}}
+        <<"foo bar">>,
+        [], [],
+        %%[report, warnings_as_errors],
+        [warnings_as_errors|?GRP_ERROR_REPORTING_COMPILER_OPTS],
+        {error, %% Output...
+         [error_info([no_out_dir])], %% Errors
+         [] %% Warnings
+        }
+       },
+       {"illegal character",
+        <<"{{{">>,
+        [], [],
+        %%[report],
+        ?GRP_ERROR_REPORTING_COMPILER_OPTS,
+        {error,
+         [error_info(
+            [{{1,3},erlydtl_scanner,{illegal_char, ${}}] )],
+         []
+        }
+       },
+       {"unexpected end of file - in code",
+        <<"{{">>,
+        [], [],
+        ?GRP_ERROR_REPORTING_COMPILER_OPTS,
+        {error,
+         [error_info(
+           [{{1,3},erlydtl_scanner,{eof, in_code}}] )],
+         []
+        }
+       },
+       {"unexpected end of file - in comment",
+        <<"{#">>,
+        [], [],
+        ?GRP_ERROR_REPORTING_COMPILER_OPTS,
+        {error,
+         [error_info(
+           [{{1,3},erlydtl_scanner,{eof, in_comment}}] )],
+         []
+        }
+       }
       ]}
     ].
 
@@ -1397,7 +1449,8 @@ process_unit_test({Name, DTL, Vars, RenderOpts, CompilerOpts, Output, Warnings})
               [ActualWarnings, Warnings], [{compile, Tcompile}]);
         {Tcompile, Output} -> test_pass([{compile, Tcompile}]);
         {Tcompile, Err} ->
-            test_fail(Name, "Compile error: ~p", [Err], [{compile, Tcompile}])
+            test_fail(Name, "Compile error: ~p~nExpected: ~p",
+                      [Err, Output], [{compile, Tcompile}])
     end.
 
 
@@ -1444,9 +1497,12 @@ error_info(File, Ws) ->
 error_info({Line, ErrorDesc})
   when is_integer(Line) ->
   {Line, erlydtl_compiler, ErrorDesc};
-error_info({Line, Module, ErrorDesc})
+error_info({Line, Module, _}=ErrorDesc)
   when is_integer(Line), is_atom(Module) ->
-    {Line, Module, ErrorDesc};
+    ErrorDesc;
+error_info({{Line, Col}, Module, _}=ErrorDesc)
+  when is_integer(Line), is_integer(Col), is_atom(Module) ->
+    ErrorDesc;
 error_info(Ws) when is_list(Ws) ->
     error_info("erlydtl_running_test", Ws);
 error_info(ErrorDesc) ->