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