使用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;
options{
    language=CSharp2;
    output=AST;
    ASTLabelType=CommonTree;
}

tokens{
    Option;
}

cmdLine    :    String option* para* EOF!;

option
    :    swt ((':' para)((','|';')para)*)? ->^(Option swt para*);

swt    :    ('/'|'-'|'\\') ID -> ID;

para    :    String|INT|ID;

ID    :    ('a'..'z'|'A'..'Z')(('a'..'z')
        |('A'..'Z')
        |'0'..'9'
        |'&'
        |'/'
        |'\\'
        |'.'
        |'_'
        )*; //don't support chinese

String    :    ('"' .+ '"')|('\'' .+ '\'');

INT    :    ('1'..'9')('0'..'9')*
    ;

WS   
    :  (' '|'\r'|'\t'|'\u000C'|'\n') {$channel=HIDDEN;}
    ;

第三步:定义命令行参数解析实体类

namespace Rz.CmdLine
{

    //命令行对象
    public class CmdLine {

        //可执行文件的文件名

        public string ExeFile { get; set; }

        private List<Switch> _cmdSwitchs = new List<Switch>();

        //开关对象列表

        public List<Switch> CmdSwitchs
        {
            get { return _cmdSwitchs; }
        }

        private List<string> _parameters = new List<string>();

        //命令行参数

        public List<string> Parameters
        {
            get { return _parameters; }
        }

    }

    //开关实体类

    public class Switch {

        //开关名称

        public string SwitchName { get; set; }

       //开关参数

        private List<string> _parameters = new List<string>();

        public List<string> Parameters
        {
            get { return _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();
}

第七步:查看程序运行结果

image

 

源代码稍后会放到google开源库中,由于codeplex的速度太慢,实在无法忍受,希望有一天微软在Internet上跟上google的脚步。

 

参考:

CodePlex上一个开源参数解析类库

以-标明选项

短选项,-sHello,其中-是标志位,s是选项,Hello是参数。或者-s Hello,这个差别在于选项与参数之间使用空格分割。

多个短选项:-x -y -z -w 或者-xyzw 或者-xy -zw

长选项:--long Hello 或者--long=hello

一个开源的Unix风格的参数解析

 


posted @ 2009-03-29 01:38  Roland  阅读(2242)  评论(2编辑  收藏  举报