%%
  Ported from the String Lambdas in    Functional Javascript
  /
%%
  This work is licensed under the MIT License :
%%
  ( c ) 2007  
  Portions Copyright ( c ) 2006  
%% 
%% 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.


-module(lib_lambda).
-author('').
-copyright('Copyright (c)2007 Debasish Ghosh. All rights reserved.').

%%
%% Exported Functions
%%
-export([lambda/1]).

-define(UNARY_ARG, "_").
-define(UNARY_ARG_MATCH, "(^_|[\s()-*+<>\/',]+_|[-]_)").
-define(UNARY_PARAM, "X").
-define(LEFT_MATCH_PARAM, ?UNARY_PARAM).
-define(RIGHT_MATCH_PARAM, "Y").
-define(NULL_STRING, "").
-define(EMPTY_STRING, " ").
-define(FUN_TEMPLATE, "~sfun(~s)->~s end~s~s").

   lambda(string ( ) ) - > function ( )
  @doc Implements string lambdas based on    implementation in Javascript .

lambda(String) ->
    case regexp:split(String, "\s*->\s*") of
        
        %% If the string contains a `->`, this separates the parameters from the body:
  > > lambda('X Y - > X+Y')(23 , 12 ) = > 35
  > > lambda('X , Y - > 2*X+Y')(23 , 12 ) = > 58
  > > lambda('X - > X+1')(1 ) = > 2
  > > lambda('X - > Y - > 2*X+Y')(1 , 4 ) = > 6
        
        {ok, [H|T]} when length([H|T]) > 1 -> 
            form_lambda_and_eval(lists:reverse([H|T]));
        
        %% Otherwise, if the string contains a `_`, this is the parameter:                                 
  > > lambda ( ' _ + 12')(3 ) = > 15

        {ok, [H|_]} ->
            case match_unary_arg_and_sub(H) of
                {ok, NewString, RepCount} when RepCount > 0 ->
                    Params = ?UNARY_PARAM,
                    FunString = lib_misc:join(["fun(", Params, ") ->", NewString, " end."]), 
                    eval(FunString);
                                                          
                %% test whether an operator appears on the left (or right), respectively
  > > lambda('2/')(4 ) = > 0.5
  > >  ) = > 3
  > > lambda('/')(6,2 ) = > 3
  > > lambda('4/2')(78 ) = > 2 - ignore the argument
                                                              
                {ok, _, 0} -> 
                    {LParams, LBody} = left_section_match(H),
                    {RParams, RBody} = right_section_match(H, LParams, LBody),

                    %% take care of false positives
  > > lambda('4/2 ' ) = > fun(X _ ) - > 4/2 end
                    case (length(RParams) == 0) and (length(RBody) == 0) of
                        true ->
  need to check if we can capture any Erlang variable
                            %% if not, then make the function return the expression
                            %% with a single argument, so that it can be applied with
                            %% higher order functions
                                
                            case capture_variables(H) of
                                {ok, Variables} when length(Variables) > 0 ->
                                    FunString = lib_misc:join(["fun(", lib_misc:join(Variables, ","), ")->", H, " end."]),
                                    eval(FunString);
                                {ok, _} -> 
                                    FunString = lib_misc:join(["fun(", ?UNARY_PARAM, ")->", H, " end."]),
                                    eval(FunString);
                                {error, Error} -> {error, Error}
                            end;
                                
                        _ ->
                            FunString = lib_misc:join(["fun(", RParams, ")->", RBody, " end."]),
                            eval(FunString)
                    end
            end;
        {error, Error} -> {error, Error}
    end.

%% Local Functions
%%

%% for lambdas containing ->, form the lambda body and eval
  lambda('X - > Y - > 2*X + Y ' ) = > ( fun(X ) - > (  ) - > 2*X + Y end ) end ) .

