Skip to content

Commit

Permalink
Basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Devin Torres committed Jun 7, 2011
0 parents commit 7dc2163
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.eunit
src/walrus_lexer.erl
src/walrus_parser.erl
15 changes: 15 additions & 0 deletions Makefile
@@ -0,0 +1,15 @@
.PHONY: all compile test dialyze clean

all: compile

compile:
@./rebar compile

test:
@./rebar eunit

dialyze: compile
@./rebar dialyze

clean:
@./rebar clean
4 changes: 4 additions & 0 deletions README.md
@@ -0,0 +1,4 @@
Walrus - An Erlang Mustache Compiler
====================================

TODO: Write the compiler :]
2 changes: 2 additions & 0 deletions ebin/.gitignore
@@ -0,0 +1,2 @@
*.app
*.beam
Binary file added rebar
Binary file not shown.
4 changes: 4 additions & 0 deletions src/walrus.app.src
@@ -0,0 +1,4 @@
{application, walrus, [
{description, "Erlang Mustache compiler"},
{vsn, "0.1.0"}
]}.
37 changes: 37 additions & 0 deletions src/walrus.erl
@@ -0,0 +1,37 @@
-module(walrus).

-export([render/2]).

-define(is_falsy(V),
(V =:= undefined orelse V =:= null orelse V =:= false orelse V =:= [])).

render(Template, Context) ->
{ok, Tokens, _} = walrus_lexer:string(Template),
{ok, ParseTree} = walrus_parser:parse(Tokens),
render(ParseTree, Context, []).

render([{text, Text} | ParseTree], Context, Acc) ->
render(ParseTree, Context, [Text | Acc]);
render([{var, Var} | ParseTree], Context, Acc) ->
Val = proplists:get_value(Var, Context),
render(ParseTree, Context, [Val | Acc]);
render([{block, Key, SubParseTree} | ParseTree], Context, Acc) ->
Val = proplists:get_value(Key, Context),
case Val of
V when ?is_falsy(V) ->
render(ParseTree, Context, Acc);
Ctx ->
Tmpl = render(SubParseTree, Ctx, []),
render(ParseTree, Context, [Tmpl | Acc])
end;
render([{inverse, Key, SubParseTree} | ParseTree], Context, Acc) ->
Val = proplists:get_value(Key, Context),
case Val of
V when ?is_falsy(V) ->
Tmpl = render(SubParseTree, Context, []),
render(ParseTree, Context, [Tmpl | Acc]);
_ ->
render(ParseTree, Context, Acc)
end;
render([], _Context, Acc) ->
lists:flatten(lists:reverse(Acc)).
21 changes: 21 additions & 0 deletions src/walrus_lexer.xrl
@@ -0,0 +1,21 @@
Definitions.

K = [a-zA-Z0-9_]+

Rules.

{{!.*}} : skip_token.
([^{}]|({[^{])|(}[^}]))+ : {token,{text,TokenLine,TokenChars}}.
{{ : {token,{'{{',TokenLine}}.
{{# : {token,{'{{#',TokenLine}}.
{{/ : {token,{'{{/',TokenLine}}.
{{\^ : {token,{'{{^',TokenLine}}.
{{{ : {token,{'{{{',TokenLine}}.
\s*{K}\s*}} : {token,{key,TokenLine,?to_key(TokenChars,TokenLen)},"}}"}.
}} : {token,{'}}',TokenLine}}.
}}} : {token,{'}}}',TokenLine}}.

Erlang code.

-define(to_key(TokenChars, TokenLen),
(list_to_atom(string:strip(string:left(TokenChars, TokenLen-2))))).
28 changes: 28 additions & 0 deletions src/walrus_parser.yrl
@@ -0,0 +1,28 @@
Nonterminals template token var block inverse.

Terminals text key '{{' '{{{' '{{#' '{{/' '{{^' '}}' '}}}'.

Rootsymbol template.

template -> token : ['$1'].
template -> token template : ['$1' | '$2'].

token -> text : {text, ?value_of('$1')}.
token -> var : '$1'.
token -> block : '$1'.
token -> inverse : '$1'.

var -> '{{' key '}}' : {var, ?value_of('$2')}.
var -> '{{{' key '}}}' : {var_unescaped, ?value_of('$2')}.

block -> '{{#' key '}}' template '{{/' key '}}'
: {block, ?keys_match(?value_of('$2'), ?value_of('$6')), '$4'}.

inverse -> '{{^' key '}}' template '{{/' key '}}'
: {inverse, ?keys_match(?value_of('$2'), ?value_of('$6')), '$4'}.

Erlang code.

-define(value_of(Token), (element(3, Token))).
-define(keys_match(K1, K2),
(if K1 =/= K2 -> throw("keys don't match"); true -> K1 end)).
30 changes: 30 additions & 0 deletions test/lexer_test.erl
@@ -0,0 +1,30 @@
-module(lexer_test).

-compile([export_all]).

basic_test() ->
Tmpl = "Hello {{name}}. Would you like a\n"
"{{#over18}}beer{{/over18}}\n"
"{{^over18}}juice box?{{/over18}}?",
Expected = {ok,[{text,1,"Hello "},
{'{{',1},
{key,1,name},
{'}}',1},
{text,1,". Would you like a\n"},
{'{{#',2},
{key,2,over18},
{'}}',2},
{text,2,"beer"},
{'{{/',2},
{key,2,over18},
{'}}',2},
{text,2,"\n"},
{'{{^',3},
{key,3,over18},
{'}}',3},
{text,3,"juice box?"},
{'{{/',3},
{key,3,over18},
{'}}',3},
{text,3,"?"}],3},
Expected = walrus_lexer:string(Tmpl).
34 changes: 34 additions & 0 deletions test/parser_test.erl
@@ -0,0 +1,34 @@
-module(parser_test).

-compile([export_all]).

basic_test() ->
Tokens = [{text,1,"Hello "},
{'{{',1},
{key,1,name},
{'}}',1},
{text,1,". Would you like a\n"},
{'{{#',2},
{key,2,over18},
{'}}',2},
{text,2,"beer"},
{'{{/',2},
{key,2,over18},
{'}}',2},
{text,2,"\n"},
{'{{^',3},
{key,3,over18},
{'}}',3},
{text,3,"juice box?"},
{'{{/',3},
{key,3,over18},
{'}}',3},
{text,3,"?"}],
Expected = {ok,[{text,"Hello "},
{var,name},
{text,". Would you like a\n"},
{block,over18,[{text,"beer"}]},
{text,"\n"},
{inverse,over18,[{text,"juice box?"}]},
{text,"?"}]},
Expected = walrus_parser:parse(Tokens).

0 comments on commit 7dc2163

Please sign in to comment.