匹配所有找到的令牌的问题

2024-06-25 05:35:47 发布

您现在位置:Python中文网/ 问答频道 /正文

我想为函数和特定命令解析一些C文件。 我的目标是获取所有使用特定命令的函数以及所有调用该命令的时间。 因此,我决定使用多个条件为其生成额外的令牌

以下是我的lexer和解析器代码:

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

results = []
calls = []

# Declare the state
states = (
  ('func', 'exclusive'),
  ('parameter', 'exclusive'),
  ('httpgettext', 'exclusive')
)

reserved = {
    'void': 'VOID',
    'int': 'INT',
    'uint8': 'UINT8',
    'uint16': 'UINT16',
    'uint32': 'UINT32',
    'TRet': 'TRET',
    'TBool': 'TBOOL',
    'bool': 'BOOL',
}

tokens = [
    'ID',
    'FUNC',
    'PARAMETERLIST',
    'CALL',
    'SEMICOLON'
] + list(reserved.values())

# Start of token description for INITIAL mode (inclusive)


def t_ID(t):
    r'[a-zA-Z_][a-zA-Z_0-9]*'
    t.type = reserved.get(t.value, 'ID')
    return t


# Start of token description for HttpGetText condition

def t_httpgettext_SEMICOLON(t):
    r'\;'
    t.value = t.lexer.lexdata[t.lexer.call_start:t.lexer.lexpos-1]
    t.type = 'CALL'
    t.lexer.pop_state()
    global calls
    arguments = str(t.value).split(',')
    calls.append([arguments[1], arguments[2]])


# Start of token description for parameter list condition


def t_parameter(t):
    r'\('
    t.lexer.parameter_start = t.lexer.lexpos
    t.lexer.paren_level = 1
    t.lexer.push_state('parameter')


def t_parameter_lparen(t):
    r'\('
    t.lexer.paren_level += 1


def t_parameter_rparen(t):
    r'\)'
    t.lexer.paren_level -= 1

    if t.lexer.paren_level == 0:
        t.value = t.lexer.lexdata[t.lexer.parameter_start:t.lexer.lexpos - 1]
        t.type = 'PARAMETERLIST'
        t.lexer.pop_state()
        return t


# Start of token description for function block condition


def t_func(t):
    r'\{'
    t.lexer.code_start = t.lexer.lexpos        # Record the starting position
    t.lexer.brace_level = 1                          # Initial brace level
    t.lexer.push_state('func')                     # Enter 'ccode' state


# Rules for the ccode state
def t_func_lbrace(t):
    r'\{'
    t.lexer.brace_level += 1


def t_func_rbrace(t):
    r'\}'
    t.lexer.brace_level -= 1

    if t.lexer.brace_level == 0:
        t.value = t.lexer.lexdata[t.lexer.code_start:t.lexer.lexpos - 1]
        t.type = "FUNC"
        t.lexer.lineno += t.value.count('\n')
        t.lexer.pop_state()
        return t


# Start of token description valid for all conditions

t_ANY_ignore = " \t§$%&+#-_:.<<|',\0"


def t_ANY_HttpGetText(t):
    r'HttpGetText'
    t.lexer.call_start = t.lexer.lexpos
    t.lexer.push_state('httpgettext')


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


def t_ANY_comment(t):
    r'(/\*(.|\n)*?\*/)|(//.*)'
    pass


def t_ANY_ignore_comments(t):
    r'//.*'
    pass


def t_ANY_newline(t):
    r'\n+'
    t.lexer.lineno += len(t.value)


lexer = lex.lex()


def p_statement_function(p):
    'statement : type identifier parameter function'
    p[0] = p[2]
    global results
    identifier = str(p[2])
    parameter_list = str(p[3]).replace('\n', '')
    function_block = str(p[4])
    if function_block.find('HttpGetText') != -1:
        results.append([identifier, parameter_list, function_block])
        print(identifier)
    # while True:
    #     tok = parser.token()
    #     print(tok)
    #     if not tok:
    #         break


def p_parameter_PARAMETERLIST(p):
    'parameter : PARAMETERLIST'
    p[0] = p[1]


def p_function_FUNC(p):
    'function : FUNC'
    p[0] = p[1]


def p_identifier_ID(p):
    'identifier : ID '
    p[0] = p[1]


def p_type_TBOOL(p):
    'type : TBOOL'
    p[0] = p[1]


def p_type_VOID(p):
    'type : VOID'
    p[0] = p[1]


def p_type_TRET(p):
    'type : TRET'
    p[0] = p[1]


def p_type_BOOL(p):
    'type : BOOL'


def p_type_INT(p):
    'type : INTEGER'
    p[0] = p[1]


def p_INTEGER_INT(p):
    'INTEGER : INT'
    p[0] = p[1]


