团队的兄弟遇到一个问题,如何把字符串动态解析成为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}])).
--The End--