团队的兄弟遇到一个问题,如何把字符串动态解析成为Erlang数据结构(Erlang term)? 比如"[{1,2},{2,3},{3,4}]"转成[{1,2},{2,3},{3,4}]

From String To Erlang Term

http://blog.yufeng.info/archives/tag/erl_eval 余锋在<erlang动态解释>这篇文章中给出了解决方案,他分析的是init.erl模块;按照文章的思路我们在shell里面试一下:

Eshell V5.8.2 (abort with ^G)
1> {ok, Scan1, _} = erl_scan:string("[a,b,c].").
{ok,[{'[',1},{atom,1,a},{',',1},{atom,1,b},{',',1},{atom,1,c},{']',1},{dot,1}],1}
2> {ok,P}=erl_parse:parse_exprs(Scan1).
{ok,[{cons,1,{atom,1,a},{cons,1,{atom,1,b},{cons,1,{atom,1,c},{nil,1}}}}]}
3> erl_eval:exprs(P, []) .
{value,[a,b,c],[]}
4>

红色标注的部分就是我们想要的结果,注意erl_scan:string(Exp).接受的参数是一个合法的表达式,必须以.结尾,代表一个表达式的结束,否则文法检查过不去;看一下输出的结果里面.符号被解析为{dot,1};这个问题最关键的部分就已经解决了,还有一个相关的问题就是如何把一个[{1,2},{2,3},{3,4}]转成字符串?这个当然要在io_lib里面去寻找答案,可以这样做: lists:flatten(io_lib:write([{1,2},{2,3},{3,4}])). 结果为"[{1,2},{2,3},{3,4}]"我们做一个完整的例子:

1> lists:flatten(io_lib:write([{1,2},{2,3},{3,4}])).
"[{1,2},{2,3},{3,4}]"
2> S= lists:flatten(io_lib:write([{1,2},{2,3},{3,4}])).
"[{1,2},{2,3},{3,4}]"
3> E=S++".". %%添加结束符
"[{1,2},{2,3},{3,4}]."
4> {ok, Scan1, _} = erl_scan:string(E).
{ok,[{'[',1},{'{',1},{integer,1,1},{',',1},{integer,1,2},{'}',1},{',',1},{'{',1},{integer,1,2},{',',1},{integer,1,3},{'}',1},{',',1},{'{',1},{integer,1,3},{',',1},{integer,1,4},{'}',1},{']',1},{dot,1}], 1}

5> {ok,P}=erl_parse:parse_exprs(Scan1).
{ok,[{cons,1,{tuple,1,[{integer,1,1},{integer,1,2}]}, {cons,1,{tuple,1,[{integer,1,2},{integer,1,3}]},
{cons,1,{tuple,1,[{integer,1,3},{integer,1,4}]},{nil,1}}}}]}
6> erl_eval:exprs(P, []) .
{value,[{1,2},{2,3},{3,4}],[]}
7>

仅仅是解析Erlang Term 也可以这样:

list_to_term(String) ->
{ok, T, _} = erl_scan:string(String++"."),
case erl_parse:parse_term(T) of
{ok, Term} ->
Term;
{error, Error} ->
Error
end.

From String To Erlang Code

这个问题可以泛化为字符串解析为Erlang代码并执行?同样的问题在.net中,我们可以这样解决:

using System;
using IronPython.Hosting; //缺少引用的去下载一个: http://ironpython.codeplex.com/
using Microsoft.Scripting.Hosting;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
ScriptEngine engine = Python.CreateEngine();
var Result = engine.Execute("2*(1+3)");
Console.WriteLine(Result);
Console.ReadLine();
}
}
}

 

还是使用上面的erl_scan erl_parse erl_eval基础设施,我们可以很容易实现:

Eshell V5.8.2 (abort with ^G)
1> E=fun(S) ->
{ok,Scanned,_} = erl_scan:string(S),
{ok,Parsed} = erl_parse:parse_exprs(Scanned),
erl_eval:exprs(Parsed,[]) end.
#Fun<erl_eval.6.13229925>
2> E("R=1+2*3.").
{value,7,[{'R',7}]}
3> E("A=2,B=3,A+B.").

{value,5,[{'A',2},{'B',3}]}

那如果是"M=K+L."这样的表达式呢?如何呢?

4> E("M=K+L.").
** exception error: {unbound_var,'K'}

我们直接在shell里面执行报的错误一致,只不过错误信息格式变化一下:* 1: variable 'K' is unbound,那么如何动态给变量绑定数值呢?这样我们可以更灵活控制表达式和值, http://www.trapexit.org/String_Eval 给了一个这样的例子:

 

-module(test).

-export([test/0]).