def p_INTEGER_UINT8(p):
    'INTEGER : UINT8'
    p[0] = p[1]


def p_INTEGER_UINT16(p):
    'INTEGER : UINT16'
    p[0] = p[1]


def p_INTEGER_UINT32(p):
    'INTEGER : UINT32'
    p[0] = p[1]


def p_error(p):
    print('Syntax error in input: ', p)
    parser.restart()


parser = yacc.yacc()


with open('C:/Users/z0046abb/Desktop/Bachelorarbeit/TextLibraryAnalysis/test_file.txt', 'r') as f:
    read_data = f.read()

parser.parse(read_data)

print(results)
print(calls)

这是我的test_file.txt的内容:

int main(argv)
{
    HttpGetText(Arg1, Arg2, Arg3, Arg4);

    return 0
}

void func2(bla, bla, bla)
{
    something = random();
    HttpGetText(1,2,3,4);
}

void func3(bla, bla, bla)
{
    something = random();
    HttpGetText(1,21,31,4);
}

void func4(bla, bla, bla)
{
    HttpGetText(1, 22, 32, 4);
}

void func5(bla, bla, bla)
{
    something();
}

void func6(bla)
{
    HttpGetText(1, 23, 33, 4);
}

HttpGetText(1, 24, 34, 4);
HtppGetText(1, 25, 35, 4);

但不知何故,并非所有匹配项都被找到/处理。 这是测试运行的输出:

main
Syntax error in input:  LexToken(VOID,'void',12,75)
func3
Syntax error in input:  LexToken(VOID,'void',30,243)
Syntax error in input:  LexToken(VOID,'void',44,353)
[['main', 'argv', '\n    HttpGetText(Arg1, Arg2, Arg3, Arg4);\n\n    return 0\n'], ['func3', 'bla, bla, bla', '\n    something = random();\n    HttpGetText(1,21,31,4);\n']]
[[' Arg2', ' Arg3'], ['2', '3'], ['21', '31'], [' 22', ' 32'], [' 23', ' 33']]

正如您所看到的,void处有一个错误,尽管它是一个保留令牌。 我不确定问题是否出在我的lexer或解析器实现中。 如果我使用来自p_statement_function(p):的“lookahead”功能(作为注释的函数的一部分),似乎所有标记都正确标记

但是,上面的输出似乎只标识main()和func3()

此外test_file.txt的最后两行也应该追加

我的第一个想法是从t.lexer.begin(state)切换到t.lexer.push_state(state),这样我就可以返回到lexer的最后一个状态,这在这里可能会有所帮助,但似乎不是这样

现在我没有主意了。它似乎不会因为我用来存储结果的全局列表而失败(我知道全局变量有点冒险)

此外main()func3()被发现是除其他实现函数之外的拟合匹配,这一事实让我感到惊讶

如果你们中有人对我有想法,我会很高兴的

编辑: 我试图修改test_file.txt。如果每个函数之间都有一些无意义的词,我可以在全局结果列表中记录所有函数。虽然这不是我想要的解决方案


Tags: 函数tokenparametervaluedeftypefunctioninteger
1条回答
网友
1楼 · 发布于 2024-06-25 05:35:47

您面临的直接问题是,您为解析器提供的起点是statement。顾名思义,非终结符匹配单个语句(实际上是单个函数定义)。它不再匹配任何内容,因此一旦语句完成,解析器就希望看到输入的结束。因此,任何标记都将是语法错误。(其中一些错误消息被Ply的错误恢复机制抑制。)

要解决此问题,您需要添加一个start non terminal,它识别一系列语句:

program : | program statement

另一个相关的错误在你的lexer中。因为t_IDt_ANY_HttpGetText之前,所以它具有优先权。这意味着在INITIAL状态下,HttpGetText标记被识别为ID某种东西,在测试扫描仪时应该是可见的。我认为这不一定很严重,因为顶级函数调用在C中是非法的(即使在全局变量的初始化器中也是如此)。但是你可以通过重新排列这两条lexer规则来轻松修复它


在回答你之前的一个问题时,我认为这与同一个项目有关,我说:

Note that trying to do all this work in the lexer is not generally recommended. Lexers should normally return simple atomic tokens, leaving it to the parser's grammar to do the work of putting the tokens together into a useful structure.

我很遗憾没有加强这一警告。尽管正确地标记C程序似乎需要做很多工作,但实际上并没有那么困难,而且有很多例子。完整的解析是复杂的,但大多数复杂情况都与声明有关,如果不需要所有这些信息,则可以进行简化

或者,存在完整的开源C解析解决方案。使用它们有一定的学习曲线,但回报是系统地分析程序结构的灵活性,而不必沉浸在C语法的怪癖中。(如果你分析的代码是C++),这些方面就更为尖锐了。

相关问题 更多 >