Browse Source

initial commit

git-svn-id: http://erlydtl.googlecode.com/svn/trunk@2 a5195066-8e3e-0410-a82a-05b01b1b9875
rsaccon 17 years ago
parent
commit
f14e16d8ad

+ 2 - 0
Emakefile

@@ -0,0 +1,2 @@
+{"src/erlydtl/*", [debug_info, {d, debug}, {outdir, "ebin"}]}.
+{"src/demo/*", [debug_info, {d, debug}, {outdir, "ebin"}]}.

+ 15 - 0
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

+ 12 - 0
demo/out/test_comment.html

@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Test Comment</title>								 
+  </head>
+  <body>
+	
+	bla
+	
+  </body>
+</html>

+ 11 - 0
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

+ 11 - 0
demo/out/test_variable.html

@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Test variable</title>								 
+  </head>
+  <body>
+	foostring
+	foostring
+  </body>
+</html>

+ 11 - 0
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

+ 12 - 0
demo/templates/test_comment.html

@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Test Comment</title>								 
+  </head>
+  <body>
+	{# comment1 #}
+	bla
+	{# comment2 #}
+  </body>
+</html>

+ 3 - 0
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 %}

+ 11 - 0
demo/templates/test_variable.html

@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Test variable</title>								 
+  </head>
+  <body>
+	{{ variable }}
+	<!--{{ variable }}-->
+  </body>
+</html>

BIN
ebin/base.beam


BIN
ebin/erlydtl.beam


BIN
ebin/erlydtl_api.beam


BIN
ebin/erlydtl_demo.beam


BIN
ebin/erlydtl_parser.beam


BIN
ebin/erlydtl_scanner.beam


BIN
ebin/erlydtl_tools.beam


BIN
ebin/test_comment.beam


BIN
ebin/test_extend.beam


BIN
ebin/test_variable.beam


+ 104 - 0
src/demo/erlydtl_demo.erl

@@ -0,0 +1,104 @@
+%%%-------------------------------------------------------------------
+%%% File:      erlydtl_demo.erl
+%%% @author    Roberto Saccon <rsaccon@gmail.com> [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.

+ 175 - 0
src/erlydtl/erlydtl_api.erl

@@ -0,0 +1,175 @@
+%%%-------------------------------------------------------------------
+%%% File:      erlydtl_api.erl
+%%% @author    Roberto Saccon <rsaccon@gmail.com> [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.
+	
+	
+

+ 189 - 0
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).

+ 66 - 0
src/erlydtl/erlydtl_parser.yrl

@@ -0,0 +1,66 @@
+%%%-------------------------------------------------------------------
+%%% File:      erlydtl_parser.erl
+%%% @author    Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
+%%% @copyright 2007 Roberto Saccon, Tait Larson
+%%% @doc Template language grammar
+%%% @reference  See <a href="http://erlydtl.googlecode.com" target="_top">http://erlydtl.googlecode.com</a> 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}.

+ 210 - 0
src/erlydtl/erlydtl_scanner.erl

@@ -0,0 +1,210 @@
+%%%-------------------------------------------------------------------
+%%% File:      erlydtl_scanner.erl
+%%% @author    Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
+%%% @copyright 2007 Roberto Saccon
+%%% @doc Template language scanner
+%%% @reference  See <a href="http://erlydtl.googlecode.com" target="_top">http://erlydtl.googlecode.com</a> 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.

+ 125 - 0
src/erlydtl/erlydtl_tools.erl

@@ -0,0 +1,125 @@
+%%%-------------------------------------------------------------------
+%%% File:      erlydtl_tools.erl
+%%% @author    Roberto Saccon <rsaccon@gmail.com> [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].