diff --git a/Emakefile b/Emakefile new file mode 100755 index 0000000..c41e4eb --- /dev/null +++ b/Emakefile @@ -0,0 +1,2 @@ +{"src/erlydtl/*", [debug_info, {d, debug}, {outdir, "ebin"}]}. +{"src/demo/*", [debug_info, {d, debug}, {outdir, "ebin"}]}. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..acc2a9d --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +ERL=/Users/rsaccon/R11B/start.sh +#ERL=/usr/local/erlware/bin/erl +ERL=erl +NODENAME=skast + + +all: + $(ERL) -make + +run: + $(ERL) -pa `pwd`/ebin + +clean: + rm -fv ebin/* + rm -fv erl_crash.dump \ No newline at end of file diff --git a/demo/out/test_comment.html b/demo/out/test_comment.html new file mode 100644 index 0000000..7c69fba --- /dev/null +++ b/demo/out/test_comment.html @@ -0,0 +1,12 @@ + + + + + Test Comment + + + + bla + + + \ No newline at end of file diff --git a/demo/out/test_extend.html b/demo/out/test_extend.html new file mode 100644 index 0000000..2ad414d --- /dev/null +++ b/demo/out/test_extend.html @@ -0,0 +1,11 @@ +blastring + +base template + +replacing the base title + +more of base template + +replacing the base content + +end of base template \ No newline at end of file diff --git a/demo/out/test_variable.html b/demo/out/test_variable.html new file mode 100644 index 0000000..ae96900 --- /dev/null +++ b/demo/out/test_variable.html @@ -0,0 +1,11 @@ + + + + + Test variable + + + foostring + foostring + + \ No newline at end of file diff --git a/demo/templates/base.html b/demo/templates/base.html new file mode 100755 index 0000000..65c3fc1 --- /dev/null +++ b/demo/templates/base.html @@ -0,0 +1,11 @@ +{{ variable }} + +base template + +{% block title %}base title{% endblock %} + +more of base template + +{% block content %}base content{% endblock %} + +end of base template \ No newline at end of file diff --git a/demo/templates/test_comment.html b/demo/templates/test_comment.html new file mode 100755 index 0000000..925793b --- /dev/null +++ b/demo/templates/test_comment.html @@ -0,0 +1,12 @@ + + + + + Test Comment + + + {# comment1 #} + bla + {# comment2 #} + + \ No newline at end of file diff --git a/demo/templates/test_extend.html b/demo/templates/test_extend.html new file mode 100755 index 0000000..9954bc3 --- /dev/null +++ b/demo/templates/test_extend.html @@ -0,0 +1,3 @@ +{% extends base.html %} +{% block title %}replacing the base title{% endblock %} +{% block content %}replacing the base content{% endblock %} \ No newline at end of file diff --git a/demo/templates/test_variable.html b/demo/templates/test_variable.html new file mode 100755 index 0000000..e83f310 --- /dev/null +++ b/demo/templates/test_variable.html @@ -0,0 +1,11 @@ + + + + + Test variable + + + {{ variable }} + + + \ No newline at end of file diff --git a/ebin/base.beam b/ebin/base.beam new file mode 100644 index 0000000..01d6270 Binary files /dev/null and b/ebin/base.beam differ diff --git a/ebin/erlydtl.beam b/ebin/erlydtl.beam new file mode 100644 index 0000000..14e2dd0 Binary files /dev/null and b/ebin/erlydtl.beam differ diff --git a/ebin/erlydtl_api.beam b/ebin/erlydtl_api.beam new file mode 100644 index 0000000..7d0d513 Binary files /dev/null and b/ebin/erlydtl_api.beam differ diff --git a/ebin/erlydtl_demo.beam b/ebin/erlydtl_demo.beam new file mode 100644 index 0000000..00c1a1c Binary files /dev/null and b/ebin/erlydtl_demo.beam differ diff --git a/ebin/erlydtl_parser.beam b/ebin/erlydtl_parser.beam new file mode 100644 index 0000000..5e51986 Binary files /dev/null and b/ebin/erlydtl_parser.beam differ diff --git a/ebin/erlydtl_scanner.beam b/ebin/erlydtl_scanner.beam new file mode 100644 index 0000000..e4895d3 Binary files /dev/null and b/ebin/erlydtl_scanner.beam differ diff --git a/ebin/erlydtl_tools.beam b/ebin/erlydtl_tools.beam new file mode 100644 index 0000000..52a9f20 Binary files /dev/null and b/ebin/erlydtl_tools.beam differ diff --git a/ebin/test_comment.beam b/ebin/test_comment.beam new file mode 100644 index 0000000..9c39cc8 Binary files /dev/null and b/ebin/test_comment.beam differ diff --git a/ebin/test_extend.beam b/ebin/test_extend.beam new file mode 100644 index 0000000..31f2fd7 Binary files /dev/null and b/ebin/test_extend.beam differ diff --git a/ebin/test_variable.beam b/ebin/test_variable.beam new file mode 100644 index 0000000..47c2f7a Binary files /dev/null and b/ebin/test_variable.beam differ diff --git a/src/demo/erlydtl_demo.erl b/src/demo/erlydtl_demo.erl new file mode 100644 index 0000000..a3806a8 --- /dev/null +++ b/src/demo/erlydtl_demo.erl @@ -0,0 +1,104 @@ +%%%------------------------------------------------------------------- +%%% File: erlydtl_demo.erl +%%% @author Roberto Saccon [http://rsaccon.com] +%%% @copyright 2007 Roberto Saccon +%%% @doc +%%% +%%% @end +%%% +%%% The MIT License +%%% +%%% Copyright (c) 2007 Roberto Saccon +%%% +%%% 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-11-17 by Roberto Saccon +%%%------------------------------------------------------------------- +-module(erlydtl_demo). +-author('rsaccon@gmail.com'). + +%% API +-export([compile_templates/0, + render_html/0]). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% @spec () -> any() +%% @doc compiles the templates to beam files +%% @end +%%-------------------------------------------------------------------- +compile_templates() -> + DocRoot = filename:join([filename:dirname(code:which(?MODULE)),"..", "demo", "templates"]), + filelib:fold_files(DocRoot, + "\.", + true, + fun(Path, _Acc) -> + Name = filename:rootname(filename:basename(Path)), + erlydtl_api:compile(Path, Name, Name, DocRoot) + end, + []). + + +%%-------------------------------------------------------------------- +%% @spec () -> any() +%% @doc renders the templete to a file +%% @end +%%-------------------------------------------------------------------- +render_html() -> + OutDir = filename:join([filename:dirname(code:which(?MODULE)),"..", "demo", "out"]), + render(OutDir, test_variable, ".html", "foostring"), + render(OutDir, test_extend, ".html", "blastring"), + render(OutDir, test_comment, ".html"). + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +render(OutDir, Name, Ext, Var) -> + case catch Name:Name(Var) of + {'EXIT', Reason} -> + io:format("TRACE ~p:~p ~p: rendering failure: ~n",[?MODULE, ?LINE, Reason]); + Val -> + case file:open(filename:join([OutDir, lists:concat([Name, Ext])]), [write]) of + {ok, IoDev} -> + file:write(IoDev, Val), + file:close(IoDev), + io:format("TRACE ~p:~p ~p: success~n",[?MODULE, ?LINE, Name]); + _ -> + io:format("TRACE ~p:~p ~p: file write failure~n",[?MODULE, ?LINE, Name]) + end + end. + +render(OutDir, Name, Ext) -> + case catch Name:Name() of + {'EXIT', Reason} -> + io:format("TRACE ~p:~p ~p: rendering failure: ~n",[?MODULE, ?LINE, Reason]); + Val -> + case file:open(filename:join([OutDir, lists:concat([Name, Ext])]), [write]) of + {ok, IoDev} -> + file:write(IoDev, Val), + file:close(IoDev), + io:format("TRACE ~p:~p ~p: success~n",[?MODULE, ?LINE, Name]); + _ -> + io:format("TRACE ~p:~p ~p: file write failure~n",[?MODULE, ?LINE, Name]) + end + end. diff --git a/src/erlydtl/erlydtl_api.erl b/src/erlydtl/erlydtl_api.erl new file mode 100755 index 0000000..8c0fc6e --- /dev/null +++ b/src/erlydtl/erlydtl_api.erl @@ -0,0 +1,175 @@ +%%%------------------------------------------------------------------- +%%% File: erlydtl_api.erl +%%% @author Roberto Saccon [http://rsaccon.com] +%%% @copyright 2007 Roberto Saccon +%%% @doc +%%% API for compiling ErlyDTL templeates +%%% @end +%%% +%%% The MIT License +%%% +%%% Copyright (c) 2007 Roberto Saccon +%%% +%%% 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-11-17 by Roberto Saccon +%%%------------------------------------------------------------------- +-module(erlydtl_api). +-author('rsaccon@gmail.com'). + +%% API +-export([compile/4]). + + +%%-------------------------------------------------------------------- +%% @spec (File:string(), ModuleName:string(), FunctionName:atom(), DocRoot:string()) -> +%% {Ok::atom, Ast::tuple() | {Error::atom(), Msg:string()} +%% @doc compiles a template to a beam file +%% @end +%%-------------------------------------------------------------------- +compile(File, ModuleName, FunctionName, DocRoot) -> + case parse(File) of + {ok, Ast} -> + RelDir = rel_dir(filename:dirname(File), DocRoot), + compile_reload(Ast, ModuleName, FunctionName, RelDir); + {error, Msg} = Err -> + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, File ++ " Parser failure:"]), + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, Msg]), + Err + end. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +rel_dir(Dir, DocRoot) when Dir =:= DocRoot -> + DocRoot; +rel_dir(Dir, DocRoot) -> + RelFile = string:substr(Dir, length(DocRoot)+2), + filename:join([DocRoot, RelFile]). + + +parse(File) -> + case file:read_file(File) of + {ok, B} -> + case erlydtl_scanner:scan(binary_to_list(B)) of + {ok, Tokens} -> + erlydtl_parser:parse(Tokens); + Err -> + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, File ++ " Scanner failure:"]), + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, Err]), + Err + end; + Err -> + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, "File read error"]), + Err + end. + + +compile_reload([H | T], ModuleName, FunctionName, RelDir) -> + {List, Args} = case transl(H, T, [], [], RelDir) of + {regular, List0, Args0} -> + {[inplace_block(X) || X <- List0], Args0}; + {inherited, List0, Arg0} -> + {List0, Arg0} + end, + Args2 = lists:reverse([{var, 1, Val} || {Val, _} <- Args]), + Cons = list_fold(lists:reverse(List)), + Ast2 = {function, 1, list_to_atom(FunctionName), length(Args2), + [{clause, 1, Args2, [], [Cons]}]}, + Ac = erlydtl_tools:create_module(Ast2 , ModuleName), + case compile:forms(Ac) of + {ok, Module, Bin} -> + case erlydtl_tools:reload(Module, Bin) of + ok -> + erlydtl_tools:write_beam(Module, Bin, "ebin"); + _ -> + {error, "reload failed"} + end; + _ -> + {error, "compilation failed"} + end. + + +list_fold([E]) -> + E; +list_fold([E1, E2]) -> + {cons, 1, E2, E1}; +list_fold([E1, E2 | Tail]) -> + lists:foldl(fun(X, T) -> + {cons, 1, X, T} + end, {cons, 1, E2, E1}, Tail). + + +transl(nil, [{extends, _, Name}], Out, Args, RelDir) -> + case parse(filename:join([RelDir, Name])) of + {ok, ParentAst} -> + [H|T]=ParentAst, + {_, List, Args1} = transl(H, T, [], [], RelDir), + {inherited, [replace_block(X, Out) || X <- List], Args1}; + {error, Msg} -> + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, Msg]), + io:format("TRACE ~p:~p Parent Parser failure: ~p~n",[?MODULE, ?LINE, Name]), + {regular, Out, Args} + end; + +transl(nil, [{var, L, Val}], Out, Args, _) -> + case lists:keysearch(Val, 2, Args) of + false -> + Key = list_to_atom(lists:concat(["A", length(Args) + 1])), + {regular, [{var, L, Key} | Out], [{Key, Val} | Args]}; + {value, {Key, _}} -> + {regular, [{var, L, Key} | Out], Args} + end; + +transl(nil, [Token], Out, Args, _) -> + {regular, [Token | Out], Args}; + +transl([H | T], [{var, L, Val}], Out, Args, DocRoot) -> + case lists:keysearch(Val, 2, Args) of + false -> + Key = list_to_atom(lists:concat(["A", length(Args) + 1])), + transl(H, T, [{var, L, Key} | Out], [{Key, Val} | Args], DocRoot); + {value, {Key, _}} -> + transl(H, T, [{var, L, Key} | Out], Args, DocRoot) + end; + +transl([H | T], [Token], Out, Args, DocRoot) -> + transl(H, T, [Token | Out], Args, DocRoot). + + +replace_block({block, Name, [nil, Str1]}, List) -> + case lists:keysearch(Name, 2, List) of + false -> + Str1; + {value, {_, _, [nil, Str2]}} -> + Str2 + end; +replace_block(Other, _) -> + Other. + + +inplace_block({block, _, [nil, Str]}) -> + Str; +inplace_block(Other) -> + Other. + + + diff --git a/src/erlydtl/erlydtl_parser.erl b/src/erlydtl/erlydtl_parser.erl new file mode 100644 index 0000000..1df8f70 --- /dev/null +++ b/src/erlydtl/erlydtl_parser.erl @@ -0,0 +1,189 @@ +-module(erlydtl_parser). +-export([parse/1, parse_and_scan/1, format_error/1]). +-file("src/erlydtl/erlydtl_parser.yrl", 63). + +block({_, _, [Name]}, Content) -> + {block, list_to_atom(Name), Content}. +-file("/Users/rsaccon/R11B/erlang/lib/parsetools-1.4.1.1/include/yeccpre.hrl", 0). +%% ``The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved via the world wide web at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +%% AB. All Rights Reserved.'' +%% +%% $Id $ +%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% The parser generator will insert appropriate declarations before this line.% + +parse(Tokens) -> + yeccpars0(Tokens, false). + +parse_and_scan({F, A}) -> % Fun or {M, F} + yeccpars0([], {F, A}); +parse_and_scan({M, F, A}) -> + yeccpars0([], {{M, F}, A}). + +format_error(Message) -> + case io_lib:deep_char_list(Message) of + true -> + Message; + _ -> + io_lib:write(Message) + end. + +% To be used in grammar files to throw an error message to the parser +% toplevel. Doesn't have to be exported! +-compile({nowarn_unused_function,{return_error,2}}). +return_error(Line, Message) -> + throw({error, {Line, ?MODULE, Message}}). + +yeccpars0(Tokens, MFA) -> + try yeccpars1(Tokens, MFA, 0, [], []) + catch + throw: {error, {_Line, ?MODULE, _M}} = Error -> + Error % probably from return_error/1 + end. + +% Don't change yeccpars1/6 too much, it is called recursively by yeccpars2/8! +yeccpars1([Token | Tokens], Tokenizer, State, States, Vstack) -> + yeccpars2(State, element(1, Token), States, Vstack, Token, Tokens, + Tokenizer); +yeccpars1([], {F, A}, State, States, Vstack) -> + case apply(F, A) of + {ok, Tokens, _Endline} -> + yeccpars1(Tokens, {F, A}, State, States, Vstack); + {eof, _Endline} -> + yeccpars1([], false, State, States, Vstack); + {error, Descriptor, _Endline} -> + {error, Descriptor} + end; +yeccpars1([], false, State, States, Vstack) -> + yeccpars2(State, '$end', States, Vstack, {'$end', 999999}, [], false). + +% For internal use only. +yeccerror(Token) -> + {error, + {element(2, Token), ?MODULE, + ["syntax error before: ", yecctoken2string(Token)]}}. + +yecctoken2string({atom, _, A}) -> io_lib:write(A); +yecctoken2string({integer,_,N}) -> io_lib:write(N); +yecctoken2string({float,_,F}) -> io_lib:write(F); +yecctoken2string({char,_,C}) -> io_lib:write_char(C); +yecctoken2string({var,_,V}) -> io_lib:format('~s', [V]); +yecctoken2string({string,_,S}) -> io_lib:write_string(S); +yecctoken2string({reserved_symbol, _, A}) -> io_lib:format('~w', [A]); +yecctoken2string({_Cat, _, Val}) -> io_lib:format('~w', [Val]); +yecctoken2string({'dot', _}) -> io_lib:format('~w', ['.']); +yecctoken2string({'$end', _}) -> + []; +yecctoken2string({Other, _}) when is_atom(Other) -> + io_lib:format('~w', [Other]); +yecctoken2string(Other) -> + io_lib:write(Other). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + +-file("src/erlydtl/erlydtl_parser.erl", 100). + +yeccpars2(0, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + __NewStack = yeccpars2_0_(__Stack), + yeccpars2(1, __Cat, [0 | __Ss], __NewStack, __T, __Ts, __Tzr); +yeccpars2(1, '$end', _, __Stack, _, _, _) -> + {ok, hd(__Stack)}; +yeccpars2(1, block, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 3, [1 | __Ss], [__T | __Stack]); +yeccpars2(1, extends, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 4, [1 | __Ss], [__T | __Stack]); +yeccpars2(1, string, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 5, [1 | __Ss], [__T | __Stack]); +yeccpars2(1, var, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 6, [1 | __Ss], [__T | __Stack]); +yeccpars2(1, _, _, _, __T, _, _) -> + yeccerror(__T); +yeccpars2(2, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + __NewStack = yeccpars2_2_(__Stack), + __Nss = lists:nthtail(1, __Ss), + yeccpars2(yeccgoto('Elements', hd(__Nss)), __Cat, __Nss, __NewStack, __T, __Ts, __Tzr); +yeccpars2(3, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + __NewStack = yeccpars2_3_(__Stack), + yeccpars2(7, __Cat, [3 | __Ss], __NewStack, __T, __Ts, __Tzr); +yeccpars2(4, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars2(yeccgoto('Element', hd(__Ss)), __Cat, __Ss, __Stack, __T, __Ts, __Tzr); +yeccpars2(5, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars2(yeccgoto('Element', hd(__Ss)), __Cat, __Ss, __Stack, __T, __Ts, __Tzr); +yeccpars2(6, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars2(yeccgoto('Element', hd(__Ss)), __Cat, __Ss, __Stack, __T, __Ts, __Tzr); +yeccpars2(7, block, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 3, [7 | __Ss], [__T | __Stack]); +yeccpars2(7, endblock, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 8, [7 | __Ss], [__T | __Stack]); +yeccpars2(7, extends, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 4, [7 | __Ss], [__T | __Stack]); +yeccpars2(7, string, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 5, [7 | __Ss], [__T | __Stack]); +yeccpars2(7, var, __Ss, __Stack, __T, __Ts, __Tzr) -> + yeccpars1(__Ts, __Tzr, 6, [7 | __Ss], [__T | __Stack]); +yeccpars2(7, _, _, _, __T, _, _) -> + yeccerror(__T); +yeccpars2(8, __Cat, __Ss, __Stack, __T, __Ts, __Tzr) -> + __NewStack = yeccpars2_8_(__Stack), + __Nss = lists:nthtail(2, __Ss), + yeccpars2(yeccgoto('Element', hd(__Nss)), __Cat, __Nss, __NewStack, __T, __Ts, __Tzr); +yeccpars2(__Other, _, _, _, _, _, _) -> + erlang:error({yecc_bug,"1.1",{missing_state_in_action_table, __Other}}). + +yeccgoto('Element', 1) -> + 2; +yeccgoto('Element', 7) -> + 2; +yeccgoto('Elements', 0) -> + 1; +yeccgoto('Elements', 3) -> + 7; +yeccgoto(__Symbol, __State) -> + erlang:error({yecc_bug,"1.1",{__Symbol, __State, missing_in_goto_table}}). + +-compile({inline,{yeccpars2_0_,1}}). +-file("src/erlydtl/erlydtl_parser.yrl", 51). +yeccpars2_0_(__Stack) -> + [begin + nil + end | __Stack]. + +-compile({inline,{yeccpars2_2_,1}}). +-file("src/erlydtl/erlydtl_parser.yrl", 52). +yeccpars2_2_([__2,__1 | __Stack]) -> + [begin + [ __1 , __2 ] + end | __Stack]. + +-compile({inline,{yeccpars2_3_,1}}). +-file("src/erlydtl/erlydtl_parser.yrl", 51). +yeccpars2_3_(__Stack) -> + [begin + nil + end | __Stack]. + +-compile({inline,{yeccpars2_8_,1}}). +-file("src/erlydtl/erlydtl_parser.yrl", 57). +yeccpars2_8_([__3,__2,__1 | __Stack]) -> + [begin + block ( __1 , __2 ) + end | __Stack]. + + +-file("src/erlydtl/erlydtl_parser.yrl", 66). diff --git a/src/erlydtl/erlydtl_parser.yrl b/src/erlydtl/erlydtl_parser.yrl new file mode 100755 index 0000000..fb7f165 --- /dev/null +++ b/src/erlydtl/erlydtl_parser.yrl @@ -0,0 +1,66 @@ +%%%------------------------------------------------------------------- +%%% File: erlydtl_parser.erl +%%% @author Roberto Saccon [http://rsaccon.com] +%%% @copyright 2007 Roberto Saccon, Tait Larson +%%% @doc Template language grammar +%%% @reference See http://erlydtl.googlecode.com for more information +%%% @end +%%% +%%% The MIT License +%%% +%%% Copyright (c) 2007 Roberto Saccon +%%% +%%% 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-11-11 by Roberto Saccon +%%%------------------------------------------------------------------- + + +Nonterminals + Elements + Element. + +Terminals + var + extends + block + endblock + string. + +Rootsymbol + Elements. + + +%% ------------------------------------------------------------------- +%% Rules +%% ------------------------------------------------------------------- + +Elements -> '$empty' : nil. +Elements -> Elements Element : ['$1', '$2']. + +Element -> string : '$1'. +Element -> var : '$1'. +Element -> extends : '$1'. +Element -> block Elements endblock : block('$1', '$2'). + + +Erlang code. + +block({_, _, [Name]}, Content) -> + {block, list_to_atom(Name), Content}. \ No newline at end of file diff --git a/src/erlydtl/erlydtl_scanner.erl b/src/erlydtl/erlydtl_scanner.erl new file mode 100755 index 0000000..0b0b6c7 --- /dev/null +++ b/src/erlydtl/erlydtl_scanner.erl @@ -0,0 +1,210 @@ +%%%------------------------------------------------------------------- +%%% File: erlydtl_scanner.erl +%%% @author Roberto Saccon [http://rsaccon.com] +%%% @copyright 2007 Roberto Saccon +%%% @doc Template language scanner +%%% @reference See http://erlydtl.googlecode.com for more information +%%% @end +%%% +%%% The MIT License +%%% +%%% Copyright (c) 2007 Roberto Saccon +%%% +%%% 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-11-11 by Roberto Saccon +%%%------------------------------------------------------------------- +-module(erlydtl_scanner). +-author('rsaccon@gmail.com'). + +%% API +-export([scan/1]). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% @spec scan(T::template()) -> {ok, S::tokens()} | {error, Reason} +%% @type template() = string() | binary(). Template to parse +%% @type tokens() = [tuple()]. +%% @doc Scan the template string T and return the a token list or +%% an error. +%% @end +%%-------------------------------------------------------------------- + +scan(Template) -> + scan(Template, [], 1). + +scan([], Scanned, _Line) -> + Tokens = fold_strings(lists:reverse(Scanned), [], []), + {ok, Tokens}; + +scan([$<, $\!, $-, $-, ${, ${ | T], Scanned, Line) -> + Rules = [until(fun is_var_end/1), + until(fun is_html_comment_end/1)], + Scan = scan2(Rules), + case Scan(T) of + {ok, Token, LinesScanned, Rest} -> + scan(Rest, [{var, Line, Token} | Scanned], Line + LinesScanned); + {error, Reason} -> + {error, {var, Line, Reason}} + end; + +scan([${, ${ | T], Scanned, Line) -> + Rules = [until(fun is_var_end/1)], + Scan = scan2(Rules), + case Scan(T) of + {ok, Token, LinesScanned, Rest} -> + scan(Rest, [{var, Line, Token} | Scanned], Line + LinesScanned); + {error, Reason} -> + {error, {var, Line, Reason}} + end; + +scan([$<, $\!, $-, $-, ${, $\% | T], Scanned, Line) -> + Rules = [until(fun is_tag_end/1), + until(fun is_html_comment_end/1)], + Scan = scan2(Rules), + case Scan(T) of + {ok, Token, LinesScanned, Rest} -> + scan(Rest, [{tag, Line, Token} | Scanned], Line + LinesScanned); + {error, Reason} -> + {error, {tag, Line, Reason}} + end; + +scan([${, $\% | T], Scanned, Line) -> + Rules = [until(fun is_tag_end/1)], + Scan = scan2(Rules), + case Scan(T) of + {ok, Token, LinesScanned, Rest} -> + scan(Rest, [{tag, Line, Token} | Scanned], Line + LinesScanned); + {error, Reason} -> + {error, {tag, Line, Reason}} + end; + +scan([${, $# | T], Scanned, Line) -> + Rules = [until(fun is_comment_end/1)], + Scan = scan2(Rules), + case Scan(T) of + {ok, _Token, LinesScanned, Rest} -> + scan(Rest, Scanned, Line + LinesScanned); + {error, Reason} -> + {error, {var, Line, Reason}} + end; + +scan([H | T], Scanned, Line) when [H] == "\r" andalso hd(T) == "\n" -> + scan(tl(T), ["\r\n" | Scanned], Line+1); + +scan([H | T], Scanned, Line) when [H] == "\r" orelse [H] == "\n" -> + scan(T, [H | Scanned], Line+1); + +scan([H | T], Scanned, Line) -> + scan(T, [H | Scanned], Line). + + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +scan2(Rules) -> + fun(Tmpl) -> + scan2(Rules, Tmpl, [], 0) + end. + + +scan2([], Tmpl, SoFar, Line) -> + {ok, lists:reverse(SoFar), Line, Tmpl}; + +scan2([Rule | T], Tmpl, SoFar, Line) -> + case Rule(Tmpl) of + {error, Reason} -> + {error, Reason}; + {ok, Rest, LinesScanned} -> + scan2(T, Rest, SoFar, Line + LinesScanned); + {ok, Tok, LinesScanned, Rest} -> + scan2(T, Rest, [Tok | SoFar], Line + LinesScanned) + end. + +fold_strings([], Folded, []) -> + lists:reverse(Folded); + +fold_strings([], Folded, Acc) -> + S = {string, 1, lists:reverse(Acc)}, + lists:reverse([S | Folded]); + +fold_strings([H | T], Folded, []) when is_tuple(H) -> + fold_strings(T, [translate_token(H) | Folded], []); + +fold_strings([H | T], Folded, Acc) when is_tuple(H) -> + S = {string, 1, lists:reverse(Acc)}, + fold_strings(T, [translate_token(H), S | Folded], []); + +fold_strings([H | T], Folded, Acc) -> + fold_strings(T, Folded, [H | Acc]). + + +translate_token({var, Line, [[S] | _]}) -> + {var, Line, S}; + +translate_token({tag, Line, [[H | T] | _]}) -> + {list_to_atom(H), Line, T}; + +translate_token(Token) -> + io:format("TRACE ~p:~p unrecognized token: ~p~n",[?MODULE, ?LINE, Token]), + Token. + + +until(P) -> + fun (Tmpl) -> + until(P, Tmpl, 0, []) + end. + +until(_P, [], _Line, _Scanned) -> + {error, end_not_found}; + +until(P, [H|T], Line, Scanned) when [H]=="\r" andalso hd(T)=="\n" -> + until(P, tl(T), Line+1, Scanned); + +until(P, [H|T], Line, Scanned) when [H]=="\n" orelse [H]== "\r" -> + until(P, T, Line+1, Scanned); + +until(P, [H|T]=Tmpl, Line, Scanned) -> + case P(Tmpl) of + {true, R} -> + Scanned1 = string:strip(lists:reverse(Scanned)), + Scanned2 = string:tokens(Scanned1, " "), + {ok, Scanned2, Line, R}; + _ -> + until(P, T, Line, [H | Scanned]) + end. + + +is_var_end([$}, $} | T]) -> {true, T}; +is_var_end(_) -> false. + + +is_tag_end([$\%, $} | T]) -> {true, T}; +is_tag_end(_) -> false. + + +is_comment_end([$#, $} | T]) -> {true, T}; +is_comment_end(_) -> false. + + +is_html_comment_end([$-, $-, $> | T]) -> {true, T}; +is_html_comment_end(_) -> false. diff --git a/src/erlydtl/erlydtl_tools.erl b/src/erlydtl/erlydtl_tools.erl new file mode 100644 index 0000000..8b5f47e --- /dev/null +++ b/src/erlydtl/erlydtl_tools.erl @@ -0,0 +1,125 @@ +%%%------------------------------------------------------------------- +%%% File: erlydtl_tools.erl +%%% @author Roberto Saccon [http://rsaccon.com] +%%% @copyright 2007 Roberto Saccon +%%% @doc +%%% Utility for creating parser based on grammar +%%% @end +%%% +%%% The MIT License +%%% +%%% Copyright (c) 2007 Roberto Saccon +%%% +%%% 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-11-17 by Roberto Saccon +%%%------------------------------------------------------------------- +-module(erlydtl_tools). +-author('rsaccon@gmail.com'). + +%% API +-export([create_parser/0, create_module/2, reload/2, write_beam/3]). + +%% -------------------------------------------------------------------- +%% Definitions +%% -------------------------------------------------------------------- +-ifdef(debug). +-define(PRINT_ERR_WARNS, [report_warnings, report_errors]). +-else. +-define(PRINT_ERR_WARNS, []). +-endif. + + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% @spec +%% @doc +%% @end +%%-------------------------------------------------------------------- +create_parser() -> + create_parser("src/erlydtl/erlydtl_parser", "ebin"). + + +%%-------------------------------------------------------------------- +%% @spec (Ast::tuple(), Name::atom()) -> any() +%% @doc Translate Abstract Syntax Tree to Abstract Module Code +%% @end +%%-------------------------------------------------------------------- +create_module(Ast, ModuleName) when is_list(Ast) -> + Tail = lists:reverse([{eof, 1} | lists:reverse(lists:flatten(Ast))]), + add_module_header(Tail, ModuleName); +create_module(Ast, ModuleName) -> + Tail = [Ast, {eof, 1}], + add_module_header(Tail, ModuleName). + + + +%%-------------------------------------------------------------------- +%% @spec (ModuleName::string(), Bin,::binary()) -> Ok::atom() | Error::atom() +%% @doc reloads byte code +%% @end +%%-------------------------------------------------------------------- +reload(Module, Bin) -> + code:purge(Module), + SrcName = atom_to_list(Module) ++ ".erl", + case code:load_binary(Module, SrcName, Bin) of + {module, _} -> ok; + _ -> error + end. + + +%%-------------------------------------------------------------------- +%% @spec (ModuleName::string(), Bin,::binary(), Dir::string()) -> any() +%% @doc writes byte code to beam file +%% @end +%%-------------------------------------------------------------------- +write_beam(ModuleName, Bin, Dir) -> + File = filename:join([Dir, atom_to_list(ModuleName) ++ ".beam"]), + file:write_file(File, Bin). + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +create_parser(Path, Outdir) -> + case yecc:file(Path) of + {ok, _} -> + compile_reload(Path, Outdir); + Err -> + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, Path ++ ": yecc failed"]), + Err + end. + +compile_reload(Path, Outdir) -> + case compile:file(Path, ?PRINT_ERR_WARNS ++ [{outdir, Outdir}]) of + {ok, Bin} -> + code:purge(Bin), + code:load_file(Bin); + Err -> + io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, Path ++ ": compilation failed"]), + Err + end. + + +add_module_header(Tail, ModuleName) -> + Tail2 = [{attribute, 1, compile, export_all} | Tail], + [{attribute, 1, module, list_to_atom(ModuleName)} | Tail2]. \ No newline at end of file