2017-07-16 95 views
1

我有一个java程序,必须解析python setup.py文件以从中提取信息。我有些东西在工作,但我撞到了墙上。我首先从一个简单的原始文件开始,一旦我运行,然后我会担心会剥离出我不想让它反映实际文件的噪音。Antlr解析python安装文件

因此,这里是我的语法

grammar SetupPy ; 

file_input: (NEWLINE | setupDeclaration)* EOF; 

setupDeclaration : 'setup' '(' method ')'; 
method : setupRequires testRequires; 
setupRequires : 'setup_requires' '=' '[' LISTVAL* ']' COMMA; 
testRequires : 'tests_require' '=' '[' LISTVAL* ']' COMMA; 

WS: [ \t\n\r]+ -> skip ; 
COMMA : ',' -> skip ; 
LISTVAL : SHORT_STRING ; 

UNKNOWN_CHAR 
: . 
; 

fragment SHORT_STRING 
: '\'' (STRING_ESCAPE_SEQ | ~[\\\r\n\f'])* '\'' 
| '"' (STRING_ESCAPE_SEQ | ~[\\\r\n\f"])* '"' 
; 

/// stringescapeseq ::= "\" <any source character> 
fragment STRING_ESCAPE_SEQ 
: '\\' . 
| '\\' NEWLINE 
; 

fragment SPACES 
: [ \t]+ 
; 

NEWLINE 
: ({atStartOfInput()}? SPACES 
    | ('\r'? '\n' | '\r' | '\f') SPACES? 
    ) 
    { 
    String newLine = getText().replaceAll("[^\r\n\f]+", ""); 
    String spaces = getText().replaceAll("[\r\n\f]+", ""); 
    int next = _input.LA(1); 
    if (opened > 0 || next == '\r' || next == '\n' || next == '\f' || next == '#') { 
     // If we're inside a list or on a blank line, ignore all indents, 
     // dedents and line breaks. 
     skip(); 
    } 
    else { 
     emit(commonToken(NEWLINE, newLine)); 
     int indent = getIndentationCount(spaces); 
     int previous = indents.isEmpty() ? 0 : indents.peek(); 
     if (indent == previous) { 
     // skip indents of the same size as the present indent-size 
     skip(); 
     } 
     else if (indent > previous) { 
     indents.push(indent); 
     emit(commonToken(Python3Parser.INDENT, spaces)); 
     } 
     else { 
     // Possibly emit more than 1 DEDENT token. 
     while(!indents.isEmpty() && indents.peek() > indent) { 
      this.emit(createDedent()); 
      indents.pop(); 
     } 
     } 
    } 
    } 
; 

和我目前的测试文件(就像我说的,从一个普通的文件剥离噪音下一步)

setup(
    setup_requires=['pytest-runner'], 
    tests_require=['pytest', 'unittest2'], 
) 

我在哪里卡住是如何告诉antlr setup_requires和tests_requires包含数组。我想要这些数组的值,无论是否有人使用单引号,双引号,不同行上的每个值以及上述所有组合。我不知道如何解决这个问题。我可以得到一些帮助吗?也许是一个例子或两个?

需要注意的事项,

  1. 不,我不能用Jython和公正运行该文件。
  2. 正则表达式是不是一种选择,由于在开发样式文件

当然,本次发行后,我还需要弄清楚如何从一个普通的文件剥离噪声和巨大的变化。我尝试使用Python3语法来做到这一点,但我在antlr上是个新手,它把我吹走了。我无法弄清楚如何编写规则来拉取值,所以我决定尝试一个更简单的语法。并迅速撞上另一堵墙。

编辑 这里是一个实际的setup.py文件,它最终必须解析。请记住setup_requires和test_requires可能会或可能不会在那里,并且可能会或可能不会按此顺序。

# -*- coding: utf-8 -*- 
from __future__ import with_statement 

from setuptools import setup 


def get_version(fname='mccabe.py'): 
    with open(fname) as f: 
     for line in f: 
      if line.startswith('__version__'): 
       return eval(line.split('=')[-1]) 


def get_long_description(): 
    descr = [] 
    for fname in ('README.rst',): 
     with open(fname) as f: 
      descr.append(f.read()) 
    return '\n\n'.join(descr) 


setup(
    name='mccabe', 
    version=get_version(), 
    description="McCabe checker, plugin for flake8", 
    long_description=get_long_description(), 
    keywords='flake8 mccabe', 
    author='Tarek Ziade', 
    author_email='[email protected]', 
    maintainer='Ian Cordasco', 
    maintainer_email='[email protected]', 
    url='https://github.com/pycqa/mccabe', 
    license='Expat license', 
    py_modules=['mccabe'], 
    zip_safe=False, 
    setup_requires=['pytest-runner'], 
    tests_require=['pytest'], 
    entry_points={ 
     'flake8.extension': [ 
      'C90 = mccabe:McCabeChecker', 
     ], 
    }, 
    classifiers=[ 
     'Development Status :: 5 - Production/Stable', 
     'Environment :: Console', 
     'Intended Audience :: Developers', 
     'License :: OSI Approved :: MIT License', 
     'Operating System :: OS Independent', 
     'Programming Language :: Python', 
     'Programming Language :: Python :: 2', 
     'Programming Language :: Python :: 2.7', 
     'Programming Language :: Python :: 3', 
     'Programming Language :: Python :: 3.3', 
     'Programming Language :: Python :: 3.4', 
     'Programming Language :: Python :: 3.5', 
     'Programming Language :: Python :: 3.6', 
     'Topic :: Software Development :: Libraries :: Python Modules', 
     'Topic :: Software Development :: Quality Assurance', 
    ], 
) 

试图调试和简化和实现我不需要找到方法,只是值。所以我正在玩这个语法

grammar SetupPy ; 

file_input: (ignore setupRequires ignore | ignore testRequires ignore)* EOF; 

setupRequires : 'setup_requires' '=' '[' dependencyValue* (',' dependencyValue)* ']'; 
testRequires : 'tests_require' '=' '[' dependencyValue* (',' dependencyValue)* ']'; 

dependencyValue: LISTVAL; 

ignore : UNKNOWN_CHAR? ; 

LISTVAL: SHORT_STRING; 
UNKNOWN_CHAR: . -> channel(HIDDEN); 

fragment SHORT_STRING: '\'' (STRING_ESCAPE_SEQ | ~[\\\r\n\f'])* '\'' 
| '"' (STRING_ESCAPE_SEQ | ~[\\\r\n\f"])* '"'; 

fragment STRING_ESCAPE_SEQ 
: '\\' . 
| '\\' 
; 

很适合简单的,甚至处理乱序问题。但完整的文件犯规的工作,它被挂在

def get_version(fname='mccabe.py'):

等于在该行的标志。

+0

您有机会评估我的解决方案吗? – TomServo

+0

我终于明白了这一点。不幸的是它打破了一个实际的文件。它拿起进口声明,并且全是古怪的。我确实发布了一个实际需要解析的文件的例子。在我放弃之前,我会继续玩这个游戏,然后用一种不那么优雅的方式来解决这个问题。我没时间了。 – scphantm

+0

是的,解析这个有点多,但是你的UNKNOWN_CHAR符号有问题。几乎所有的东西都不是隐含的词法分析器,它强烈地依赖于这个规则。 – TomServo

回答

1

我检查了你的语法并简化了一下。我拿出所有的python-esqe空白处理,并将空白视为空格。这个语法也解析了这个输入,正如你在问题中所说的那样,每行处理一个项目,单引号和双引号等等。

setup(
    setup_requires=['pytest-runner'], 
    tests_require=['pytest', 
    'unittest2', 
    "test_3" ], 
) 

而这里的大大简化的语法:

grammar SetupPy ; 
setupDeclaration : 'setup' '(' method ')' EOF; 
method : setupRequires testRequires ; 
setupRequires : 'setup_requires' '=' '[' LISTVAL* (',' LISTVAL)* ']' ',' ; 
testRequires : 'tests_require' '=' '[' LISTVAL* (',' LISTVAL)* ']' ',' ; 
WS: [ \t\n\r]+ -> skip ; 
LISTVAL : SHORT_STRING ; 
fragment SHORT_STRING 
: '\'' (STRING_ESCAPE_SEQ | ~[\\\r\n\f'])* '\'' 
| '"' (STRING_ESCAPE_SEQ | ~[\\\r\n\f"])* '"' 
; 
fragment STRING_ESCAPE_SEQ 
: '\\' . 
| '\\' 
; 

哦,这里的显示标记的正确分配的解析器词法分析器输出:

[@0,0:4='setup',<'setup'>,1:0] 
[@1,5:5='(',<'('>,1:5] 
[@2,12:25='setup_requires',<'setup_requires'>,2:4] 
[@3,26:26='=',<'='>,2:18] 
[@4,27:27='[',<'['>,2:19] 
[@5,28:42=''pytest-runner'',<LISTVAL>,2:20] 
[@6,43:43=']',<']'>,2:35] 
[@7,44:44=',',<','>,2:36] 
[@8,51:63='tests_require',<'tests_require'>,3:4] 
[@9,64:64='=',<'='>,3:17] 
[@10,65:65='[',<'['>,3:18] 
[@11,66:73=''pytest'',<LISTVAL>,3:19] 
[@12,74:74=',',<','>,3:27] 
[@13,79:89=''unittest2'',<LISTVAL>,4:1] 
[@14,90:90=',',<','>,4:12] 
[@15,95:102='"test_3"',<LISTVAL>,5:1] 
[@16,104:104=']',<']'>,5:10] 
[@17,105:105=',',<','>,5:11] 
[@18,108:108=')',<')'>,6:0] 
[@19,109:108='<EOF>',<EOF>,6:1] 

enter image description here

现在你应该能够遵循简单的ANTLR访客或听众模式来抓取你的LISTVAL令牌,并与他们做你的事情。我希望这能满足你的需求。它当然可以很好地解析你的测试输入,等等。

+1

也许这也是一个upvote?谢谢,我们都知道在这些慢速标签中rep是多么的难。 :) – TomServo