• 大家来找错自己写个正则引擎(三)构建正则解析树及分词


      第一节介绍过RegexNode类,它的SplitWords用来分词,大致逻辑就是遍历要分词的字符串,不断的调用自身的Parse属性指向的委托,Parse属性指向一个bool ParseFunc(string input, int index, out string output)类型的委托,其中input表示要解析的字符串,index表示其实的解析位置,返回值表示否则匹配成功,output表示匹配成功是匹配到的字符串。如果本次匹配没有匹配到输出,就把当前字符当成一个独立的匹配结果,然后扫描指针向后移动,继续遍历扫描解析。

      具体代码灰常简单,如下。

    public delegate bool ParseFunc(string input, int index, out string output);
    public class RegexNode {
        public RegexNode() {
            Nodes = new List<RegexNode>();
        }
        public RegexNode(ParseFunc func) {
            Parse = func;
            Nodes = new List<RegexNode>();
        }
        public List<RegexNode> Nodes { get; set; }
        public ParseFunc Parse { get; set; }
        public Releation Releation { get; set; }
        public string Text { get; set; }

        public string[] SplitWords(string input) {
            List<string> result = new List<string>();
            int index = 0;

            while (index < input.Length) {
                string output = null;
                if(Parse(input, index, out output))
                {
                    index += output.Length;
                    result.Add(output);
                }
                else
                {
                    if (index < input.Length) {
                        result.Add(input.Substring(index, 1));
                    }
                    index ++;
                }
            }
            return result.ToArray();
        }

    }

      其实把抽象模式树转换成正则解析树的代码也很简单,因为都是树与数的转换,用一个简单的递归调用对源树进行深度优先遍历就行了,遍历的过程中根据模式树中提供的信息,调用解析方法工厂ParseFuncFactory的相关方法得到该子模式的解析函数,并赋值给目标树节点的Parse属性。代码中要考虑当前节点没有子节点的情况,如果是匹配一个或多个就用OneOrMoreMaxMatch,否则用MaxMatch,如果有子节点,要考察当前节点的Releation,然后进一步确定调用ParseFuncFactory的哪个方法,代码很短,如下。

    public static RegexNode GetParseNode(PatternNode rootPatt) {
        RegexNode root = new RegexNode();
        root.Releation = rootPatt.Releation;
        root.Text = rootPatt.Text;
        if (rootPatt.Nodes.Count == 0) {
            if (rootPatt.OneOrMore) {
                string toMatch = rootPatt.Text.TrimEnd(new[]{'*',')'});
                toMatch = toMatch.TrimStart('(');
                root.Parse = ParseFuncFactory.OneOrMoreMaxMatch(toMatch);
            }
            else {
                root.Parse = ParseFuncFactory.MaxMatch(rootPatt.Text);
            }
            return root;
        }

        foreach (var pt in rootPatt.Nodes) {
            var node = GetParseNode(pt);
            root.Nodes.Add(node);
        }

        if (rootPatt.Releation == Releation.And)
        {
            if (rootPatt.OneOrMore) {
                root.Parse = ParseFuncFactory.MatchOneOrMoreWithAnd(root.Nodes);
            }
            else {
                root.Parse = ParseFuncFactory.MatchAnd(root.Nodes);
            }
        }
        else if (rootPatt.Releation == Releation.Or) {
            if (rootPatt.OneOrMore) {
                root.Parse = ParseFuncFactory.MatchOneOrMoreWithOr(root.Nodes);
            }
            else {
                root.Parse = ParseFuncFactory.MatchOr(root.Nodes);
            }
        }
        return root;
    }

    这部分代码比较简洁,比我预想的还简单,可见高阶函数的应用确实能使某些问题的解决变得简单,应该抽空仔细看看SICP了。这部分代码应该可挑剔的地方不多吧,大家也再找找。

  • 相关阅读:
    [thinkphp] 是如何输出一个页面的
    [thinkphp] 获取根目录绝对路径
    onethink 插件模板定位
    win7 安全模式开启声音
    百度贴吧楼层评论地址
    第一天问题
    [php] 解析JSON字符串
    NDK编译时两 .so之间调用问题
    CDN问题积累
    C++模板特化
  • 原文地址:https://www.cnblogs.com/onlytiancai/p/1747554.html
Copyright © 2020-2023  润新知