test()->
    %% Create a code string with unbound variables 'A' and 'B'
    String="Results=A+B/2.",
    
    %% Scan the code into tokens
    {ok,ErlTokens,_}=erl_scan:string(String),
    io:format("ErlTokens are ~p~n",[ErlTokens]),

    %% Now parse the tokens into the abstract form
    {ok,ErlAbsForm}=erl_parse:parse_exprs(ErlTokens),
    io:format("ErlAbsForm are ~p~n",[ErlAbsForm]),

    %% Now we need to bind values to variable 'A' and 'B'
    Bindings=erl_eval:add_binding('A',20,erl_eval:new_bindings()),
    NewBindings=erl_eval:add_binding('B',45,Bindings),
    io:format("The bindings are ~p~n",[erl_eval:bindings(NewBindings)]),

    %% Now evaluate the string
    io:format("Going into erl_eval:exprs~n",[]),
    {value,Value,_}=erl_eval:exprs(ErlAbsForm,NewBindings),
    io:format("Value is ~p~n",[Value]).

You can compile and run this in the shell:

(arrian@psyduck)17> c(test).
{ok,test}
(arrian@psyduck)18> test:test().
ErlTokens are [{var,1,'Results'},
               {'=',1},
               {var,1,'A'},
               {'+',1},
               {var,1,'B'},
               {'/',1},
               {integer,1,2},
               {dot,1}]
ErlAbsForm are [{match,1,
                       {var,1,'Results'},
                       {op,1,
                           '+',
                           {var,1,'A'},
                           {op,1,'/',{var,1,'B'},{integer,1,2}}}}]
The bindings are [{'A',20},{'B',45}]
Going into erl_eval:exprs
Value is 42.5000
ok
(arrian@psyduck)19>            

Note: If you bind variables that don't exist in the code string/token set/abstract form then when you erl_eval the abstract form will simply silently ignore your additional bindings

 

