from distutils.errors import *
import errno
import glob
import hashlib
import imp
import inspect
import os
import re
import shutil
import sys
import tempfile
import unittest
import antlr3
def unlink(path):
try:
os.unlink(path)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise
class GrammarCompileError(Exception):
"""Grammar failed to compile."""
pass
# At least on MacOSX tempdir (/tmp) is a symlink. It's sometimes dereferences,
# sometimes not, breaking the inspect.getmodule() function.
testbasedir = os.path.join(
os.path.realpath(tempfile.gettempdir()),
'antlr3-test')
class BrokenTest(unittest.TestCase.failureException):
def __repr__(self):
name, reason = self.args
return '{}: {}: {} works now'.format(
(self.__class__.__name__, name, reason))
def broken(reason, *exceptions):
'''Indicates a failing (or erroneous) test case fails that should succeed.
If the test fails with an exception, list the exception type in args'''
def wrapper(test_method):
def replacement(*args, **kwargs):
try:
test_method(*args, **kwargs)
except exceptions or unittest.TestCase.failureException:
pass
else:
raise BrokenTest(test_method.__name__, reason)
replacement.__doc__ = test_method.__doc__
replacement.__name__ = 'XXX_' + test_method.__name__
replacement.todo = reason
return replacement
return wrapper
dependencyCache = {}
compileErrorCache = {}
# setup java CLASSPATH
if 'CLASSPATH' not in os.environ:
cp = []
baseDir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
libDir = os.path.join(baseDir, 'lib')
jar = os.path.join(libDir, 'ST-4.0.5.jar')
if not os.path.isfile(jar):
raise DistutilsFileError(
"Missing file '{}'. Grab it from a distribution package.".format(jar)
)
cp.append(jar)
jar = os.path.join(libDir, 'antlr-3.4.1-SNAPSHOT.jar')
if not os.path.isfile(jar):
raise DistutilsFileError(
"Missing file '{}'. Grab it from a distribution package.".format(jar)
)
cp.append(jar)
jar = os.path.join(libDir, 'antlr-runtime-3.4.jar')
if not os.path.isfile(jar):
raise DistutilsFileError(
"Missing file '{}'. Grab it from a distribution package.".format(jar)
)
cp.append(jar)
cp.append(os.path.join(baseDir, 'runtime', 'Python', 'build'))
classpath = '-cp "' + ':'.join([os.path.abspath(p) for p in cp]) + '"'
else:
classpath = ''
class ANTLRTest(unittest.TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.moduleName = os.path.splitext(os.path.basename(sys.modules[self.__module__].__file__))[0]
self.className = self.__class__.__name__
self._baseDir = None
self.lexerModule = None
self.parserModule = None
self.grammarName = None
self.grammarType = None
@property
def baseDir(self):
if self._baseDir is None:
testName = 'unknownTest'
for frame in inspect.stack():
code = frame[0].f_code
codeMod = inspect.getmodule(code)
if codeMod is None:
continue
# skip frames not in requested module
if codeMod is not sys.modules[self.__module__]:
continue
# skip some unwanted names
if code.co_name in ('nextToken', '<module>'):
continue
if code.co_name.startswith('test'):
testName = code.co_name
break
self._baseDir = os.path.join(
testbasedir,
self.moduleName, self.className, testName)
if not os.path.isdir(self._baseDir):
os.makedirs(self._baseDir)
return self._baseDir
def _invokeantlr(self, dir, file, options, javaOptions=''):
cmd = 'cd {}; java {} {} org.antlr.Tool -o . {} {} 2>&1'.format(
dir, javaOptions, classpath, options, file
)
fp = os.popen(cmd)
output = ''
failed = False
for line in fp:
output += line
if line.startswith('error('):
failed = True
rc = fp.close()
if rc:
failed = True
if failed:
raise GrammarCompileError(
"Failed to compile grammar '{}':\n{}\n\n{}".format(file, cmd, output)
)
def compileGrammar(self, grammarName=None, options='', javaOptions=''):
if grammarName is None:
grammarName = self.moduleName + '.g'
self._baseDir = os.path.join(
testbasedir,
self.moduleName)
if not os.path.isdir(self._baseDir):
os.makedirs(self._baseDir)
if self.grammarName is None:
self.grammarName = os.path.splitext(grammarName)[0]
grammarPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), grammarName)
# get type and name from first grammar line
with open(grammarPath, 'r') as fp:
grammar = fp.read()
m = re.match(r'\s*((lexer|parser|tree)\s+|)grammar\s+(\S+);', grammar, re.MULTILINE)
self.assertIsNotNone(m, grammar)
self.grammarType = m.group(2) or 'combined'
self.assertIn(self.grammarType, ('lexer', 'parser', 'tree', 'combined'))
# don't try to rebuild grammar, if it already failed
if grammarName in compileErrorCache:
return
try:
# # get dependencies from antlr
# if grammarName in dependencyCache:
# dependencies = dependencyCache[grammarName]
# else:
# dependencies = []
# cmd = ('cd %s; java %s %s org.antlr.Tool -o . -depend %s 2>&1'
# % (self.baseDir, javaOptions, classpath, grammarPath))
# output = ""
# failed = False
# fp = os.popen(cmd)
# for line in fp:
# output += line
# if line.startswith('error('):
# failed = True
# elif ':' in line:
# a, b = line.strip().split(':', 1)
# dependencies.append(
# (os.path.join(self.baseDir, a.strip()),
# [os.path.join(self.baseDir, b.strip())])
# )
# rc = fp.close()
# if rc is not None:
# failed = True
# if failed:
# raise GrammarCompileError(
# "antlr -depend failed with code {} on grammar '{}':\n\n{}\n{}".format(
# rc, grammarName, cmd, output)
# )
# # add dependencies to my .stg files
# templateDir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'tool', 'src', 'main', 'resources', 'org', 'antlr', 'codegen', 'templates', 'Python'))
# templates = glob.glob(os.path.join(templateDir, '*.stg'))
# for dst, src in dependencies:
# src.extend(templates)
# dependencyCache[grammarName] = dependencies
# rebuild = False
# for dest, sources in dependencies:
# if not os.path.isfile(dest):
# rebuild = True
# break
# for source in sources:
# if os.path.getmtime(source) > os.path.getmtime(dest):
# rebuild = True
# break
# if rebuild:
# self._invokeantlr(self.baseDir, grammarPath, options, javaOptions)
self._invokeantlr(self.baseDir, grammarPath, options, javaOptions)
except:
# mark grammar as broken
compileErrorCache[grammarName] = True
raise
def lexerClass(self, base):
"""Optionally build a subclass of generated lexer class"""
return base
def parserClass(self, base):
"""Optionally build a subclass of generated parser class"""
return base
def walkerClass(self, base):
"""Optionally build a subclass of generated walker class"""
return base
def __load_module(self, name):
modFile, modPathname, modDescription = imp.find_module(name, [self.baseDir])
with modFile:
return imp.load_module(name, modFile, modPathname, modDescription)
def getLexer(self, *args, **kwargs):
"""Build lexer instance. Arguments are passed to lexer.__init__()."""
if self.grammarType == 'lexer':
self.lexerModule = self.__load_module(self.grammarName)
cls = getattr(self.lexerModule, self.grammarName)
else:
self.lexerModule = self.__load_module(self.grammarName + 'Lexer')
cls = getattr(self.lexerModule, self.grammarName + 'Lexer')
cls = self.lexerClass(cls)
lexer = cls(*args, **kwargs)
return lexer
def getParser(self, *args, **kwargs):
"""Build parser instance. Arguments are passed to parser.__init__()."""
if self.grammarType == 'parser':
self.lexerModule = self.__load_module(self.grammarName)
cls = getattr(self.lexerModule, self.grammarName)
else:
self.parserModule = self.__load_module(self.grammarName + 'Parser')
cls = getattr(self.parserModule, self.grammarName + 'Parser')
cls = self.parserClass(cls)
parser = cls(*args, **kwargs)
return parser
def getWalker(self, *args, **kwargs):
"""Build walker instance. Arguments are passed to walker.__init__()."""
self.walkerModule = self.__load_module(self.grammarName + 'Walker')
cls = getattr(self.walkerModule, self.grammarName + 'Walker')
cls = self.walkerClass(cls)
walker = cls(*args, **kwargs)
return walker
def writeInlineGrammar(self, grammar):
# Create a unique ID for this test and use it as the grammar name,
# to avoid class name reuse. This kinda sucks. Need to find a way so
# tests can use the same grammar name without messing up the namespace.
# Well, first I should figure out what the exact problem is...
id = hashlib.md5(self.baseDir.encode('utf-8')).hexdigest()[-8:]
grammar = grammar.replace('$TP', 'TP' + id)
grammar = grammar.replace('$T', 'T' + id)
# get type and name from first grammar line
m = re.match(r'\s*((lexer|parser|tree)\s+|)grammar\s+(\S+);', grammar, re.MULTILINE)
self.assertIsNotNone(m, grammar)
grammarType = m.group(2) or 'combined'
grammarName = m.group(3)
self.assertIn(grammarType, ('lexer', 'parser', 'tree', 'combined'))
grammarPath = os.path.join(self.baseDir, grammarName + '.g')
# dump temp grammar file
with open(grammarPath, 'w') as fp:
fp.write(grammar)
return grammarName, grammarPath, grammarType
def writeFile(self, name, contents):
testDir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(self.baseDir, name)
with open(path, 'w') as fp:
fp.write(contents)
return path
def compileInlineGrammar(self, grammar, options='', javaOptions='',
returnModule=False):
# write grammar file
grammarName, grammarPath, grammarType = self.writeInlineGrammar(grammar)
# compile it
self._invokeantlr(
os.path.dirname(grammarPath),
os.path.basename(grammarPath),
options,
javaOptions
)
if grammarType == 'combined':
lexerMod = self.__load_module(grammarName + 'Lexer')
parserMod = self.__load_module(grammarName + 'Parser')
if returnModule:
return lexerMod, parserMod
lexerCls = getattr(lexerMod, grammarName + 'Lexer')
lexerCls = self.lexerClass(lexerCls)
parserCls = getattr(parserMod, grammarName + 'Parser')
parserCls = self.parserClass(parserCls)
return lexerCls, parserCls
if grammarType == 'lexer':
lexerMod = self.__load_module(grammarName)
if returnModule:
return lexerMod
lexerCls = getattr(lexerMod, grammarName)
lexerCls = self.lexerClass(lexerCls)
return lexerCls
if grammarType == 'parser':
parserMod = self.__load_module(grammarName)
if returnModule:
return parserMod
parserCls = getattr(parserMod, grammarName)
parserCls = self.parserClass(parserCls)
return parserCls
if grammarType == 'tree':
walkerMod = self.__load_module(grammarName)
if returnModule:
return walkerMod
walkerCls = getattr(walkerMod, grammarName)
walkerCls = self.walkerClass(walkerCls)
return walkerCls