Irony是一个强大的框架,它允许开发者在C#中创建全新的编程语言。通过定义语法规则并提供新关键字、函数的实现,可以轻松构建出满足特定需求的编程语言。本文将介绍如何使用Irony框架实现与WWW服务器的交互,包括发送GET和POST请求、匹配响应内容等功能。
Irony框架的核心组件包括扫描器、解析器和解释器,它们都是用C#编写的。开发者需要定义语言的语法规则(同样使用C#),并提供新关键字和函数的实现。CodeProject网站上有一些入门文章,例如《编写第一个领域特定语言》、《JSBasic - 一个BASIC到JavaScript的编译器》、《编写第一个Visual Studio语言服务》以及《Irony - .NET编译器构建工具包》等,这些文章展示了Irony的一些可能性。
为了实现基本的WWW操作,需要定义以下语法规则:
program ::= *
stmt ::=
getStmt
| postStmt
| matchStmt
| caseStmt
| gotoStmt
| labelStmt
| assignmentStmt
| expr
其中,最重要的语法元素是"get"、"post"和"match"语句。"get"和"post"语句允许向Web服务器发送请求(GET或POST),并将结果存储在变量中。例如:
get "http://www.google.com/" into @variable
log(@variable)
end
"log"是WWW DSL中的一个函数,它允许将信息存储到文件或控制台中。变量不需要显式声明,地址也可以来自变量:
@addr = "http://www.google.com/"
get @addr into @variable
log(@variable)
end
错误处理也是可能的:
@addr = "http://www.google.com/"
get @addr into @variable
log(@variable)
:error
log("Error appear!")
end
同样,可以向服务器发送POST请求(例如,登录Gmail):
@addr = "https://www.google.com/accounts/ClientLogin"
post @addr
referer = "https://www.google.com/accounts/ClientLogin"
postdata
"accountType"="GOOGLE"
"Email"="account@gmail.com"
"Passwd"="putpasswordhere"
"service"="mail"
"source"="Pol-WWWDSL-1.0"
end
into @variable
log(@variable)
:error
log("Error appear!")
end
从服务器获得响应后,可以使用"match"语句进行处理。例如:
@addr = "https://www.google.com/accounts/ClientLogin"
post @addr referer = "https://www.google.com/accounts/ClientLogin"
postdata
"accountType"="GOOGLE"
"Email"="account@gmail.com"
"Passwd"="putpasswordhere"
"service"="mail"
"source"="Pol-WWWDSL-1.0"
end
into @variable
log(@variable)
match @variable using
>>>
SID=(?[^\\s]+)\\s+LSID=(?[^\\s]+)\\s+Auth=(?[^\\s]+)
>>>
log(@sid)
log(@lsid)
log(@auth)
:error
log("Match failed :(")
end
:error
log("Error appear!")
end
当有多个选择时,"switch"语句比"match"更好,例如:
get "http://www.google.pl/search?q=Irony" into @variable
switch @variable
case
>>>
Wikipedia, the free encyclopedia
>>>
log("Wikipedia result")
end
case
>>>
definition | Dictionary.com
>>>
log("Dictionary result, no wikipedia result")
end
default
log("Other results, no wikipedia nor dictionary result")
end
end
最后一个语法元素是"goto"跳转。这种"丑陋"的构造在简单的脚本中非常方便,例如:
:restart
get "http://www.google.pl/search?q=Irony" into @variable
switch @variable
case
>>>
Wikipedia, the free encyclopedia
>>>
log("Wikipedia result")
goto restart
end
default
log("Other results, no wikipedia result")
end
end
在这个项目中使用的Irony版本缺少"goto"实现。因此,准备了一个简单的解决方案来提供这个功能。
它工作如下:
protected override void DoEvaluate(EvaluationContext context)
{
throw new GotoJumpException(labelNode_);
}
这允许从调用堆栈返回。然后,执行重新开始,但从跳转目的地标签的适当点开始。
while (nodes != null)
{
try
{
foreach (var node in nodes)
{
node.Evaluate(evalContext);
}
nodes = null;
}
catch (GotoJumpException e)
{
nodes = m_gotoNodes[e.LabelId.ValueString];
}
}
这些执行点在脚本执行之前准备好。语法树在标签点被修剪,得到的分支存储在m_gotoNodes字典中,标签名称作为键。
foreach (var labelStmt in labels)
{
var nodes = labelStmt.Parent.ChildNodes.Skip(
labelStmt.Parent.ChildNodes.IndexOf(labelStmt));
var parent = labelStmt.Parent;
while (parent.Parent != null)
{
var upper = parent.Parent;
if ((upper.Term.Name == "stmt+") || (upper.Term.Name == "program"))
{
var upperNextNodes =
upper.ChildNodes.Skip(upper.ChildNodes.IndexOf(parent) + 1);
nodes = nodes.Concat(upperNextNodes);
}
parent = upper;
}
m_gotoNodes.Add(((Token)labelStmt.ChildNodes[0]).ValueString, nodes);
}
WWW DSL的"标准库"由四个函数组成。"wait"函数允许等待指定的秒数,例如:
get @addr into @variable
match @variable using
>>>
you have to wait (?\d+) minutes
>>>
wait(int(@minutes)*60)
end
end
上述示例使用了额外的函数"int",它允许从字符串转换为整数。基本的数值操作(如上面的乘法)由Irony本身提供。
最后两个函数是"log"(在前面的示例中已经展示)和"download",这是一个允许从WWW服务器下载文件的函数。以下是一个使用WWW DSL创建的最简单的文件下载器示例:
download(@arg1, @arg2)
在这个示例中,有两个变量(@arg1和@arg2),它们等同于程序"main"函数中的"args"。@arg1是第一个,@arg2是第二个传递给WWW DSL脚本的参数。
脚本准备好后,需要检查其正确性。Irony有一个非常好的应用程序来检查语法和脚本,即Irony语法探索器。