Skip to content

Commit 6df8227

Browse files
author
Stefan Hellkvist
committed
adding transformation functions
1 parent 38cc0eb commit 6df8227

File tree

1 file changed

+88
-18
lines changed

1 file changed

+88
-18
lines changed

src/template.erl

+88-18
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ replace([], Result, _Dict) ->
6767
lists:reverse(lists:flatten(Result));
6868

6969
replace("$(" ++ Rest, Result, Dict) ->
70-
{ok, VarName, RemainsAfterVariable} = read_var_name(Rest),
71-
Value = maps:get(VarName, Dict),
70+
{ok, VarName, Transform, RemainsAfterVariable} = read_var_name(Rest),
71+
Value = eval(VarName, Transform, maps:get(VarName, Dict)),
7272
replace(RemainsAfterVariable, [lists:reverse(Value) | Result], Dict);
7373

7474
replace([C | RestTemplate], Result, Dict) ->
@@ -81,13 +81,13 @@ match([], [], D, _) ->
8181
D;
8282

8383
match("$(" ++ Rest, String, D, Arg) ->
84-
{ok, VarName, RemainsAfterVariable} = read_var_name(Rest),
84+
{ok, VarName, Transform, RemainsAfterVariable} = read_var_name(Rest),
8585
case maps:get(VarName, D, undefined) of
8686
undefined -> %% if we don't have an existing binding we try all possible bindings
87-
bind(VarName, [], RemainsAfterVariable, String, D, Arg);
87+
bind({VarName, Transform}, [], RemainsAfterVariable, String, D, Arg);
8888

8989
CurrentValue -> %% if we have an existing binding we substitute the value of the variable and try to match
90-
match(lists:flatten([CurrentValue | RemainsAfterVariable]), String, D, Arg)
90+
match(lists:flatten([eval(VarName, Transform, CurrentValue) | RemainsAfterVariable]), String, D, Arg)
9191
end;
9292

9393
match([C | TemplateRest], [C | StringRest], D, Arg) ->
@@ -98,43 +98,69 @@ match(_, _, _, Arg) ->
9898

9999

100100

101-
bind(VarName, Value, [], [], D, _) ->
102-
D#{VarName => lists:reverse(Value)};
101+
bind({VarName, T}, Value, [], [], D, _) ->
102+
D#{VarName => eval(VarName, T, lists:reverse(Value))};
103103

104-
bind(VarName, _, [], String, D, _) when length(String) > 0 ->
105-
D#{VarName => String};
104+
bind({VarName, T}, _, [], String, D, _) when length(String) > 0 ->
105+
D#{VarName => eval(VarName, T, String)};
106106

107-
bind(VarName, Value, Template, [], D, Arg) when length(Template) > 0 ->
108-
NewD = D#{VarName => lists:reverse(Value)},
107+
bind({VarName, T}, Value, Template, [], D, Arg) when length(Template) > 0 ->
108+
NewD = D#{VarName => eval(VarName, T, lists:reverse(Value))},
109109
match(Template, [], NewD, Arg);
110110

111-
bind(VarName, AccValue, String, String, D, _) ->
112-
D#{VarName => lists:reverse(AccValue)};
111+
bind({VarName, T}, AccValue, String, String, D, _) ->
112+
D#{VarName => eval(VarName, T, lists:reverse(AccValue))};
113113

114-
bind(VarName, AccValue, Template, [C | StringRest] = String, D, Arg) ->
115-
NewD = D#{VarName => lists:reverse(AccValue)},
114+
bind({VarName, T}, AccValue, Template, [C | StringRest] = String, D, Arg) ->
115+
NewD = D#{VarName => eval(VarName, T, lists:reverse(AccValue))},
116116
case match(Template, String, NewD, Arg) of
117117
{error, _Reason} ->
118-
bind(VarName, [C | AccValue], Template, StringRest, D, Arg);
118+
bind({VarName, T}, [C | AccValue], Template, StringRest, D, Arg);
119119

120120
YetAnotherD ->
121121
YetAnotherD
122122
end.
123123

124124

125+
eval(_, identity, Val) ->
126+
Val;
127+
eval(VarName, Body, Val) ->
128+
FunStr = "fun (" ++ VarName ++ ") -> " ++ Body ++ " end.",
129+
{ok, Tokens, _} = erl_scan:string(FunStr),
130+
{ok, [Form]} = erl_parse:parse_exprs(Tokens),
131+
{value, Fun, _} = erl_eval:expr(Form, erl_eval:new_bindings()),
132+
Fun(Val).
133+
134+
125135

