用于花括号构造的PLY解析C文件

问题描述

我想用PLY解析一些C代码。 我要提取内容如下:

{ARGUMENT1,ARGUMENT2,ARGUMENT3,ARGUMENT4}

此结构可以隐藏在更多的花括号中。

{SOME,RANDOM,STUFF {ARGUMENT1,ARGUMENT4},SOME,MORE,STUFF }

目前,我能够为要提取ARGUMENT1,ARGUMENT4提取词法,但前提是要唯一匹配。

    {SOME,STUFF }{Argument1,Argument2,Argument3,Argument4}

这是我当前方法失败的地方,因为上面示例的词法输出为:

ARGUMENT1,Argument4

我如何只收到以下信息:

ARGUMENT1,ARGUMENT4
Argument1,Argument4

简短说明: 我有一个条件词法分析器,它搜索左花括号以保存其位置。 对于每个新的左括号,我增加一个计数器。 对于每个右大括号,我都会减少计数器。 如果计数器为零,则将t.value设置为从最新的左括号到随后的右括号的所有元素。 我猜这应该在示例字符串中多次命中。 我认为,我无法从ccode状态切换到initial状态。

现在是我的实际代码(在本示例中,我在花括号中省略了逗号,使我更易于编程):

import ply.lex as lex
import ply.yacc as yacc

# Declare the state
states = (
  ('ccode','exclusive'),)

tokens = [
    'TEXT','CCODE'
]

# this saves all rbrace positions
# to get the inner curly brace construct you want to use first element
# text lib call should always be the inner curly brace construct
rbrace_positions = []


def t_ANY_TEXT(t):
    r'\w+'
    t.value = str(t.value)
    return t


# Match the first {. Enter ccode state.
def t_ccode(t):
    r'\{'
    t.lexer.code_start = t.lexer.lexpos        # Record the starting position
    print(t.lexer.code_start)
    t.lexer.level = 1                          # Initial brace level
    t.lexer.begin('ccode')                     # Enter 'ccode' state


def t_lbrace(t):
    r'\{'
    t.lexer.level += 1


def t_rbrace(t):
    r'\}'
    t.lexer.level -= 1


# Rules for the ccode state
def t_ccode_lbrace(t):
    r'\{'
    t.lexer.current_lbrace = t.lexer.lexpos
    t.lexer.level += 1


def t_ccode_rbrace(t):
    r'\}'
    rbrace_positions.append(t.lexer.lexpos)
    t.lexer.level -= 1

    # If closing brace,return the code fragment
    if t.lexer.level == 0:
        t.value = t.lexer.lexdata[t.lexer.current_lbrace:rbrace_positions[0]-1]
        t.type = "CCODE"
        t.lexer.lineno += t.value.count('\n')
        t.lexer.begin('INITIAL')
        for _ in reversed(rbrace_positions):
            rbrace_positions.pop()

        return t


# C or C++ comment (ignore)
def t_ccode_comment(t):
    r'(/\*(.|\n)*?\*/)|(//.*)'
    pass


# C string
def t_ccode_string(t):
    r'\"([^\\\n]|(\\.))*?\"'


# C character literal
def t_ccode_char(t):
    r'\'([^\\\n]|(\\.))*?\''


# Any sequence of non-whitespace characters (not braces,strings)
def t_ccode_nonspace(t):
    r'[^\s\{\}\'\"]+'


# Ignored characters (whitespace)
t_ccode_ignore = " \t\n"


# For bad characters,we just skip over it
def t_ccode_error(t):
    t.lexer.skip(1)


def t_error(t):
    t.lexer.skip(1)


lexer = lex.lex()
data = '''{ I DONT WANT TO RECEIVE THIS 
{THIS IS WHAT I WANT TO SEE} 
AS WELL AS I DONT WANT TO RECEIVE THIS} 
OUTSIDE OF CURLY BRACES 
{I WANT TO SEE THIS AGAIN}
'''
lexer.input(data)

for tok in lexer:
    print(tok)

