123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- %%%-------------------------------------------------------------------
- %%% File: erlydtl_compiler_utils.erl
- %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
- %%% @author Evan Miller <emmiller@gmail.com>
- %%% @author Andreas Stenius <kaos@astekk.se>
- %%% @copyright 2008 Roberto Saccon, Evan Miller
- %%% @copyright 2014 Andreas Stenius
- %%% @doc
- %%% ErlyDTL template compiler utils.
- %%% @end
- %%%
- %%% The MIT License
- %%%
- %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
- %%% Copyright (c) 2014 Andreas Stenius
- %%%
- %%% Permission is hereby granted, free of charge, to any person obtaining a copy
- %%% of this software and associated documentation files (the "Software"), to deal
- %%% in the Software without restriction, including without limitation the rights
- %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- %%% copies of the Software, and to permit persons to whom the Software is
- %%% furnished to do so, subject to the following conditions:
- %%%
- %%% The above copyright notice and this permission notice shall be included in
- %%% all copies or substantial portions of the Software.
- %%%
- %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- %%% THE SOFTWARE.
- %%%
- %%% @since 2007-12-16 by Roberto Saccon, Evan Miller
- %%% @since 2014 by Andreas Stenius
- %%%-------------------------------------------------------------------
- -module(erlydtl_compiler_utils).
- -author('rsaccon@gmail.com').
- -author('emmiller@gmail.com').
- -author('Andreas Stenius <kaos@astekk.se>').
- %% --------------------------------------------------------------------
- %% Definitions
- %% --------------------------------------------------------------------
- -define(LIB_VERSION, 1).
- -export([
- add_error/3, add_errors/2,
- add_filters/2,
- add_tags/2,
- add_warning/3, add_warnings/2,
- begin_scope/1, begin_scope/2,
- call_extension/3,
- empty_scope/0,
- end_scope/4,
- format_error/1,
- full_path/2,
- get_current_file/1,
- init_treewalker/1,
- is_stripped_token_empty/1,
- load_library/2, load_library/3, load_library/4,
- merge_info/2,
- print/3, print/4,
- push_scope/2,
- reset_block_dict/2,
- reset_parse_trail/2,
- resolve_variable/2, resolve_variable/3,
- restore_scope/2,
- shorten_filename/1, shorten_filename/2,
- to_string/2,
- unescape_string_literal/1,
- push_auto_escape/2,
- pop_auto_escape/1,
- token_pos/1
- ]).
- -include("erlydtl_ext.hrl").
- %% --------------------------------------------------------------------
- %% API
- %% --------------------------------------------------------------------
- init_treewalker(Context) ->
- TreeWalker = #treewalker{ context=Context },
- case call_extension(Context, init_treewalker, [TreeWalker]) of
- {ok, TW} when is_record(TW, treewalker) -> TW;
- undefined -> TreeWalker
- end.
- to_string(Arg, #dtl_context{ binary_strings = true }) ->
- to_binary_string(Arg);
- to_string(Arg, #dtl_context{ binary_strings = false }) ->
- to_list_string(Arg).
- unescape_string_literal(String) ->
- unescape_string_literal(remove_quotes(String), [], noslash).
- full_path(File, DocRoot) ->
- case filename:absname(File) of
- File -> File;
- _ -> filename:join([DocRoot, File])
- end.
- print(Fmt, Args, Context) ->
- print(?V_INFO, Fmt, Args, Context).
- print(Verbosity, Fmt, Args, #treewalker{ context=Context }) ->
- print(Verbosity, Fmt, Args, Context);
- print(Verbosity, Fmt, Args, #dtl_context{ verbose = Verbose })
- when Verbosity =< Verbose ->
- io:format(Fmt, Args);
- print(_Verbosity, _Fmt, _Args, _Context) ->
- ok.
- get_current_file(#treewalker{ context=Context }) ->
- get_current_file(Context);
- get_current_file(#dtl_context{ parse_trail=[File|_] }) -> File;
- get_current_file(#dtl_context{ is_compiling_dir=Dir }) when Dir =/= false -> Dir;
- get_current_file(#dtl_context{ doc_root=Root }) -> Root.
- add_error(Module, Error, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=add_error(Module, Error, Context) };
- add_error(Module, Error, #dtl_context{
- errors=#error_info{ report=Report, list=Es }=Ei
- }=Context) ->
- Item = get_error_item(
- Report, "",
- get_current_file(Context),
- Error, Module),
- Context#dtl_context{
- errors=Ei#error_info{ list=[Item|Es] }
- }.
- add_errors(Errors, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=add_errors(Errors, Context) };
- add_errors(Errors, Context) ->
- lists:foldl(
- fun (E, C) -> add_error(?MODULE, E, C) end,
- Context, Errors).
- add_warning(Module, Warning, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=add_warning(Module, Warning, Context) };
- add_warning(Module, Warning, #dtl_context{ warnings=warnings_as_errors }=Context) ->
- add_error(Module, Warning, Context);
- add_warning(Module, Warning, #dtl_context{
- warnings=#error_info{ report=Report, list=Ws }=Wi
- }=Context) ->
- Item = get_error_item(
- Report, "Warning: ",
- get_current_file(Context),
- Warning, Module),
- Context#dtl_context{
- warnings=Wi#error_info{ list=[Item|Ws] }
- }.
- add_warnings(Warnings, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=add_warnings(Warnings, Context) };
- add_warnings(Warnings, Context) ->
- lists:foldl(
- fun (W, C) -> add_warning(?MODULE, W, C) end,
- Context, Warnings).
- call_extension(#treewalker{ context=Context }, Fun, Args) ->
- call_extension(Context, Fun, Args);
- call_extension(#dtl_context{ extension_module=undefined }, _Fun, _Args) ->
- undefined;
- call_extension(#dtl_context{ extension_module=Mod }, Fun, Args)
- when is_atom(Mod), is_atom(Fun), is_list(Args) ->
- M = case code:is_loaded(Mod) of
- false ->
- case code:load_file(Mod) of
- {module, Mod} ->
- Mod;
- _ ->
- undefined
- end;
- _ -> Mod
- end,
- if M /= undefined ->
- case erlang:function_exported(M, Fun, length(Args)) of
- true ->
- apply(M, Fun, Args);
- false ->
- undefined
- end;
- true ->
- undefined
- end.
- merge_info(Info1, Info2) when is_record(Info1, ast_info), is_record(Info2, ast_info) ->
- merge_info1(record_info(size, ast_info), Info1, Info2, #ast_info{}).
- resolve_variable(VarName, TreeWalker) ->
- resolve_variable(VarName, undefined, TreeWalker).
- resolve_variable(VarName, Default, #treewalker{ context=Context }) ->
- case resolve_variable1(Context#dtl_context.local_scopes, VarName) of
- undefined ->
- case proplists:get_value(VarName, Context#dtl_context.const) of
- undefined ->
- case proplists:get_value(VarName, Context#dtl_context.vars) of
- undefined -> {default, Default, []};
- Value -> {default_vars, Value, []}
- end;
- Value -> {constant, Value, []}
- end;
- {Value, Filters} -> {scope, Value, Filters}
- end.
- push_scope(Scope, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=push_scope(Scope, Context) };
- push_scope(Scope, #dtl_context{ local_scopes=Scopes }=Context) ->
- Context#dtl_context{ local_scopes=[Scope|Scopes] }.
- restore_scope(Target, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=restore_scope(Target, Context) };
- restore_scope(#treewalker{ context=Target }, Context) ->
- restore_scope(Target, Context);
- restore_scope(#dtl_context{ local_scopes=Scopes }, Context) ->
- Context#dtl_context{ local_scopes=Scopes }.
- begin_scope(TreeWalker) -> begin_scope(empty_scope(), TreeWalker).
- begin_scope({Scope, Values}, TreeWalker) ->
- Id = make_ref(),
- {Id, push_scope({Id, Scope, Values}, TreeWalker)}.
- end_scope(Fun, Id, AstList, TreeWalker) ->
- close_scope(Fun, Id, AstList, TreeWalker).
- empty_scope() -> {[], []}.
- reset_block_dict(BlockDict, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=reset_block_dict(BlockDict, Context) };
- reset_block_dict(BlockDict, Context) ->
- Context#dtl_context{ block_dict=BlockDict }.
- reset_parse_trail(ParseTrail, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=reset_parse_trail(ParseTrail, Context) };
- reset_parse_trail(ParseTrail, Context) ->
- Context#dtl_context{ parse_trail=ParseTrail }.
- load_library(Lib, Context) -> load_library(none, Lib, [], Context).
- load_library(Pos, Lib, Context) -> load_library(Pos, Lib, [], Context).
- load_library(Pos, Lib, Which, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=load_library(Pos, Lib, Which, Context) };
- load_library(Pos, Lib, Which, Context) ->
- case lib_module(Lib, Context) of
- {ok, Mod} ->
- ?LOG_DEBUG(
- "~s: Load library '~s' from ~s ~p~n",
- [get_current_file(Context), Lib, Mod, Which], Context),
- Filters = read_library(Mod, filters, Which),
- Tags = read_library(Mod, tags, Which),
- Found = Filters ++ Tags,
- Missing = lists:filter(
- fun (W) -> lists:keyfind(W, 1, Found) == false end,
- Which),
- add_tags(
- Tags,
- add_filters(
- Filters,
- warn_missing(
- Missing, {Pos, Lib, Mod}, Context)));
- Error ->
- ?WARN({Pos, Error}, Context)
- end.
- add_filters(Load, #dtl_context{ filters=Filters }=Context) ->
- ?LOG_TRACE("Load filters: ~p~n", [Load], Context),
- Context#dtl_context{ filters=Load ++ Filters }.
- add_tags(Load, #dtl_context{ tags=Tags }=Context) ->
- ?LOG_TRACE("Load tags: ~p~n", [Load], Context),
- Context#dtl_context{ tags=Load ++ Tags }.
- %% shorten_filename/1 copied from Erlang/OTP lib/compiler/src/compile.erl
- shorten_filename(Name) ->
- {ok, Cwd} = file:get_cwd(),
- shorten_filename(Name, Cwd).
- shorten_filename(Name, Cwd) ->
- case lists:prefix(Cwd, Name) of
- false -> Name;
- true ->
- case lists:nthtail(length(Cwd), Name) of
- "/"++N -> N;
- N -> N
- end
- end.
- push_auto_escape(State, #treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=push_auto_escape(State, Context) };
- push_auto_escape(State, #dtl_context{ auto_escape=AutoEscape }=Context) ->
- Context#dtl_context{ auto_escape=[State|AutoEscape] }.
- pop_auto_escape(#treewalker{ context=Context }=TreeWalker) ->
- TreeWalker#treewalker{ context=pop_auto_escape(Context) };
- pop_auto_escape(#dtl_context{ auto_escape=[_|AutoEscape] }=Context)
- when length(AutoEscape) > 0 ->
- Context#dtl_context{ auto_escape=AutoEscape };
- pop_auto_escape(Context) -> Context.
- token_pos(Token) when is_tuple(Token) ->
- token_pos(tuple_to_list(Token));
- token_pos([T|Ts]) when is_tuple(T) ->
- case T of
- {R, C}=P when is_integer(R), is_integer(C) -> P;
- _ -> token_pos(tuple_to_list(T) ++ Ts)
- end;
- token_pos([T|Ts]) when is_list(T) -> token_pos(T ++ Ts);
- token_pos([_|Ts]) -> token_pos(Ts);
- token_pos([]) -> none.
- is_stripped_token_empty({string, _, S}) ->
- [] == [C || C <- S, C /= 32, C /= $\r, C /= $\n, C /= $\t];
- is_stripped_token_empty({comment, _}) -> true;
- is_stripped_token_empty({comment_tag, _, _}) -> true;
- is_stripped_token_empty(_) -> false.
- format_error({load_library, Name, Mod, Reason}) ->
- io_lib:format("Failed to load library '~p' (~p): ~p", [Name, Mod, Reason]);
- format_error({load_from, Name, Mod, Tag}) ->
- io_lib:format("'~p' not in library '~p' (~p)", [Tag, Name, Mod]);
- format_error({unknown_extension, Tag}) ->
- io_lib:format("Unhandled extension: ~p", [Tag]);
- format_error(Other) ->
- io_lib:format("## Sorry, error description for ~p not yet implemented.~n"
- "## Please report this so we can fix it.", [Other]).
- %%====================================================================
- %% Internal functions
- %%====================================================================
- to_binary_string(Arg) when is_binary(Arg) -> Arg;
- to_binary_string(Arg) when is_list(Arg) -> list_to_binary(Arg);
- to_binary_string(Arg) when is_integer(Arg) ->
- case erlang:function_exported(erlang, integer_to_binary, 1) of
- true -> erlang:integer_to_binary(Arg);
- false -> list_to_binary(integer_to_list(Arg))
- end;
- to_binary_string(Arg) when is_atom(Arg) -> atom_to_binary(Arg, latin1).
- to_list_string(Arg) when is_list(Arg) -> Arg;
- to_list_string(Arg) when is_binary(Arg) -> binary_to_list(Arg);
- to_list_string(Arg) when is_integer(Arg) -> integer_to_list(Arg);
- to_list_string(Arg) when is_atom(Arg) -> atom_to_list(Arg).
- unescape_string_literal([], Acc, noslash) ->
- lists:reverse(Acc);
- unescape_string_literal([$\\ | Rest], Acc, noslash) ->
- unescape_string_literal(Rest, Acc, slash);
- unescape_string_literal([C | Rest], Acc, noslash) ->
- unescape_string_literal(Rest, [C | Acc], noslash);
- unescape_string_literal("n" ++ Rest, Acc, slash) ->
- unescape_string_literal(Rest, [$\n | Acc], noslash);
- unescape_string_literal("r" ++ Rest, Acc, slash) ->
- unescape_string_literal(Rest, [$\r | Acc], noslash);
- unescape_string_literal("t" ++ Rest, Acc, slash) ->
- unescape_string_literal(Rest, [$\t | Acc], noslash);
- unescape_string_literal([C | Rest], Acc, slash) ->
- unescape_string_literal(Rest, [C | Acc], noslash).
- get_error_item(Report, Prefix, File, Error, DefaultModule) ->
- case compose_error_desc(Error, DefaultModule) of
- {Pos, Module, ErrorDesc} ->
- new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc);
- ErrorItem ->
- ErrorItem
- end.
- compose_error_desc({{Line, Col}=Pos, ErrorDesc}, Module)
- when is_integer(Line), is_integer(Col), is_atom(Module) ->
- {Pos, Module, ErrorDesc};
- compose_error_desc({Line, ErrorDesc}, Module)
- when is_integer(Line); Line =:= none ->
- {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({none, Module, _}=ErrorDesc, _)
- when is_atom(Module) ->
- ErrorDesc;
- compose_error_desc({_, InfoList}=ErrorDesc, _)
- when is_list(InfoList) -> ErrorDesc;
- compose_error_desc(ErrorDesc, Module) ->
- {none, Module, ErrorDesc}.
- new_error_item(Report, Prefix, File, Pos, Module, ErrorDesc) ->
- if Report ->
- io:format("~s:~s~s~s~n",
- [File, pos_info(Pos), Prefix,
- Module:format_error(ErrorDesc)]);
- true -> nop
- end,
- {File, [{Pos, Module, ErrorDesc}]}.
- 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]).
- resolve_variable1([], _VarName) -> undefined;
- resolve_variable1([Scope|Scopes], VarName) ->
- case lists:keyfind(VarName, 1, get_scope(Scope)) of
- false ->
- resolve_variable1(Scopes, VarName);
- {_, Value} -> {Value, []};
- {_, Value, Filters} when is_list(Filters) -> {Value, Filters};
- {_, Value, Filter} when is_atom(Filter) -> {Value, [{Filter, []}]};
- {_, Value, Filter} -> {Value, [Filter]}
- end.
- get_scope({_Id, Scope, _Values}) -> Scope;
- get_scope(Scope) -> Scope.
- merge_info1(1, _, _, Info) -> Info;
- merge_info1(FieldIdx, Info1, Info2, Info) ->
- Value = lists:umerge(
- lists:usort(element(FieldIdx, Info1)),
- lists:usort(element(FieldIdx, Info2))),
- merge_info1(FieldIdx - 1, Info1, Info2, setelement(FieldIdx, Info, Value)).
- close_scope(Fun, Id, AstList, TreeWalker) ->
- case merge_scopes(Id, TreeWalker) of
- {[], TreeWalker1} -> {AstList, TreeWalker1};
- {Values, TreeWalker1} ->
- {lists:foldl(
- fun ({ScopeId, ScopeValues}, AstAcc) ->
- {Pre, Target, Post} = split_ast(ScopeId, AstAcc),
- Pre ++ Fun(ScopeValues ++ Target) ++ Post
- end,
- AstList, Values),
- TreeWalker1}
- end.
- merge_scopes(Id, #treewalker{ context=Context }=TreeWalker) ->
- {Values, Scopes} = merge_scopes(Id, Context#dtl_context.local_scopes, []),
- {lists:reverse(Values),
- TreeWalker#treewalker{
- context=Context#dtl_context{
- local_scopes = Scopes
- } }}.
- merge_scopes(Id, [{Id, _Scope, []}|Scopes], Acc) -> {Acc, Scopes};
- merge_scopes(Id, [{Id, _Scope, Values}|Scopes], Acc) -> {[{Id, Values}|Acc], Scopes};
- merge_scopes(Id, [{_ScopeId, _Scope, []}|Scopes], Acc) ->
- merge_scopes(Id, Scopes, Acc);
- merge_scopes(Id, [{ScopeId, _Scope, Values}|Scopes], Acc) ->
- merge_scopes(Id, Scopes, [{ScopeId, Values}|Acc]);
- merge_scopes(Id, [_PlainScope|Scopes], Acc) ->
- merge_scopes(Id, Scopes, Acc).
- split_ast(Id, AstList) ->
- split_ast(Id, AstList, []).
- split_ast(_Split, [], {Pre, Acc}) ->
- {Pre, lists:reverse(Acc), []};
- split_ast(_Split, [], Acc) ->
- {[], lists:reverse(Acc), []};
- split_ast(Split, [Split|Rest], {Pre, Acc}) ->
- {Pre, lists:reverse(Acc), Rest};
- split_ast(Split, [Split|Rest], Acc) ->
- split_ast(end_scope, Rest, {lists:reverse(Acc), []});
- split_ast(Split, [Ast|Rest], {Pre, Acc}) ->
- split_ast(Split, Rest, {Pre, [Ast|Acc]});
- split_ast(Split, [Ast|Rest], Acc) ->
- split_ast(Split, Rest, [Ast|Acc]).
- lib_module(Name, #dtl_context{ libraries=Libs }) ->
- Mod = proplists:get_value(Name, Libs, Name),
- case code:ensure_loaded(Mod) of
- {module, Mod} ->
- case implements_behaviour(erlydtl_library, Mod) of
- true ->
- case Mod:version() of
- ?LIB_VERSION -> {ok, Mod};
- V -> {load_library, Name, Mod, {version, V}}
- end;
- false -> {load_library, Name, Mod, behaviour}
- end;
- {error, Reason} ->
- {load_library, Name, Mod, Reason}
- end.
- implements_behaviour(Behaviour, Mod) ->
- [] =:= [Behaviour] -- [B || [B] <- proplists:get_all_values(behaviour, Mod:module_info(attributes))].
- read_library(Mod, Section, Which) ->
- [{Name, lib_function(Mod, Fun)}
- || {Name, Fun} <- read_inventory(Mod, Section),
- Which =:= [] orelse lists:member(Name, Which)].
- warn_missing([], _, Context) -> Context;
- warn_missing([X|Xs], {Pos, Lib, Mod}=Info, Context) ->
- warn_missing(Xs, Info, ?WARN({Pos, {load_from, Lib, Mod, X}}, Context)).
- lib_function(_, {Mod, Fun}) ->
- lib_function(Mod, Fun);
- lib_function(Mod, Fun) ->
- %% we could check for lib function availability here.. (sanity check)
- {Mod, Fun}.
- read_inventory(Mod, Section) ->
- [case Item of
- {_Name, _Fun} -> Item;
- Fun -> {Fun, Fun}
- end || Item <- Mod:inventory(Section)].
- remove_quotes(String) ->
- remove_last_quote(remove_first_quote(String)).
- remove_first_quote([34 | Rest]) ->
- Rest;
- remove_first_quote(String) ->
- String.
- remove_last_quote(String) ->
- lists:reverse(remove_first_quote(lists:reverse(String))).
|