PLY 解析 C 文件以获得大括号构造

PLY Parse C Files for curly brace construct

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

{ARGUMENT1, ARGUMENT2, ARGUMENT3, ARGUMENT4}

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

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

目前我可以对我想提取的结构进行 lex ARGUMENT1, ARGUMENT2, ARGUMENT3, ARGUMENT4 但前提是它是唯一匹配的。

    {SOME, RANDOM, STUFF {ARGUMENT1, ARGUMENT2, ARGUMENT3, ARGUMENT4}, SOME, MORE, RANDOM, STUFF }{Argument1, Argument2, Argument3, Argument4}

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

ARGUMENT1, ARGUMENT2, ARGUMENT3, ARGUMENT4}, SOME, MORE, RANDOM, STUFF }{Argument1, Argument2, Argument3, Argument4

如何才能只收到以下内容:

ARGUMENT1, ARGUMENT2, ARGUMENT3, ARGUMENT4
Argument1, Argument2, Argument3, 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, Argument2, Argument3, Argument4。 显然那些 C 文件不会编译,但没有必要,因为它们包含在其他一些文件中。

感谢您的所有意见!

你的描述不是很清楚。你的例子似乎表明你想找到一个不包含任何子列表的大括号列表。这就是我要解决的问题。

请注意,通常不建议尝试在词法分析器中完成所有这些工作。词法分析器通常应该 return 简单的原子标记,将其留给解析器的语法来完成将标记组合成有用结构的工作。但是,如果我的用例正确,则可以使用词法分析器执行此操作。

你的代码决定是否 return 一个 CCODE 标记是基于当深度计数器到达右括号时它是否为 0。但这显然不是您想要的:您不关心大括号嵌套的深度;相反,当遇到右大括号时,您想知道它是否是最里面的大括号。你不需要一个堆栈,因为你只需要最后一次打开大括号读取的位置,而且你只需要在它未关闭时使用它。因此,每次看到左大括号时,都会设置最后一个大括号的位置,而当看到右大括号时,则检查是否设置了最后一个大括号的位置。如果是,您可以 return 从该位置开始的字符串并将最后一个左大括号位置设置为 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',1,102)
LexToken(TEXT,'OF',1,110)
LexToken(TEXT,'CURLY',1,113)
LexToken(TEXT,'BRACES',1,119)
LexToken(CCODE,'I WANT TO SEE THIS AGAIN',1,152)