Python - 词法分析与分词

12 投票
5 回答
15281 浏览
提问于 2025-04-15 19:53

我想加快我的探索过程,因为这是我第一次接触词法分析。也许我走的方向不太对。首先,我来描述一下我的问题:

我有一些非常大的属性文件(大约有1000个属性),但经过提炼后,实际上只有大约15个重要的属性,其余的可以生成或者很少变化。

举个例子:

general {
  name = myname
  ip = 127.0.0.1
}

component1 {
   key = value
   foo = bar
}

这是我想要创建的格式,用来处理像这样的内容:

property.${general.name}blah.home.directory = /blah
property.${general.name}.ip = ${general.ip}
property.${component1}.ip = ${general.ip}
property.${component1}.foo = ${component1.foo}

变成

property.mynameblah.home.directory = /blah
property.myname.ip = 127.0.0.1
property.component1.ip = 127.0.0.1
property.component1.foo = bar

词法分析和分词听起来是我最好的选择,但这只是一个非常简单的形式。这是一个简单的语法和简单的替换,我想确保我不会用大锤子去钉钉子。

我可以自己创建一个词法分析器和分词器,或者使用ANTlr,但我不想重复造轮子,而且ANTlr听起来有点过于复杂。

我对编译器技术不太熟悉,所以如果能给我一些正确的方向和代码示例,我会非常感激。

注意:我可以更改输入格式。

5 个回答

2

虽然你的格式看起来很简单,但我觉得用一个完整的解析器或者词法分析器会太复杂了。其实用一些正则表达式和字符串处理的方法就能解决问题。

另外一个想法是把文件改成像json或者xml这样的格式,然后使用现成的工具包来处理。

4

一个简单的确定性有限自动机(DFA)在这里很有效。你只需要几个状态:

  1. 在寻找 ${
  2. 已经看到 ${,现在要找至少一个有效的字符来组成名字
  3. 已经看到至少一个有效的名字字符,接下来要找更多的名字字符或者 }

如果属性文件的顺序不重要,你可能需要一个两次处理的程序来确认每个名字是否正确解析。

当然,你还需要写替换的代码,但一旦你有了所有使用的名字的列表,最简单的实现方式就是把 ${name} 找到并替换成它对应的值。

14

effbot.org 上有一篇很棒的文章,讲的是如何用正则表达式进行词法分析。

根据你的问题来调整分词器:

import re

token_pattern = r"""
(?P<identifier>[a-zA-Z_][a-zA-Z0-9_]*)
|(?P<integer>[0-9]+)
|(?P<dot>\.)
|(?P<open_variable>[$][{])
|(?P<open_curly>[{])
|(?P<close_curly>[}])
|(?P<newline>\n)
|(?P<whitespace>\s+)
|(?P<equals>[=])
|(?P<slash>[/])
"""

token_re = re.compile(token_pattern, re.VERBOSE)

class TokenizerException(Exception): pass

def tokenize(text):
    pos = 0
    while True:
        m = token_re.match(text, pos)
        if not m: break
        pos = m.end()
        tokname = m.lastgroup
        tokvalue = m.group(tokname)
        yield tokname, tokvalue
    if pos != len(text):
        raise TokenizerException('tokenizer stopped at pos %r of %r' % (
            pos, len(text)))

为了测试它,我们可以这样做:

stuff = r'property.${general.name}.ip = ${general.ip}'
stuff2 = r'''
general {
  name = myname
  ip = 127.0.0.1
}
'''

print ' stuff '.center(60, '=')
for tok in tokenize(stuff):
    print tok

print ' stuff2 '.center(60, '=')
for tok in tokenize(stuff2):
    print tok

循环:

========================== stuff ===========================
('identifier', 'property')
('dot', '.')
('open_variable', '${')
('identifier', 'general')
('dot', '.')
('identifier', 'name')
('close_curly', '}')
('dot', '.')
('identifier', 'ip')
('whitespace', ' ')
('equals', '=')
('whitespace', ' ')
('open_variable', '${')
('identifier', 'general')
('dot', '.')
('identifier', 'ip')
('close_curly', '}')
========================== stuff2 ==========================
('newline', '\n')
('identifier', 'general')
('whitespace', ' ')
('open_curly', '{')
('newline', '\n')
('whitespace', '  ')
('identifier', 'name')
('whitespace', ' ')
('equals', '=')
('whitespace', ' ')
('identifier', 'myname')
('newline', '\n')
('whitespace', '  ')
('identifier', 'ip')
('whitespace', ' ')
('equals', '=')
('whitespace', ' ')
('integer', '127')
('dot', '.')
('integer', '0')
('dot', '.')
('integer', '0')
('dot', '.')
('integer', '1')
('newline', '\n')
('close_curly', '}')
('newline', '\n')

撰写回答