• 【转】Vczh Library++3.0之可配置语法分析器(分析器内核)


    从网上无意间看到这个系列的文章,作者非常有想法,转下来慢慢研究,好好学习。   祝大家学习愉快,做自己的爱好 ^_^ !

    上一篇文章中,我们已经实现了在C++中直接写文法并转换成文法树的工作,现在要开始实现三个组合文法了。

        文法树的定义是递归的,所以我们的文法树在分析输入的时候也是递归的。每个文法树都由更小的文法树组成,因此每一个文法树在分析输入的时候都需要调用更小的文法树的分析过程。因为连接、分支和循环都很像,因此下面我的描述性文字会少一些。

        下面是连接的代码:

     1         template<typename I, typename O1, typename O2>
     2         class _Seq : public Combinator<I, ParsingPair<O1, O2>>
     3         {
     4         protected:
     5             typename Combinator<I, O1>::Ref        first;
     6             typename Combinator<I, O2>::Ref        second;
     7         public:
     8             _Seq(const typename Combinator<I, O1>::Ref& _first, const typename Combinator<I, O2>::Ref& _second)
     9                 :first(_first)
    10                 ,second(_second)
    11             {
    12             }
    13 
    14             ParsingResult<OutputType> Parse(InputType& input, GlobalInfoType& globalInfo)const
    15             {
    16                 if(ParsingResult<O1> firstResult=first->Parse(input, globalInfo))
    17                 {
    18                     if(ParsingResult<O2> secondResult=second->Parse(input, globalInfo))
    19                     {
    20                         return ParsingResult<OutputType>(ParsingPair<O1, O2>(firstResult.Value(), secondResult.Value()));
    21                     }
    22                 }
    23                 return ParsingResult<OutputType>();
    24             }
    25         };


        让我们看Parse函数。首先用左子树(first)分析输入,如果成功则接着使用右子树进行分析并返回结构,否则返回空。这非常符合文法树的定义,两个输入的连接必须是两个输入以指定的顺序先后出现。因此对于A的合法输入i,B的合法输入j,A+B的合法输入就是ij。至于这里的globalInfo参数是用来存放错误信息的。

        接下来看分支:

     1         template<typename I, typename O>
     2         class _Alt : public Combinator<I, O>
     3         {
     4         protected:
     5             typename Combinator<I, O>::Ref        first;
     6             typename Combinator<I, O>::Ref        second;
     7         public:
     8             _Alt(const typename Combinator<I, O>::Ref& _first, const typename Combinator<I, O>::Ref& _second)
     9                 :first(_first)
    10                 ,second(_second)
    11             {
    12             }
    13 
    14             ParsingResult<OutputType> Parse(InputType& input, GlobalInfoType& globalInfo)const
    15             {
    16                 GlobalInfoType firstInfo;
    17                 InputType firstInput=input;
    18                 if(ParsingResult<OutputType> firstResult=first->Parse(firstInput, firstInfo))
    19                 {
    20                     input=firstInput;
    21                     globalInfo.Append(firstInfo);
    22                     return firstResult;
    23                 }
    24 
    25                 GlobalInfoType secondInfo;
    26                 InputType secondInput=input;
    27                 if(ParsingResult<OutputType> secondResult=second->Parse(secondInput, secondInfo))
    28                 {
    29                     input=secondInput;
    30                     globalInfo.Append(secondInfo);
    31                     return secondResult;
    32                 }
    33 
    34                 if(firstInput>=secondInput)
    35                 {
    36                     input=firstInput;
    37                     globalInfo.Append(firstInfo);
    38                 }
    39                 else
    40                 {
    41                     input=secondInput;
    42                     globalInfo.Append(secondInfo);
    43                 }
    44                 return ParsingResult<OutputType>();
    45             }
    46         };


        分支比较复杂。因为左右只要有一个成立了就代表输入成立,因此在输入的时候我们需要先将输入的状态保存下来,然后才开始左子树的分析。成功了就直接返回,失败了就使用右子树进行分析。如果都失败了,那我们就要看看究竟谁出错的地方比较晚,就返回谁的错误。每个函数在Parse的时候,都会在输入发生错误的时候停下来,这个时候不会进行输入的恢复,因为这个位置代表了第一个出错的位置。

        最后是循环:

     1         template<typename I, typename O>
     2         class _Loop : public Combinator<I, ParsingList<O>>
     3         {
     4         protected:
     5             typename Combinator<I, O>::Ref        element;
     6             int                                    min;
     7             int                                    max;
     8         public:
     9             _Loop(const typename Combinator<I, O>::Ref _element, int _min, int _max=-1)
    10                 :element(_element)
    11                 ,min(_min)
    12                 ,max(_max)
    13             {
    14             }
    15 
    16             ParsingResult<OutputType> Parse(InputType& input, GlobalInfoType& globalInfo)const
    17             {
    18                 ParsingList<O> result;
    19                 InputType elementInput=input;
    20                 GlobalInfoType elementInfo;
    21 
    22                 while(max==-1 || result.Count()<max)
    23                 {
    24                     ParsingResult<O> elementResult=element->Parse(elementInput, elementInfo);
    25                     if(elementResult)
    26                     {
    27                         result.Append(elementResult.Value());
    28                         input=elementInput;
    29                         globalInfo.Append(elementInfo);
    30                         elementInfo.errors.Clear();
    31                     }
    32                     else
    33                     {
    34                         break;
    35                     }
    36                 }
    37 
    38                 if(result.Count()>=min)
    39                 {
    40                     globalInfo.CandidateAppend(elementInfo);
    41                     return ParsingResult<OutputType>(result);
    42                 }
    43                 else
    44                 {
    45                     input=elementInput;
    46                     globalInfo.Append(elementInfo);
    47                     return ParsingResult<OutputType>();
    48                 }
    49             }
    50         };


        代码比较长主要是考虑到分析可以指定下界和上界,也就是最少循环次数和最大循环次数。如果上限是-1则表示没有上限。于是在这里要将最后一次输入的错误保存到另一个地方,如果是在循环不下去了,就要看循环的次数是否在允许的范围内。不允许就要记录错误了,

        其实文法有这三种元素是远远不够的,因为这三种元素都是组合元素,最根本的是我们要判断输入的一个记号(或者字符)是否满足某个条件,满足则吃掉并往下走。这种操作叫Unit:

     1         template<typename I>
     2         class _Unit : public Combinator<I, typename I::ElementType>
     3         {
     4             typedef typename I::ElementType        ElementType;
     5         protected:
     6             ElementType            baseline;
     7             WString                errorMessage;
     8         public:
     9             _Unit(const ElementType& _baseline, const WString _errorMessage=WString::Empty)
    10                 :baseline(_baseline)
    11                 ,errorMessage(_errorMessage)
    12             {
    13             }
    14 
    15             ParsingResult<OutputType> Parse(InputType& input, GlobalInfoType& globalInfo)const
    16             {
    17                 if(input.Available() && input.Current()==baseline)
    18                 {
    19                     ParsingResult<OutputType> result(input.Current());
    20                     input.Next();
    21                     return result;
    22                 }
    23                 else
    24                 {
    25                     globalInfo.errors.Add(new ErrorType(errorMessage, input));
    26                     return ParsingResult<OutputType>();
    27                 }
    28             }
    29         };


        这里的行为还是相对简单的,而且Unit也是现在展示的4个操作里面,唯一产生了一个具体的错误信息的文法元素。于是相似的我们可以有当一个函数返回true的时候才往下走,或者当分析出错的时候让一个函数来生成具体的错误信息或者假装成功往下走,或者当分析成功的时候再用一个函数来讲结果转换成一个目标语法树的节点。这种琐碎的操作就比较多了。这里已经把文法分析其介绍的差不多了,下一篇文章我们将以文法分析其作为例子,讲一下FpMacro的实现并放出可以编译的Demo。

  • 相关阅读:
    聚焦LSMIMO的四大层面,浅谈5G关键技术
    基于LiteOS Studio零成本学习LiteOS物联网操作系统
    使用LiteOS Studio图形化查看LiteOS在STM32上运行的奥秘
    GaussDB(DWS)应用实践丨负载管理与作业排队处理方法
    GaussDB(DWS)磁盘维护:vacuum full执行慢怎么办?
    从物理空间到数字世界,数字孪生打造智能化基础设施
    Lab 4 : OpenFlow
    SDN控制器拓扑发现(一)
    pxe dhcp
    RyuBook1.0案例二:Traffic Monitor项目源码分析
  • 原文地址:https://www.cnblogs.com/xuangong/p/2122665.html
Copyright © 2020-2023  润新知