我曾经介绍过开源项目smerl,其定位就是Simple Metaprogramming for Erlang, 我们可以从这份代码里面学到erl_scan erl_parse erl_eval更灵活的应用,项目地址:http://code.google.com/p/smerl/ 

  test_smerl() ->
      M1 = smerl:new(foo),
      {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."),
      smerl:compile(M2),
      foo:bar(),   % returns 2``
      smerl:has_func(M2, bar, 0). % returns true

 最后顺便提一句,如何把{abc}作为字符串写入到文件中呢?  lists:flatten(io_lib:format("~p",[{a,b,c}])).

 

2012年9月27日16:54:11 更新 添加一段类似的代码

%%%
%%% distel_ie - an interactive erlang shell
%%%
%%% Some of the code has shamelessly been stolen from Luke Gorrie 
%%% [luke@bluetail.com] - ripped from its elegance and replaced by bugs. 
%%% It just goes to show that you can't trust anyone these days. And
%%% as if that wasn't enough, I'll even blame Luke: "He _made_ me do it!"
%%%
%%% So, without any remorse, I hereby declare this code to be:
%%%
%%% copyright (c) 2002 david wallin [david.wallin@ul.ie].
%%%
%%% (it's probably going to be released onto an unexpecting public under
%%%  some sort of BSD license).

-module(distel_ie).

-export([
     evaluate/2,

     test1/0,
     test2/0,
     test3/0,
     test4/0,
     test5/0,

     ensure_registered/0,

     start/0,
     start/1,
     init/1,
     loop/1
    ]).
-compile(export_all).
-define(FMT(X), list_to_binary(lists:flatten(io_lib:format("~p", [X])))).


%%
%% ensure_registered/0
ensure_registered() ->
    case whereis(distel_ie) of
           undefined -> start() ;
           Pid  ->
              group_leader(group_leader(), Pid),
              ok
    end.

%%
%% start/0

start() ->
    start([]).


%%
%% start/1

start(Options) ->
    spawn(?MODULE, init, [Options]).


%%
%% init/1

init(Options) ->
    register(distel_ie, self()),
    Defs = ets:new(definitions, [set]),
    Line = 12,
    Bindings = [],
    State = {Defs, Line, Bindings},
    loop(State).


%%
%% loop/1

loop({Defs, Line, Bindings}) ->
    receive 
    {evaluate, Emacs, String} -> 
        case catch evaluate(String, {Defs, Line, Bindings}) of
        {'EXIT', Rsn} ->
            Emacs ! {ok, list_to_binary(
                   io_lib:format("EXIT: ~p", [Rsn]))},
            ?MODULE:loop({Defs, Line, Bindings});
        {Result, {NL, NB}} ->
            Emacs ! Result,
            ?MODULE:loop({Defs, NL, NB})
        end;
    forget_bindings ->
        put(distel_ie_bindings, []),
        ?MODULE:loop({Defs, Line, []}) ;
    Unknown ->
        io:format("distel_ie: unknown message recvd '~p'\n", [Unknown]),
        ?MODULE:loop({Defs, Line, Bindings})

    end.


%%
%% evaluate/2

evaluate(String, {Defs, Line, Bindings}) ->
    case parse_expr(String) of
    %% ok, so it is an expression :
    {ok, Parse} ->
        RemoteParse = add_remote_call_info(Parse, Defs),
            case catch erl_eval:exprs(RemoteParse, Bindings) of
                {value, V, NewBinds} ->
            {{ok, ?FMT(V)}, {Line, NewBinds}};
                Error ->
            {?FMT(Error), {Line, Bindings}}
        end;
    %% try and treat it as a form / definition instead :
    Other ->
        case parse_form(String) of
        {ok, Parse} -> 
            {ok, Name, Arity} = get_function_name(Parse),
            ets:insert(Defs, {Name, Parse}),
            FunTrees = lists:flatten(
                 lists:reverse(ets:match(Defs,{'_', '$1'}))),
            %% Line isn't really used yet
            NewLine = Line,
            compile_load(FunTrees),
            Def = list_to_binary(atom_to_list(Name) ++ "/" ++ 
                     integer_to_list(Arity)),
            {{ok, Def}, {NewLine, Bindings}} ;
        Error ->
            {{error, ?FMT({Error, Other})}, {Line, Bindings}}
        end
    end.

%%
%% parse_expr/1

parse_expr(String) ->
    case erl_scan:string(String) of
    {ok, Tokens, _} ->
        catch erl_parse:parse_exprs(Tokens) ;
    {error, {_Line, erl_parse, Rsn}} ->
        {error, lists:flatten(Rsn)};
    {error, Error, _} ->
        {error, Error}
    end.

%%
%% parse_form/1

parse_form(String) ->
    case erl_scan:string(String) of
    {ok, Tokens, _} ->
        catch erl_parse:parse_form(Tokens) ;
    {error, {_Line, erl_parse, Rsn}} ->
        {error, lists:flatten(Rsn)};
    {error, Error, _} ->
        {error, Error}
    end.


%%
%% defun/1

defun(String) ->
    {ok, Tokens, _} = erl_scan:string(String),
    {ok, Parse} = erl_parse:parse_form(Tokens),
    compile_load([Parse]).


%%
%% compile_load/1

compile_load(Parse) ->

    Header = [{attribute,9,module,distel_ie_internal},
          {attribute,11,compile,export_all},
          {attribute,12,export,[]}],

    EOF = [{eof,20}],

    SyntaxTree = Header ++ Parse ++ EOF,

    {ok, Mod, Binary} = compile:forms(SyntaxTree),
    File = "heltigenomfelfelsomfansomentyskindianenbefjadradgerman",
    code:load_binary(Mod, File, Binary).


%%
%% add_remote_call_info/2
%%
%% TODO: this is gonna need more work, e.g. it needs to recurse into 
%% lists (cons) and tuples ... +more

add_remote_call_info([], _Defs) -> [] ;
add_remote_call_info({var, L, Var}, Defs) ->
    {var, L, Var} ;
add_remote_call_info({atom, L, Atom}, Defs) ->
    {atom, L, Atom} ;
add_remote_call_info({integer, L, Value}, Defs) ->
    {integer, L, Value} ;
add_remote_call_info({string, L, String}, Defs) ->
    {string, L, String} ;
add_remote_call_info([{call, L, {atom, L2, Name}, Body} | Rs], Defs) ->
    B = add_remote_call_info(Body, Defs),
    IsBuiltin = erlang:is_builtin(erlang, Name, length(B)),
    Call = case IsBuiltin of 
           true ->
           {call, L, {atom, L2, Name}, B} ;
           false ->
           Arity = length(Body),
           case find_module(Name, Arity) of 
               {ok, Mod} ->
                {call, L, {remote, L2, 
                      {atom, L2, Mod}, 
                      {atom, L2, Name}}, B} ;
               {error, _} ->
               {call, L, {atom, L2, Name}, B}
           end
       end,
    [Call | add_remote_call_info(Rs, Defs)] ;

add_remote_call_info([{tuple, L, Values} | Rs], Defs) ->
    F = fun(X) -> add_remote_call_info(X, Defs) end,
    [{tuple, L, lists:map(F, Values)} | add_remote_call_info(Rs, Defs)] ;


add_remote_call_info([{Type, L, Hdr, Body} | Rs], Defs) when list(Body) ->
    B = add_remote_call_info(Body, Defs),
    [{Type, L, Hdr, B} | add_remote_call_info(Rs, Defs)] ;

add_remote_call_info([{Type, L, Hd, Tl} | Rs], Defs) ->
    [{Type, L, Hd, Tl} | add_remote_call_info(Rs, Defs)] ;

add_remote_call_info([R | Rs], Defs) ->
    [add_remote_call_info(R, Defs) | add_remote_call_info(Rs, Defs) ];

add_remote_call_info(X, Defs) ->
    X.


%%
%% find_module/2

find_module(Function, Arity) ->
    Mods = [distel_ie_internal, distel_ie, c],
    F = fun(M) -> not is_exported(Function, Arity, M) end,
    case lists:dropwhile(F, Mods) of
    [] ->
        search_modules(Function, Arity, code:all_loaded()) ;
    [M | _] ->
        {ok, M}
    end.


%%
%% is_exported/3

is_exported(Function, Arity, Module) ->
    case code:is_loaded(Module) of
    false ->
        false;
    _ ->
        Info = Module:module_info(),
        {value, {exports, Exports}} = lists:keysearch(exports, 1, Info),
        lists:member({Function, Arity}, Exports)
    end.


%%
%% search_modules/3

search_modules(Function, Arity, []) ->
    {error, not_found};
search_modules(Function, Arity, [{M, _} | Ms]) ->
    case is_exported(Function, Arity, M) of
    true ->
        {ok, M} ;
    false ->
        search_modules(Function, Arity, Ms)
    end.
    

%%
%% get_function_name/1

get_function_name({function, _, Name, Arity, _}) -> 
    {ok, Name, Arity} ;

get_function_name(Unknown) ->
    {error, Unknown}.


%%% ------------------------------------------------------------------- [tests]
    
%%
%% test1/0

test1() ->
    Defun = "sista(W) -> lists:last(W).",
    defun(Defun).

%%
%% test2/0

test2() ->
    
    Prefix = [
          {attribute,9,module,compile_and_load_me},
          {attribute,11,compile,export_all},
          {attribute,12,export,[]}
         ],
    
    Postfix = [{eof,20}],
    
    String = "sista([]) -> [] ;\n\nsista(W) -> lists:last(W).\n",
%    String = "sista([], _) -> [] ;\n\nsista(W, _) -> lists:last(W).\n",
    
    {ok, Tokens, _} = erl_scan:string(String, 23),
    {ok, Tree} = erl_parse:parse_form(Tokens),
    
    io:format("tree : '~p'\n", [Tree]),
    
    SyntaxTree = Prefix ++ [Tree] ++ Postfix,
    
    io:format("syntaxtree : '~p'\n", [SyntaxTree]),
    
    {ok, Mod, Binary} = compile:forms(SyntaxTree),
    code:load_binary(Mod, "spam", Binary),
    compile_and_load_me:sista([1,2,galapremiere]).


%%
%% test3/0

test3() ->

    Sista = "sista(W) -> lists:last(W).\n",
    defun(Sista),
    distel_ie_internal:sista([1,2,galapremiere]),
    NySista = "en_till_sista(W) -> lists:last(W).\n",
    defun(NySista),
    distel_ie_internal:en_till_sista([1,2,onestepbeyond]).


%%
%% test4/0

test4() ->

    Defs = ets:new(definitions, [set]),

    Define = "nisse([]) -> [] ;\n\nnisse(W) -> lists:last(W).",
    Expr = "nisse([1,2,3]).",
%    Expr = "distel_ie_internal:nisse([1,2,3]).",
%    Expr = "lists:last([1,2,3]).",
    
    D = evaluate(Define, {Defs, 10, []}),
    io:format("nisse defined: '~p'\n", [D]),
    io:format("is_loaded: '~p'\n", [code:is_loaded(distel_ie_internal)]),
    evaluate(Expr, {Defs, 10, []}).


%%
%% test5/0

test5() ->

    Defs = ets:new(definitions, [set]),
    
    Define = "nisse([]) -> [] ;\n\nnisse(W) -> lists:last(W).",
    Expr = "nisse([1,2,3]).",

    evaluate(Define, {Defs, 10, []}),
%    ets:insert(Defs, {nisse, nil}),

    {ok, Tokens, _} = erl_scan:string(Expr, 23),
    {ok, Tree} = erl_parse:parse_exprs(Tokens),

    io:format("original tree : '~p'\n", [Tree]),
    
    RemoteTree = add_remote_call_info(Tree, Defs),
    io:format("remote tree : '~p'\n", [RemoteTree]).

 

 

 

--The End--