From 0a301a53947a92dc0f14081cad94a02958466a29 Mon Sep 17 00:00:00 2001 From: Paul Mineiro Date: Wed, 10 Jun 2009 21:43:11 +0000 Subject: [PATCH] --- AUTHORS | 1 + ChangeLog | 2 + Makefile.am.local | 1 + NEWS | 0 README | 1 + bootstrap | 17 +++ configure.ac.local | 2 + doc/Makefile.am.local | 1 + doc/overview.edoc | 4 + fw-pkgin/Makefile.am.local | 1 + fw-pkgin/config | 61 +++++++++ fw-pkgin/post-install | 9 ++ fw-pkgin/post-remove | 9 ++ fw-pkgin/pre-install | 9 ++ fw-pkgin/pre-remove | 9 ++ fw-pkgin/start | 10 ++ fw-pkgin/stop | 10 ++ src/Makefile.am.local | 15 ++ src/lyet.erl | 271 +++++++++++++++++++++++++++++++++++++ tests/Makefile.am.local | 2 + tests/testlyet.erl | 59 ++++++++ 21 files changed, 494 insertions(+) create mode 100644 AUTHORS create mode 100644 ChangeLog create mode 100644 Makefile.am.local create mode 100644 NEWS create mode 100644 README create mode 100755 bootstrap create mode 100644 configure.ac.local create mode 100644 doc/Makefile.am.local create mode 100644 doc/overview.edoc create mode 100644 fw-pkgin/Makefile.am.local create mode 100644 fw-pkgin/config create mode 100755 fw-pkgin/post-install create mode 100755 fw-pkgin/post-remove create mode 100755 fw-pkgin/pre-install create mode 100755 fw-pkgin/pre-remove create mode 100755 fw-pkgin/start create mode 100755 fw-pkgin/stop create mode 100644 src/Makefile.am.local create mode 100644 src/lyet.erl create mode 100644 tests/Makefile.am.local create mode 100644 tests/testlyet.erl diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9676d51 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Paul Mineiro diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..100451f --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2 @@ +Version 0.0.0 + * Initial version. diff --git a/Makefile.am.local b/Makefile.am.local new file mode 100644 index 0000000..c6d5812 --- /dev/null +++ b/Makefile.am.local @@ -0,0 +1 @@ +# put whatever (auto)make commands here, they will be included from Makefile.am diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..a8bf9d5 --- /dev/null +++ b/README @@ -0,0 +1 @@ +Erlang parse transform to provide let like functionality. diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..4f6f5e4 --- /dev/null +++ b/bootstrap @@ -0,0 +1,17 @@ +#! /bin/sh + +if test -d fw/bin + then + PATH="`pwd`/fw/bin:$PATH" + export PATH + fi + +fwb=`which fw-bootstrap` + +if test -z "$fwb" + then + echo "bootstrap: fatal: fw-bootstrap not installed or not in PATH" 1>&2 + exit 1 + fi + +"$fwb" --fw_version "0.1.34" --name lyt --template erlang --revision cvs "$@" diff --git a/configure.ac.local b/configure.ac.local new file mode 100644 index 0000000..f5fb31a --- /dev/null +++ b/configure.ac.local @@ -0,0 +1,2 @@ +dnl -- include additional autoconf commands here +dnl -- do not include AC_OUTPUT, this is called for you diff --git a/doc/Makefile.am.local b/doc/Makefile.am.local new file mode 100644 index 0000000..c6d5812 --- /dev/null +++ b/doc/Makefile.am.local @@ -0,0 +1 @@ +# put whatever (auto)make commands here, they will be included from Makefile.am diff --git a/doc/overview.edoc b/doc/overview.edoc new file mode 100644 index 0000000..d244bc0 --- /dev/null +++ b/doc/overview.edoc @@ -0,0 +1,4 @@ +@author Paul Mineiro (based upon work by Ulf Wiger) +@copyright 2009 Paul Mineiro released under EPL +@title Lyet +@doc Erlang parse transform to provide let like functionality. diff --git a/fw-pkgin/Makefile.am.local b/fw-pkgin/Makefile.am.local new file mode 100644 index 0000000..c6d5812 --- /dev/null +++ b/fw-pkgin/Makefile.am.local @@ -0,0 +1 @@ +# put whatever (auto)make commands here, they will be included from Makefile.am diff --git a/fw-pkgin/config b/fw-pkgin/config new file mode 100644 index 0000000..885febd --- /dev/null +++ b/fw-pkgin/config @@ -0,0 +1,61 @@ +# The FW_PACKAGE_MAINTAINER field is populated with the +# environment variable FW_PACKAGE_DEFAULT_MAINTAINER if non-empty + +FW_PACKAGE_NAME="lyet" +FW_PACKAGE_VERSION="0.0.0" +FW_PACKAGE_MAINTAINER="Paul Mineiro " +FW_PACKAGE_SHORT_DESCRIPTION="Erlang parse transform to provide let like functionality." +FW_PACKAGE_DESCRIPTION=`cat README` +FW_PACKAGE_ARCHITECTURE_DEPENDENT="0" + +# Dependency information. The native syntax corresponds to Debian, +# http://www.debian.org/doc/debian-policy/ch-relationships.html +# Section 7.1 "Syntax of Relationship Fields" +# +# For other packaging systems, the syntax is translated for you. + +FW_PACKAGE_DEPENDS="" +FW_PACKAGE_CONFLICTS="" +FW_PACKAGE_PROVIDES="" +FW_PACKAGE_REPLACES="" +FW_PACKAGE_SUGGESTS="" + +FW_PACKAGE_BUILD_DEPENDS="" +FW_PACKAGE_BUILD_CONFLICTS="" + +FW_DUPLOAD_ARGS=${FW_DUPLOAD_ARGS-"-t dukeshardy"} + +# uncomment and set manually if autodetection of modules is incorrect. +# should be an erlang expression which evaluates to a list. +# FW_ERL_APP_MODULES="[]" + +# uncomment and set manually if autodetection of registered processes is incorrect +# should be an erlang expression which evaluates to a list. +# FW_ERL_APP_REGISTERED="[]" + +# uncomment and set manually if autodetection of start module is incorrect +# should be an erlang expression which evaluates to an atom. +# FW_ERL_APP_START_MODULE="" + +# uncomment to define start args to the start module. should be an erlang +# expression which evaluates to a list. this only has an effect if +# FW_ERL_APP_START_MODULE is set manually. +# FW_ERL_APP_START_ARGS="[]" + +# uncomment if the module line being generated is incorrect and you want +# to override it. +# FW_ERL_APP_MOD_LINE="{ mod, { $FW_ERL_APP_START_MODULE, $FW_ERL_APP_START_ARGS } }" + +# uncomment to define the application environment variables. should be an +# erlang expression which evaluates to a list. +# FW_ERL_APP_ENVIRONMENT="[]" + +# uncomment to indicate additional OTP applications (besides kernel and stdlib) +# that this application depends upon. should be an erlang expression which +# evaluates to a list. +# FW_ERL_PREREQ_APPLICATIONS_EXTRA="[]" + +# uncomment to add arbitrary extra content to the app file, e.g., an +# included application directive. should be an erlang expression which +# evaluates to a proplist (list of key-value tuple pairs). +# FW_ERL_APP_EXTRA="[]" diff --git a/fw-pkgin/post-install b/fw-pkgin/post-install new file mode 100755 index 0000000..2958228 --- /dev/null +++ b/fw-pkgin/post-install @@ -0,0 +1,9 @@ +#! /bin/sh + +#--------------------------------------------------------------------- +# post-install +# +# Executed after the package is installed. +#--------------------------------------------------------------------- + +exit 0 diff --git a/fw-pkgin/post-remove b/fw-pkgin/post-remove new file mode 100755 index 0000000..6b8e5fc --- /dev/null +++ b/fw-pkgin/post-remove @@ -0,0 +1,9 @@ +#! /bin/sh + +#--------------------------------------------------------------------- +# post-remove +# +# Executed after the package is removed. +#--------------------------------------------------------------------- + +exit 0 diff --git a/fw-pkgin/pre-install b/fw-pkgin/pre-install new file mode 100755 index 0000000..d8d1722 --- /dev/null +++ b/fw-pkgin/pre-install @@ -0,0 +1,9 @@ +#! /bin/sh + +#--------------------------------------------------------------------- +# pre-install +# +# Executed before the package is installed. +#--------------------------------------------------------------------- + +exit 0 diff --git a/fw-pkgin/pre-remove b/fw-pkgin/pre-remove new file mode 100755 index 0000000..5dc7ae1 --- /dev/null +++ b/fw-pkgin/pre-remove @@ -0,0 +1,9 @@ +#! /bin/sh + +#--------------------------------------------------------------------- +# pre-remove +# +# Executed before the package is removed. +#--------------------------------------------------------------------- + +exit 0 diff --git a/fw-pkgin/start b/fw-pkgin/start new file mode 100755 index 0000000..dc16300 --- /dev/null +++ b/fw-pkgin/start @@ -0,0 +1,10 @@ +#! /bin/sh + +#--------------------------------------------------------------------- +# start +# +# Executed when the package (service) is started up. +# Not supported by all package formats. +#--------------------------------------------------------------------- + +exit 0 diff --git a/fw-pkgin/stop b/fw-pkgin/stop new file mode 100755 index 0000000..665484d --- /dev/null +++ b/fw-pkgin/stop @@ -0,0 +1,10 @@ +#! /bin/sh + +#--------------------------------------------------------------------- +# start +# +# Executed when the package (service) is shut down. +# Not supported by all package formats. +#--------------------------------------------------------------------- + +exit 0 diff --git a/src/Makefile.am.local b/src/Makefile.am.local new file mode 100644 index 0000000..f740ec4 --- /dev/null +++ b/src/Makefile.am.local @@ -0,0 +1,15 @@ +# put whatever (auto)make commands here, they will be included from Makefile.am + +dist_erlappsrc_DATA = \ + $(wildcard *.erl) + +dist_erlappinclude_DATA = \ + $(wildcard *.hrl) + +erlappebin_SCRIPTS = \ + @FW_PACKAGE_NAME@.app \ + $(patsubst %.erl, %.beam, $(dist_erlappsrc_DATA)) + +check_DATA = \ + .dialyzer_ok \ + .@FW_PACKAGE_NAME@.app_ok diff --git a/src/lyet.erl b/src/lyet.erl new file mode 100644 index 0000000..190824f --- /dev/null +++ b/src/lyet.erl @@ -0,0 +1,271 @@ +%%% The contents of this file are subject to the Erlang Public License, +%%% Version 1.0, (the "License"); you may not use this file except in +%%% compliance with the License. You may obtain a copy of the License at +%%% http://www.erlang.org/license/EPL1_0.txt +%%% +%%% 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 Original Code is ct_expand.erl +%%% +%%% The Initial Developer of the Original Code is Ericsson Telecom +%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson +%%% Telecom AB. All Rights Reserved. +%%% +%%% Contributor(s): Paul Mineiro +%%% +%%% ------------------------------------------------------------------ +-module(lyet). + +%% Most of this is "library" code extracted from ct_expand.erl +%% the custom stuff is in parse_transform/2 + +-export([function/4, + format_error/1]). +-export([parse_transform/2]). + +-define(ERROR(R, T, F, I), + begin + rpt_error(R, T, F, I), + throw({error,erl_syntax:get_pos( + proplists:get_value(form,I)),{unknown,R}}) + end). + +-import(erl_syntax, [clause/3, + clause_patterns/1, + clause_body/1, + clause_guard/1, + match_expr/2, + function_clauses/1, + get_pos/1, + add_ann/2, + get_ann/1]). + +%%% ================================================================== +%%% This is the custom code that's been added. The rest of the module +%%% is 'library code' (or should be...) +%%% Any expression wrapped inside a call to lyet:lyet(Var = Value, Expr) is +%%% replaced at compile-time with +%%% (fun (Var) -> Expr end) (Value) +%%% +parse_transform(Forms, Options) -> + function({?MODULE, lyet, any}, + fun(Form, _Context) -> + case erl_syntax:application_arguments(Form) of + [] -> + erlang:error("illegal_form: lyet:lyet() requires at least one argument."); + L=[_|_]-> + [Expr|Assignments] = lists:reverse(L), + lists:foldl + (fun (Assign, Acc) -> + { match, Line, { var, _, Var }, Val } = + erl_syntax:revert (Assign), + { call, + Line, + { 'fun', + Line, + { clauses, + [ { clause, + Line, + [ { var, Line, Var } ], + [], + [ Acc ] + } + ] + } + }, + [ Val ] + } + end, + erl_syntax:revert (Expr), + Assignments); + _Args -> + erlang:error(illegal_form) + end + end, Forms, Options). + +%%% End custom code. +%%% ================================================================== + + +%%% API: function({Module, Function, Arity}, Fun, Forms, Options) -> +%%% NewForms +%%% +%%% Forms and Options are the arguments passed to the parse_transform/2 +%%% function. +%%% {Module, Function, Arity} is the function call to transform +%%% Fun(Form, Context) -> NewForm is the fun provided by the caller. +%%% +%%% Context is a property list, containing the following properties: +%%% - {file, Filename} +%%% - {module, ModuleName} +%%% - {function, FunctionName} % name of the enclosing function +%%% - {arity, Arity :: integer()} % arity of same +%%% - {var_names, Vars :: [atom()]} % generated variables binding the +%%% % function arguments. +%%% % length(Vars) == Arity +%%% +function({_Module, _Function, _Arity} = MFA, F, + Forms, Options) when is_function(F) -> + parse_transform(MFA, F, Forms, Options). + +parse_transform(MFA, Fun, Forms, _Options) -> + [File|_] = [F || {attribute,_,file,{F,_}} <- Forms], + try begin + NewTree = xform(MFA, Fun, Forms, [{file, File}]), + revert_tree(NewTree) + end + catch + throw:{error,Ln,What} -> + {error, [{File, [{Ln,?MODULE,What}]}], []} + end. + +revert_tree(Tree) -> + [erl_syntax:revert(T) || T <- lists:flatten(Tree)]. + + +format_error(Other) -> + lists:flatten( + io_lib:format("unknown error in parse_transform: ~p", [Other])). + + + + +xform({M,F,A}, Fun, Forms, Context0) -> + Bef = fun(function, Form, Ctxt) -> + {Fname, Arity} = erl_syntax_lib:analyze_function(Form), + VarNames = erl_syntax_lib:new_variable_names( + Arity, + erl_syntax_lib:variables(Form)), + {Form, [{function, Fname}, + {arity, Arity}, + {var_names, VarNames}|Ctxt]}; + (_, Form, Context) -> + {Form, Context} + end, + Aft = fun(application, Form, Context) -> + case erl_syntax_lib:analyze_application(Form) of + {M, {F, _}} when A =:= any -> + add_ann( + bind_state, + Fun(Form, Context)); + {M, {F, A}} -> + add_ann( + bind_state, + Fun(Form, Context)); + _ -> + Form + end; + (function, Form, Context) -> + Form1 = + erl_syntax_lib:map_subtrees( + fun(Clause) -> + case should_i_bind(Clause) of + true -> + Pats = clause_patterns(Clause), + CBod = clause_body(Clause), + CGd = clause_guard(Clause), + Pats1 = + lists:zipwith( + fun(V, P) -> + match_expr(v(V), P) + end, + proplists:get_value( + var_names, Context), + Pats), + clause(Pats1, CGd, CBod); + false -> + Clause + end + end, Form), + Form1; + (_, Form, _Context) -> + Form + end, + [Module] = [Mx || {attribute, _, module, Mx} <- Forms], + transform(Forms, Bef, Aft, [{module, Module}|Context0]). + + +transform(Forms, Before, After, Context) -> + F1 = + fun(Form) -> + Type = erl_syntax:type(Form), + {Form1, Context1} = + try Before(Type, Form, Context) + catch + error:Reason -> + ?ERROR(Reason, 'before', Before, + [{type, Type}, + {context, Context}, + {form, Form}]) + end, + Form2 = + case erl_syntax:subtrees(Form1) of + [] -> + Form1; + List -> + NewList = + transform( + List, Before, After, Context1), + erl_syntax:update_tree(Form, NewList) + end, + Type2 = erl_syntax:type(Form2), + try After(Type2, Form2, Context1) + catch + error:Reason2 -> + ?ERROR(Reason2, 'after', After, + [{type, Type2}, + {context, Context1}, + {form, Form2}]) + end + end, + F2 = fun(List) when is_list(List) -> + map(F1, List); + (Form) -> + F1(Form) + end, + map(F2, Forms). + +%%% Slightly modified version of lists:mapfoldl/3 +%%% Here, F/2 is able to insert forms before and after the form +%%% in question. The inserted forms are not transformed afterwards. +map(F, [Hd|Tail]) -> + {Before, Res, After} = + case F(Hd) of + {Be, _, Af} = Result when is_list(Be), is_list(Af) -> + Result; + R1 -> + {[], R1, []} + end, + Rs = map(F, Tail), + Before ++ [Res| After ++ Rs]; +map(F, []) when is_function(F, 1) -> []. + + + +rpt_error(Reason, BeforeOrAfter, Fun, Info) -> + Fmt = lists:flatten( + ["*** ERROR in parse_transform function:~n" + "*** Reason = ~p~n" + "*** applying ~w fun (~p)~n", + ["*** ~10w = ~p~n" || _ <- Info]]), + Args = [Reason, BeforeOrAfter, Fun | + lists:foldr( + fun({K,V}, Acc) -> + [K, V | Acc] + end, [], Info)], + io:format(Fmt, Args). + + +should_i_bind(Tree) -> + erl_syntax_lib:fold( + fun(T, Flag) -> + lists:member(bind_state, get_ann(T)) or Flag + end, false, Tree). + + + +v(V) -> + erl_syntax:variable(V). diff --git a/tests/Makefile.am.local b/tests/Makefile.am.local new file mode 100644 index 0000000..fb15b0d --- /dev/null +++ b/tests/Makefile.am.local @@ -0,0 +1,2 @@ +TESTS = \ + module-testlyet diff --git a/tests/testlyet.erl b/tests/testlyet.erl new file mode 100644 index 0000000..6866d8f --- /dev/null +++ b/tests/testlyet.erl @@ -0,0 +1,59 @@ +-module (testlyet). +-compile ({ parse_transform, lyet }). + +-ifdef (HAVE_EUNIT). +-include_lib ("eunit/include/eunit.hrl"). +-endif. + +g (X) -> X + 1. +h (X) -> X + 2. +l (X) -> X + 3. +m (X) -> X + 4. + +% different equivalent ways of writing m (l (h (g (X)))) +% i like the first one the best + +testone (X) -> + lyet:lyet (X = g (X), + X = h (X), + X = l (X), + m (X)). + + +testtwo (X) -> + lyet:lyet (X = g (X), + lyet:lyet (X = h (X), + lyet:lyet (X = l (X), + m (X) + ) + ) + ). + +testthree (X) -> + lyet:lyet (X = + lyet:lyet (X = + lyet:lyet (X = + lyet:lyet (X = g (X), + X), + h (X)), + l (X)), + m (X)). + +testnoassign () -> + lyet:lyet (9 + 2). + +-ifdef (EUNIT). + +one_test () -> + ?assertEqual (11, testone (1)). + +two_test () -> + ?assertEqual (11, testtwo (1)). + +three_test () -> + ?assertEqual (11, testthree (1)). + +noassign_test () -> + ?assertEqual (11, testnoassign ()). + +-endif.