解析每行以特定符号开头的块

时间:2016-03-03 09:46:55

标签: parsing yacc lalr

我需要解析一段看起来像这样的代码:

* Block
| Line 1
| Line 2
| ...

很容易做到:

block : head lines;
head  : '*' line;
lines : lines '|' line
      | '|' line
      ;

现在我想知道,如何添加嵌套块,例如:

* Block
| Line 1
| * Subblock
| | Line 1.1
| | ...
| Line 2
| ...

这可以表示为LALR语法吗?

当然,我可以解析顶级块,然后再次运行我的解析器来处理这些顶级块。但是,我只是在学习这个主题,所以对我来说避免这种方法很有意思。

2 个答案:

答案 0 :(得分:3)

嵌套块语言不是无上下文[注2],因此无法使用LALR(k)解析器进行解析。

然而,嵌套的括号语言当然是无上下文的,通过替换词法扫描程序中的初始 | 序列,将输入转换为括号形式相对容易。转型很简单:

  • | 的初始序列比上一行长时,插入BEGIN_BLOCK。 (初始序列必须正好一个 | ;否则可能是语法错误。)

  • | 的初始序列比前一行短时,插入足够的END_BLOCK以使预期长度达到正确的值。

  • | 本身不会传递给解析器。

这与用于解析布局感知语言(如Python和Haskell)的INDENT / DEDENT策略非常相似。主要的区别在于,我们不需要一堆缩进级别。

完成转换后,语法将如下所示:

content: /* empty */
       | content line
       | content block
block  : head BEGIN_BLOCK content END_BLOCK
       | head
head   : '*' line

flex实现的大致轮廓如下:(见下面注1)。

%x INDENT CONTENT
%%
  static int depth = 0, new_depth = 0;
  /* Handle pending END_BLOCKs */
  send_end:
    if (new_depth < depth) {
      --depth;
      return END_BLOCK;
  }
^"|"[[:blank:]]*   { new_depth = 1; BEGIN(INDENT); }
^.                 { new_depth = 0; yyless(0); BEGIN(CONTENT);
                     goto send_end; }
^\n                /* Ignore blank lines */
<INDENT>{
  "|"[[:blank:]]*  ++new_depth;
  .                { yyless(0); BEGIN(CONTENT);
                     if (new_depth > depth) {
                       ++depth;
                       if (new_depth > depth) { /* Report syntax error */ }
                       return BEGIN_BLOCK;
                     } else goto send_end;
                   }
  \n               BEGIN(INITIAL); /* Maybe you care about this blank line? */
}
  /* Put whatever you use here to lexically scan the lines */
<CONTENT>{
  \n               BEGIN(INITIAL);
}

注意:

  1. 并非所有人都对goto感到满意,但它可以节省一些代码重复。状态变量(depthnew_depth)是本地static变量的事实使得词法分析器不可重入且不可重新启动(在出错之后)。这只对玩具代码有用;对于任何真实的东西,你应该让词法扫描器重新进入并将状态变量放入extra数据结构。

  2. 术语“无上下文”和“上下文敏感”是语法的技术描述,因此有点误导。基于单词似乎意味着什么的直觉通常是错误的。上下文敏感性的一个非常常见的来源是一种语言,其中有效性取决于产生相同令牌序列的相同非终端的两个不同派生。 (假设非终端可以导出多个令牌序列;否则,可以消除非终端。)

    在普通编程语言中有很多这种上下文敏感的例子;通常,语法将允许这些构造,并且稍后将在一些语义分析阶段执行检查。这些包括要求声明标识符(IDENTIFIER的两个派生产生相同的字符串)或要求使用正确数量的参数调用函数(这里,只需要推导的长度)非终端匹配,但这足以触发上下文敏感度。)

    在这种情况下,要求是连续行中可能被称为bar-prefix的两个实例产生相同的 | 字符串。在这种情况下,由于效果确实是语法上的,因此推迟到后来的语义分析会使解析失败。上下文敏感性的其他例子是“句法上的”还是“语义上的”是一场辩论,它产生了惊人的热量而没有对讨论产生太多的影响。

答案 1 :(得分:0)

这个答案实际上是一个评论(......我的评论难以阅读)

如果你编写一个明确的块尾令牌,事情会变得更加清晰

import urllib.request as urllib
from http.cookiejar import CookieJar
from os.path import isfile
from os.path import join as joinPath
from sys import exc_info
from traceback import print_tb
from urllib.parse import urlencode

# constant
APPLICATION_PATH = '/srv/path/'
ALERT_POINT_PATH = joinPath(APPLICATION_PATH, 'alert_contact')
URL_REQUEST_TIMEOUT = 42

SMS_BOX_URL = 'xx.xxxx.xxx.xxx'


def initWebConnection():  # init web connection
    response = 0
    initUrlLibResponse = initUrlLib()  # init urllib
    if initUrlLibResponse:
        response = 1

    return response


def initUrlLib():  # init urllib
    response = 0
    try:
        cookieJar = CookieJar()  # cookies
        opener = urllib.build_opener(urllib.HTTPCookieProcessor(cookieJar))
        urllib.install_opener(opener)
    except Exception as e:
        response = 1

    # ex_type, ex, tb = exc_info()
    return response


def urlRequest(url, data=None):  # make url request
    contentResponse = None
    try:
        request = None
        if data:
            dataRequest = urlencode(data)
            dataRequest = dataRequest.encode('UTF-8')
            request = urllib.Request(url, dataRequest)
        else:
            request = urllib.Request(url)
        response = urllib.urlopen(url=request, timeout=URL_REQUEST_TIMEOUT)  # make request

        # get response
        contentResponse = response.read()
    except Exception as e:
        contentResponse = None

    # ex_type, ex, tb = exc_info()
    return contentResponse


try:
    evt.data = 'Some name'

    # check production state
    isInProduction = False
    if evt.prodState == 1000:
        isInProduction = True

    if isInProduction:
        initWebConnection()

        # check alert point'
        if isfile(ALERT_POINT_PATH):
            alertContactContent = None
            with open(ALERT_POINT_PATH, 'r') as alertContactFile:
                alertContactContent = alertContactFile.read()
            alertContactContent = alertContactContent.splitlines()

            if alertContactContent:
                evt.summary = '#[ DNS:  ALERT ]#  {}'.format(evt.summary)

                for alertContactContentLine in alertContactContent:
                    webRequestData = dict(
                        ## TO DO: set the url parameters appropriately
                        phone=alertContactContentLine,
                        message='NEW ALERT: {}'.format(evt.ipAddress),
                    )
                    webRequestResponse = urlRequest(SMS_BOX_URL, webRequestData)
        else:
            evt.summary = '#[ ERROR: SMS ALERT NO CONTACT ]#  {}'.format(evt.summary)
except Exception as e:
    ex_type, ex, tb = exc_info()
    print('\n #[ERROR]#exception: {ex}\n'.format(ex=e))
    print('\n #[ERROR]#exception traceback: {trace}\n'.format(trace=print_tb(tb)))

    evt.summary = '#[ DNS:ERROR traceback in event message ]#  {}'.format(evt.summary)
    evt.message = '#[ DNS:ERROR ex_type:\n {} \nex: {} \n traceback:\n {} \n]#  {}'.format(ex_type, ex,
                                                                                                      print_tb(tb),
                                                                                                      evt.message)

和语法成为

*Block{
  |Line 1
  *SubBlock{
     | line 1.1
     | line 1.2
  }
  |Line 2
  |...
}