126136
%% reads out the variable name from a string where the string starts after the "$(" part of the variable
137+
%% a variable name could either be on the simple form $(NAME) or include a function body to be evaluated to substitute
138+
%% the value of the variable, such as $(NAME|lists:reverse(NAME))
127139
read_var_name(String) ->
128140
read_var_name(String, []).
129141

130142
read_var_name([], _Res) ->
131143
{error, eof};
132-
read_var_name([$) | Remain], Res) ->
133-
{ok, lists:reverse(Res), Remain};
144+
read_var_name("|" ++ Remain, Res) ->
145+
read_fun_body(Res, Remain, [], 0);
146+
read_var_name(")" ++ Remain, Res) ->
147+
{ok, lists:reverse(Res), identity, Remain};
134148
read_var_name([C | Remain], Res) ->
135149
read_var_name(Remain, [C | Res]).
136150

137151

152+
read_fun_body(_VarName, [], _Res, _PBalance) ->
153+
{error, eof};
154+
read_fun_body(VarName, "(" ++ Remain, Res, PBalance) ->
155+
read_fun_body(VarName, Remain, [$( | Res], PBalance + 1);
156+
read_fun_body(VarName, ")" ++ Remain, Res, PBalance) when PBalance > 0 ->
157+
read_fun_body(VarName, Remain, [$) | Res], PBalance - 1);
158+
read_fun_body(VarName, ")" ++ Remain, Res, 0) ->
159+
{ok, lists:reverse(VarName), lists:reverse(Res), Remain};
160+
read_fun_body(VarName, [C | Remain], Res, PBalance) ->
161+
read_fun_body(VarName, Remain, [C | Res], PBalance).
162+
163+
138164

139165
%%% EUNIT tests %%%%
140166
-ifdef(TEST).
@@ -242,4 +268,48 @@ match_with_variable_pattern_test() ->
242268
?assertEqual(["A"], maps:keys(D)),
243269
?assertEqual("$(A)", maps:get("A", D)).
244270

271+
match_with_complex_variable_test() ->
272+
D = match("$(A|lists:reverse(A))", "ab"),
273+
?assertEqual(["A"], maps:keys(D)),
274+
?assertEqual("ba", maps:get("A", D)).
275+
276+
match_palindrom_test() ->
277+
D = match("$(A)$(A|lists:reverse(A))", "olassalo"),
278+
?assertEqual(["A"], maps:keys(D)),
279+
?assertEqual("olas", maps:get("A", D)).
280+
281+
match_non_palindrom_test() ->
282+
?assertEqual({error,{no_match,{"$(A)$(A|lists:reverse(A))","abcb"}}},
283+
match("$(A)$(A|lists:reverse(A))", "abcb")).
284+
285+
match_reversed_vars_test() ->
286+
D = match("$(A)a$(A|lists:reverse(A))", "12a21"),
287+
?assertEqual("12", maps:get("A", D)).
288+
289+
match_reversed2_vars_test() ->
290+
D = match("$(A|lists:reverse(A))a$(A)", "12a21"),
291+
?assertEqual("21", maps:get("A", D)).
292+
293+
match_complex_vars_test() ->
294+
?assertEqual(#{"A" => "Stefan"},
295+
template:match("$(A)$(A|lists:reverse(string:to_upper(A)))", "StefanNAFETS")).
296+
297+
298+
%% read_var_name
299+
read_simple_var_test() ->
300+
?assertEqual({ok, "A", identity, ""}, read_var_name("A)")),
301+
?assertEqual({ok, "A", identity, "rest"}, read_var_name("A)rest")).
302+
303+
read_complex_var_test() ->
304+
?assertEqual({ok, "A", "r(A)", ""}, read_var_name("A|r(A))")),
305+
?assertEqual({ok, "A", "r(A)", "rest"}, read_var_name("A|r(A))rest")).
306+
307+
read_bad_complex_var_test() ->
308+
?assertEqual({error, eof}, read_var_name("A|r(A")).
309+
310+
%% eval test
311+
eval_test() ->
312+
?assertEqual("a", eval("A", identity, "a")),
313+
?assertEqual("cba", eval("A", "lists:reverse(A)", "abc")).
314+
245315
-endif.

0 commit comments

Comments
 (0)