数据只是一个带有简单示例的测试字符串。 但是在我的C源文件中,有一些要提取Argument1,Argument4的构造。 显然,这些C文件无法编译,但由于它们包含在其他文件中,因此无需编译。

感谢您的所有投入!

解决方法

您的描述并不十分清楚。您的示例似乎表明您要查找不包含任何子列表的支撑列表。这就是我要解决的问题。

请注意,通常不建议尝试在lexer中完成所有这些工作。 Lexers通常应该返回简单的原子标记,将其留给解析器的语法来完成将标记放在一起成为有用结构的工作。但是,如果我的用例正确无误,则可以使用lexer做到这一点。

您的代码根据深度计数器在碰到大括号时是否为0来决定是否返回CCODE令牌。但这显然不是您想要的:您不在乎括号的嵌套深度;相反,当遇到闭合括号时,您想知道它是否是最里面的括号。您不需要堆栈,因为您只需要最后一个打开的括号读取的位置,并且只在未关闭括号时才需要。因此,每次看到开口撑杆时,都要设置最后一个开口撑杆位置,当看到闭合撑杆时,请检查是否设置了最后一个开口撑杆位置。如果是,则可以返回该位置之后的字符串,并将最后一个右括号位置设置为None。如果未设置,则继续扫描。

这是一个基于您的代码的简化示例:

import ply.lex as lex
  
# Declare the state
states = (
  ('ccode','exclusive'),)

tokens = [
    'TEXT','CCODE'
]
# Changed from t_ANY_TEXT because otherwise you get all the text inside 
# braces as well. Perhaps that's what you wanted but it makes the output less
# clear.
def t_TEXT(t):
    r'\w+'
    t.value = str(t.value)
    return t


# Match the first {. Enter ccode state.
def t_ccode(t):
    r'\{'
    t.lexer.current_open = t.lexer.lexpos      # Record the starting position
    t.lexer.level = 1                          # Initial brace level
    t.lexer.begin('ccode')                     # Enter 'ccode' state

# t_lbrace and t_rbrace deleted because they never match

# Rules for the ccode state
def t_ccode_lbrace(t):
    r'\{'
    t.lexer.current_open = t.lexer.lexpos
    t.lexer.level += 1

def t_ccode_rbrace(t):
    r'\}'
    t.lexer.level -= 1
    if t.lexer.level == 0:
        t.lexer.begin('INITIAL')
    if t.lexer.current_open is not None:
        t.value = t.lexer.lexdata[t.lexer.current_open:t.lexer.lexpos - 1]
        t.type = "CCODE"
        t.lexer.current_open = None
        return t

# C or C++ comment (ignore)
def t_ccode_comment(t):
    r'(/\*(.|\n)*?\*/)|(//.*)'

# C string
def t_ccode_string(t):
    r'\"([^\\\n]|(\\.))*?\"'

# C character literal
def t_ccode_char(t):
    r'\'([^\\\n]|(\\.))*?\''

# Any sequence of non-whitespace characters (not braces,strings)
def t_ccode_nonspace(t):
    r'''[^\s{}'"]+''' # No need to escape inside a character class

# Ignored characters (whitespace)
t_ccode_ignore = " \t\n"

# For bad characters,we just skip over it
def t_ccode_error(t):
    t.lexer.skip(1)

def t_error(t):
    t.lexer.skip(1)

lexer = lex.lex()
data = '''{ I DONT WANT TO RECEIVE THIS 
{THIS IS WHAT I WANT TO SEE} 
AS WELL AS I DONT WANT TO RECEIVE THIS} 
OUTSIDE OF CURLY BRACES 
{I WANT TO SEE THIS AGAIN}
'''
lexer.input(data)

for tok in lexer:
    print(tok)

样品运行:

$ python3 nested_brace.py 
LexToken(CCODE,'THIS IS WHAT I WANT TO SEE',1,58)
LexToken(TEXT,'OUTSIDE',102)
LexToken(TEXT,'OF',110)
LexToken(TEXT,'CURLY',113)
LexToken(TEXT,'BRACES',119)
LexToken(CCODE,'I WANT TO SEE THIS AGAIN',152)