form_lambda_and_eval([H|T]) ->
    Expr = H,
    Length = length([H|T]),
    case length([H|T]) of
        1 -> 
            %% since _ is allowed in string lambda
            %% replace underscore with a valid erlang variable name
  > > lambda('_->_+4')(8 ) = > fun(X _ ) - > X _ + 4 end = > 12
                
            {ok, NewString, _} = match_unary_arg_and_sub(H),
            eval(string:concat(NewString, "."));
        _ ->
            case regexp:split(hd(T), "\s*,\s*|\s+") of
                {ok, _} when Length == 1 -> eval(H);
                {ok, Lhs} ->
                    Params = lib_misc:join(Lhs, ","),
                    Body = lib_misc:join(["(fun(", Params, ")->", Expr, " end)"]),
                    TRest = T -- [hd(T)],
                    form_lambda_and_eval([Body] ++ TRest);
                {error, Error} -> {error, Error}
            end
     end.
    
%% left section match
  > > lambda ( ' /3')(27 ) = > 9
  > > lambda('/4')(12 ) = > 3

left_section_match(String) ->
    case regexp:first_match(String, "^\s*([+*\/(div)(rem)(bnot)(band)(bor)(bxor)(bsl)(bsr)|=<>])") of
        {match,_,_} ->
            Params = ?LEFT_MATCH_PARAM,
            Body = lib_misc:join([Params, String], ?EMPTY_STRING),
            {Params, Body};
        _ -> {?NULL_STRING, ?NULL_STRING}
    end.

%% right section match
  > > lambda ( ' 9/')(3 ) = > 3
  > > lambda('/')(12,6 ) = > 2

right_section_match(String, LParams, LBody) ->
    case regexp:first_match(String, "[+*\-\/(div)(rem)(band)(bor)(bxor)(bsl)(bsr)=<>]\s*$") of
        {match,_,_} ->
            Params = ?RIGHT_MATCH_PARAM,
            case length(LBody) of
                0 -> 
                    {Params, lib_misc:join([String, Params], ?EMPTY_STRING)};
                _ -> 
                    {lib_misc:join([LParams, Params], ","), string:concat(LBody, Params)}
            end;
        _ -> {LParams, LBody}
    end.

%%

%% get the list of variables according to the order in which
%% they appear in the string lambda
%% >> 'X+2*Y' => [X,Y]
%% >> 'X+2*Y+X' => [X,Y]
%% >> 'Y+2*X' => [Y,X]

capture_variables(String) ->
    case regexp:matches(String, "[\s\b\f\n]*[A-Z][0-9a-zA-Z]*[\s\b\f\n]*") of
        {match, Matches} ->
            {ok, lists:foldl(
                fun insert_if_absent/2,
                [], [string:strip(string:substr(String, S, L), both) || {S,L} <- Matches])};
        {error, Error} -> {error, Error}
    end.

insert_if_absent(String, List) ->
    case lists:member(String, List) of
        false -> List ++ [String];
        true -> List
    end.

eval(S) ->
    {ok,Scanned,_} = erl_scan:string(S),
    {ok,Parsed} = erl_parse:parse_exprs(Scanned),
    {value, Value,_} = erl_eval:exprs(Parsed,[]),
    Value.

%% match unary arg (_) and replace with X
  " _ ->_+1 " = > { ok , " X->X+1 " , 2 }
%% could not use the built-in regexp:gsub/3, since it does not
  support word boundary matching as perl ( \b )

match_unary_arg_and_sub(String) ->
    case regexp:matches(String, ?UNARY_ARG_MATCH) of
        {match, Matches} ->
            Positions = [S+L-1 || {S,L} <- Matches],
            replace(String, Positions, 0);
        {error, Error} -> {error, Error}
    end.

