使用ANTLR进行命令行参数解析
关于命令行参数的解析没有特定的规则,目前比较流行的有unix风格和微软风格。其实除了unix风格的比较一致外,微软自己提供的命令行参数解析就有很多种风格。在.net平台下的main函数中,仅仅把参数分解为以空格分割的数组,这对需要加开关,并且有的开关有自己的参数的情况是不够的,而且为了解析这些参数需要学习部分词法分析的知识,这对用处不是很大的命令行参数显得有些“鸡肋”,当然用Antlr来处理命令行参数更显得有些鸡肋,并且是大才小用,因为Antlr的语法规则比较复杂,学习起来有一定的难度。但对于已经使用Antlr进行DSL开发的开发人员来说,解决命令行参数解析的问题是举手之劳。
我们需要先定义命令行参数的输入规则,参考.Net framework提供的命令行工具,制定如下的规则:
1、以空格分割参数,与.net命令行参数保持一致。
2、选项(option)或者开关(switch)用/ 或 \ 或 - 作为标志。
3、选项可以有参数,使用冒号:分割,选项的参数如果是多个使用逗号(,)或者分号(;)分隔。
4、选项之后是命令行自身的参数。
5、选项名称以英文字母开头。
6、选项的参数和命令行自身的参数可以使用英文字母和数字型、下划线,如果包含双字节码或者特殊字符,需要使用双引号。
规则制定好以后,开始进入正题:
第一步,需要从Antlr网站(http://www.antlr.org)上下载Antlr类库及相关的学习资料及工具,工具中比较重要的是Antlr works。
第二步,使用Antlr works或者文本编辑器编辑词法和语法规则:
grammar CmdPara; tokens{ cmdLine : String option* para* EOF!; option swt : ('/'|'-'|'\\') ID -> ID; para : String|INT|ID; ID : ('a'..'z'|'A'..'Z')(('a'..'z') String : ('"' .+ '"')|('\'' .+ '\''); INT : ('1'..'9')('0'..'9')* WS |
第三步:定义命令行参数解析实体类
namespace Rz.CmdLine //命令行对象 //可执行文件的文件名 public string ExeFile { get; set; } private List<Switch> _cmdSwitchs = new List<Switch>(); //开关对象列表 public List<Switch> CmdSwitchs private List<string> _parameters = new List<string>(); //命令行参数 public List<string> Parameters } //开关实体类 public class Switch { //开关名称 public string SwitchName { get; set; } //开关参数 private List<string> _parameters = new List<string>(); public List<string> Parameters |
第四步:使用Antlr定义语法树遍历器
tree grammar CmdWalker;
options{
language=CSharp2;
tokenVocab=CmdPara;
ASTLabelType=CommonTree;
}
@header{
using Rz.CmdLine;
}
@members{
}
cmdLine returns[CmdLine r = new CmdLine()]
: String
{
r.ExeFile=$String.Text;
}
(option{r.CmdSwitchs.Add($option.r);})*
(para{r.Parameters.Add($para.r);})*
;
option returns[Switch r = new Switch()]
:
^(Option
swt{r.SwitchName=$swt.r;}
(para{r.Parameters.Add($para.r);})*
)
;
swt returns[string r]
: ID{r=$ID.Text;}
;
para returns[string r]
:
String{r=$String.Text;}
|INT{r=$INT.Text;}
|ID{r=$ID.Text;}
;
第 五 步:定义解析命令行的静态类,把命令行转变为前面定义的实体类。
public class CmdEngine
{
public static CmdLine Parse(string cmdLine) {
ANTLRStringStream input = new ANTLRStringStream(cmdLine);
CmdParaLexer lex = new CmdParaLexer(input);
CommonTokenStream token = new CommonTokenStream(lex);
CmdParaParser pars = new CmdParaParser(token);
CommonTreeNodeStream tree = new CommonTreeNodeStream(pars.cmdLine().Tree);
CmdWalker walker = new CmdWalker(tree);
return walker.cmdLine();
}
}
第六步:编写测试程序
static void Main(string[] args)
{
Console.WriteLine(string.Join(" ",args));
CmdLine cmd = CmdEngine.Parse(Environment.CommandLine);
Console.WriteLine(cmd.ExeFile);
foreach (Switch s in cmd.CmdSwitchs) {
Console.WriteLine("Switch /{0}:{1}",s.SwitchName,string.Join(",",s.Parameters.ToArray()));
}
foreach (string s in cmd.Parameters) {
Console.WriteLine("Command Parameter [{0}]:{1}",cmd.Parameters.IndexOf(s),s);
}
Console.ReadLine();
}
第七步:查看程序运行结果
源代码稍后会放到google开源库中,由于codeplex的速度太慢,实在无法忍受,希望有一天微软在Internet上跟上google的脚步。
参考:
以-标明选项 短选项,-sHello,其中-是标志位,s是选项,Hello是参数。或者-s Hello,这个差别在于选项与参数之间使用空格分割。 多个短选项:-x -y -z -w 或者-xyzw 或者-xy -zw 长选项:--long Hello 或者--long=hello |