replace(String, [], RepCount) -> {ok, String, RepCount};
replace(String, [First|Rest], RepCount) ->
    case First of
        1 -> Str = ?UNARY_PARAM;
        _ -> Str = string:concat(string:substr(String, 1, First - 1), ?UNARY_PARAM)
    end,
    replace(string:concat(Str, string:substr(String, First + 1, length(String) - length(Str))), Rest, RepCount + 1).

   




 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 without limitation the rights to use, copy, modify, merge, publish,
 the following conditions:
 
 The above copyright notice and this permission notice shall be
 
 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
 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 Exported Functions

 If the string contains a `->`, this separates the parameters from the body:
 Otherwise, if the string contains a `_`, this is the parameter:                                 
 test whether an operator appears on the left (or right), respectively
 take care of false positives
 if not, then make the function return the expression
 with a single argument, so that it can be applied with
 higher order functions
 Local Functions

 for lambdas containing ->, form the lambda body and eval
 since _ is allowed in string lambda
 replace underscore with a valid erlang variable name
 left section match
 right section match

 get the list of variables according to the order in which
 they appear in the string lambda
 >> 'X+2*Y' => [X,Y]
 >> 'X+2*Y+X' => [X,Y]
 >> 'Y+2*X' => [Y,X]
 match unary arg (_) and replace with X
 could not use the built-in regexp:gsub/3, since it does not
  Ported from the String Lambdas in    Functional Javascript
  /
  This work is licensed under the MIT License :
  ( c ) 2007  
  Portions Copyright ( c ) 2006  
  " Software " ) , to deal in the Software without restriction , including
  distribute , sublicense , and/or sell copies of the Software , and to
  permit persons to whom the Software is furnished to do so , subject to
  included in all copies or substantial portions of the Software .
  THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND ,
  LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION


-module(lib_lambda).
-author('').
-copyright('Copyright (c)2007 Debasish Ghosh. All rights reserved.').

-export([lambda/1]).

-define(UNARY_ARG, "_").
-define(UNARY_ARG_MATCH, "(^_|[\s()-*+<>\/',]+_|[-]_)").
-define(UNARY_PARAM, "X").
-define(LEFT_MATCH_PARAM, ?UNARY_PARAM).
-define(RIGHT_MATCH_PARAM, "Y").
-define(NULL_STRING, "").
-define(EMPTY_STRING, " ").
-define(FUN_TEMPLATE, "~sfun(~s)->~s end~s~s").

   lambda(string ( ) ) - > function ( )
  @doc Implements string lambdas based on    implementation in Javascript .

lambda(String) ->
    case regexp:split(String, "\s*->\s*") of
        
  > > lambda('X Y - > X+Y')(23 , 12 ) = > 35
  > > lambda('X , Y - > 2*X+Y')(23 , 12 ) = > 58
  > > lambda('X - > X+1')(1 ) = > 2
  > > lambda('X - > Y - > 2*X+Y')(1 , 4 ) = > 6
        
        {ok, [H|T]} when length([H|T]) > 1 -> 
            form_lambda_and_eval(lists:reverse([H|T]));
        
  > > lambda ( ' _ + 12')(3 ) = > 15

        {ok, [H|_]} ->
            case match_unary_arg_and_sub(H) of
                {ok, NewString, RepCount} when RepCount > 0 ->
                    Params = ?UNARY_PARAM,
                    FunString = lib_misc:join(["fun(", Params, ") ->", NewString, " end."]), 
                    eval(FunString);
                                                          
  > > lambda('2/')(4 ) = > 0.5
  > >  ) = > 3
  > > lambda('/')(6,2 ) = > 3
  > > lambda('4/2')(78 ) = > 2 - ignore the argument
                                                              
                {ok, _, 0} -> 
                    {LParams, LBody} = left_section_match(H),
                    {RParams, RBody} = right_section_match(H, LParams, LBody),

  > > lambda('4/2 ' ) = > fun(X _ ) - > 4/2 end
                    case (length(RParams) == 0) and (length(RBody) == 0) of
                        true ->
  need to check if we can capture any Erlang variable
                                
                            case capture_variables(H) of
                                {ok, Variables} when length(Variables) > 0 ->
                                    FunString = lib_misc:join(["fun(", lib_misc:join(Variables, ","), ")->", H, " end."]),
                                    eval(FunString);
                                {ok, _} -> 
                                    FunString = lib_misc:join(["fun(", ?UNARY_PARAM, ")->", H, " end."]),
                                    eval(FunString);
                                {error, Error} -> {error, Error}
                            end;
                                
                        _ ->
                            FunString = lib_misc:join(["fun(", RParams, ")->", RBody, " end."]),
                            eval(FunString)
                    end
            end;
        {error, Error} -> {error, Error}
    end.


  lambda('X - > Y - > 2*X + Y ' ) = > ( fun(X ) - > (  ) - > 2*X + Y end ) end ) .

form_lambda_and_eval([H|T]) ->
    Expr = H,
    Length = length([H|T]),
    case length([H|T]) of
        1 -> 
  > > lambda('_->_+4')(8 ) = > fun(X _ ) - > X _ + 4 end = > 12
                
            {ok, NewString, _} = match_unary_arg_and_sub(H),
            eval(string:concat(NewString, "."));
        _ ->
            case regexp:split(hd(T), "\s*,\s*|\s+") of
                {ok, _} when Length == 1 -> eval(H);
                {ok, Lhs} ->
                    Params = lib_misc:join(Lhs, ","),
                    Body = lib_misc:join(["(fun(", Params, ")->", Expr, " end)"]),
                    TRest = T -- [hd(T)],
                    form_lambda_and_eval([Body] ++ TRest);
                {error, Error} -> {error, Error}
            end
     end.
    
  > > lambda ( ' /3')(27 ) = > 9
  > > lambda('/4')(12 ) = > 3

left_section_match(String) ->
    case regexp:first_match(String, "^\s*([+*\/(div)(rem)(bnot)(band)(bor)(bxor)(bsl)(bsr)|=<>])") of
        {match,_,_} ->
            Params = ?LEFT_MATCH_PARAM,
            Body = lib_misc:join([Params, String], ?EMPTY_STRING),
            {Params, Body};
        _ -> {?NULL_STRING, ?NULL_STRING}
    end.

  > > lambda ( ' 9/')(3 ) = > 3
  > > lambda('/')(12,6 ) = > 2

right_section_match(String, LParams, LBody) ->
    case regexp:first_match(String, "[+*\-\/(div)(rem)(band)(bor)(bxor)(bsl)(bsr)=<>]\s*$") of
        {match,_,_} ->
            Params = ?RIGHT_MATCH_PARAM,
            case length(LBody) of
                0 -> 
                    {Params, lib_misc:join([String, Params], ?EMPTY_STRING)};
                _ -> 
                    {lib_misc:join([LParams, Params], ","), string:concat(LBody, Params)}
            end;
        _ -> {LParams, LBody}
    end.



capture_variables(String) ->
    case regexp:matches(String, "[\s\b\f\n]*[A-Z][0-9a-zA-Z]*[\s\b\f\n]*") of
        {match, Matches} ->
            {ok, lists:foldl(
                fun insert_if_absent/2,
                [], [string:strip(string:substr(String, S, L), both) || {S,L} <- Matches])};
        {error, Error} -> {error, Error}
    end.

insert_if_absent(String, List) ->
    case lists:member(String, List) of
        false -> List ++ [String];
        true -> List
    end.

eval(S) ->
    {ok,Scanned,_} = erl_scan:string(S),
    {ok,Parsed} = erl_parse:parse_exprs(Scanned),
    {value, Value,_} = erl_eval:exprs(Parsed,[]),
    Value.

  " _ ->_+1 " = > { ok , " X->X+1 " , 2 }
  support word boundary matching as perl ( \b )

match_unary_arg_and_sub(String) ->
    case regexp:matches(String, ?UNARY_ARG_MATCH) of
        {match, Matches} ->
            Positions = [S+L-1 || {S,L} <- Matches],
            replace(String, Positions, 0);
        {error, Error} -> {error, Error}
    end.

replace(String, [], RepCount) -> {ok, String, RepCount};
replace(String, [First|Rest], RepCount) ->
    case First of
        1 -> Str = ?UNARY_PARAM;
        _ -> Str = string:concat(string:substr(String, 1, First - 1), ?UNARY_PARAM)
    end,
    replace(string:concat(Str, string:substr(String, First + 1, length(String) - length(Str))), Rest, RepCount + 1).

   
