@@ -30,12 +30,13 @@ Use the reference pages for more in-depth explanations. (links in the [main page | |||||
## LALR usage | ## LALR usage | ||||
By default Lark silently resolves Shift/Reduce conflicts as Shift. To enable warnings pass `debug=True`. To get the messages printed you have to configure `logging` framework beforehand. For example: | |||||
By default Lark silently resolves Shift/Reduce conflicts as Shift. To enable warnings pass `debug=True`. To get the messages printed you have to configure the `LOGGER` beforehand. For example: | |||||
```python | ```python | ||||
from lark import Lark | |||||
import logging | import logging | ||||
logging.basicConfig(level=logging.DEBUG) | |||||
from lark import Lark, LOGGER | |||||
LOGGER.setLevel(logging.DEBUG) | |||||
collision_grammar = ''' | collision_grammar = ''' | ||||
start: as as | start: as as | ||||
@@ -1,3 +1,4 @@ | |||||
from .common import LOGGER | |||||
from .tree import Tree | from .tree import Tree | ||||
from .visitors import Transformer, Visitor, v_args, Discard | from .visitors import Transformer, Visitor, v_args, Discard | ||||
from .visitors import InlineTransformer, inline_args # XXX Deprecated | from .visitors import InlineTransformer, inline_args # XXX Deprecated | ||||
@@ -1,6 +1,13 @@ | |||||
import logging | |||||
from .utils import Serialize | from .utils import Serialize | ||||
from .lexer import TerminalDef | from .lexer import TerminalDef | ||||
LOGGER = logging.getLogger("LARK") | |||||
LOGGER.addHandler(logging.StreamHandler()) | |||||
# Set to highest level, since we have some warnings amongst the code | |||||
# By default, we should not output any log messages | |||||
LOGGER.setLevel(logging.CRITICAL) | |||||
###{standalone | ###{standalone | ||||
class LexerConf(Serialize): | class LexerConf(Serialize): | ||||
@@ -1,13 +1,13 @@ | |||||
from __future__ import absolute_import | from __future__ import absolute_import | ||||
import sys, os, pickle, hashlib, logging | |||||
import sys, os, pickle, hashlib | |||||
from io import open | from io import open | ||||
from .utils import STRING_TYPE, Serialize, SerializeMemoizer, FS | from .utils import STRING_TYPE, Serialize, SerializeMemoizer, FS | ||||
from .load_grammar import load_grammar | from .load_grammar import load_grammar | ||||
from .tree import Tree | from .tree import Tree | ||||
from .common import LexerConf, ParserConf | |||||
from .common import LexerConf, ParserConf, LOGGER | |||||
from .lexer import Lexer, TraditionalLexer, TerminalDef, UnexpectedToken | from .lexer import Lexer, TraditionalLexer, TerminalDef, UnexpectedToken | ||||
from .parse_tree_builder import ParseTreeBuilder | from .parse_tree_builder import ParseTreeBuilder | ||||
@@ -205,7 +205,7 @@ class Lark(Serialize): | |||||
cache_fn = '.lark_cache_%s.tmp' % md5 | cache_fn = '.lark_cache_%s.tmp' % md5 | ||||
if FS.exists(cache_fn): | if FS.exists(cache_fn): | ||||
logging.debug('Loading grammar from cache: %s', cache_fn) | |||||
LOGGER.debug('Loading grammar from cache: %s', cache_fn) | |||||
with FS.open(cache_fn, 'rb') as f: | with FS.open(cache_fn, 'rb') as f: | ||||
self._load(f, self.options.transformer, self.options.postlex) | self._load(f, self.options.transformer, self.options.postlex) | ||||
return | return | ||||
@@ -284,7 +284,7 @@ class Lark(Serialize): | |||||
self.lexer = self._build_lexer() | self.lexer = self._build_lexer() | ||||
if cache_fn: | if cache_fn: | ||||
logging.debug('Saving grammar to cache: %s', cache_fn) | |||||
LOGGER.debug('Saving grammar to cache: %s', cache_fn) | |||||
with FS.open(cache_fn, 'wb') as f: | with FS.open(cache_fn, 'wb') as f: | ||||
self.save(f) | self.save(f) | ||||
@@ -10,11 +10,11 @@ is better documented here: | |||||
http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/ | http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/ | ||||
""" | """ | ||||
import logging | |||||
from collections import deque | from collections import deque | ||||
from ..visitors import Transformer_InPlace, v_args | from ..visitors import Transformer_InPlace, v_args | ||||
from ..exceptions import UnexpectedEOF, UnexpectedToken | from ..exceptions import UnexpectedEOF, UnexpectedToken | ||||
from ..common import LOGGER | |||||
from .grammar_analysis import GrammarAnalyzer | from .grammar_analysis import GrammarAnalyzer | ||||
from ..grammar import NonTerminal | from ..grammar import NonTerminal | ||||
from .earley_common import Item, TransitiveItem | from .earley_common import Item, TransitiveItem | ||||
@@ -301,7 +301,7 @@ class Parser: | |||||
try: | try: | ||||
debug_walker = ForestToPyDotVisitor() | debug_walker = ForestToPyDotVisitor() | ||||
except ImportError: | except ImportError: | ||||
logging.warning("Cannot find dependency 'pydot', will not generate sppf debug image") | |||||
LOGGER.warning("Cannot find dependency 'pydot', will not generate sppf debug image") | |||||
else: | else: | ||||
debug_walker.visit(solutions[0], "sppf.png") | debug_walker.visit(solutions[0], "sppf.png") | ||||
@@ -6,11 +6,11 @@ For now, shift/reduce conflicts are automatically resolved as shifts. | |||||
# Author: Erez Shinan (2017) | # Author: Erez Shinan (2017) | ||||
# Email : erezshin@gmail.com | # Email : erezshin@gmail.com | ||||
import logging | |||||
from collections import defaultdict, deque | from collections import defaultdict, deque | ||||
from ..utils import classify, classify_bool, bfs, fzset, Serialize, Enumerator | from ..utils import classify, classify_bool, bfs, fzset, Serialize, Enumerator | ||||
from ..exceptions import GrammarError | from ..exceptions import GrammarError | ||||
from ..common import LOGGER | |||||
from .grammar_analysis import GrammarAnalyzer, Terminal, LR0ItemSet | from .grammar_analysis import GrammarAnalyzer, Terminal, LR0ItemSet | ||||
from ..grammar import Rule | from ..grammar import Rule | ||||
@@ -256,8 +256,8 @@ class LALR_Analyzer(GrammarAnalyzer): | |||||
raise GrammarError('Reduce/Reduce collision in %s between the following rules: %s' % (la, ''.join([ '\n\t\t- ' + str(r) for r in rules ]))) | raise GrammarError('Reduce/Reduce collision in %s between the following rules: %s' % (la, ''.join([ '\n\t\t- ' + str(r) for r in rules ]))) | ||||
if la in actions: | if la in actions: | ||||
if self.debug: | if self.debug: | ||||
logging.warning('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name) | |||||
logging.warning(' * %s', list(rules)[0]) | |||||
LOGGER.warning('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name) | |||||
LOGGER.warning(' * %s', list(rules)[0]) | |||||
else: | else: | ||||
actions[la] = (Reduce, list(rules)[0]) | actions[la] = (Reduce, list(rules)[0]) | ||||
m[state] = { k.name: v for k, v in actions.items() } | m[state] = { k.name: v for k, v in actions.items() } | ||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import, print_function | |||||
import unittest | import unittest | ||||
import logging | import logging | ||||
from lark import LOGGER | |||||
from .test_trees import TestTrees | from .test_trees import TestTrees | ||||
from .test_tools import TestStandalone | from .test_tools import TestStandalone | ||||
@@ -11,11 +12,13 @@ from .test_reconstructor import TestReconstructor | |||||
try: | try: | ||||
from .test_nearley.test_nearley import TestNearley | from .test_nearley.test_nearley import TestNearley | ||||
except ImportError: | except ImportError: | ||||
logging.warning("Warning: Skipping tests for Nearley grammar imports (js2py required)") | |||||
LOGGER.warning("Warning: Skipping tests for Nearley grammar imports (js2py required)") | |||||
# from .test_selectors import TestSelectors | # from .test_selectors import TestSelectors | ||||
# from .test_grammars import TestPythonG, TestConfigG | # from .test_grammars import TestPythonG, TestConfigG | ||||
from .test_logger import TestLogger | |||||
from .test_parser import ( | from .test_parser import ( | ||||
TestLalrStandard, | TestLalrStandard, | ||||
TestEarleyStandard, | TestEarleyStandard, | ||||
@@ -31,7 +34,7 @@ from .test_parser import ( | |||||
TestParsers, | TestParsers, | ||||
) | ) | ||||
logging.basicConfig(level=logging.INFO) | |||||
LOGGER.setLevel(logging.INFO) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
unittest.main() | unittest.main() |
@@ -0,0 +1,65 @@ | |||||
import logging | |||||
from contextlib import contextmanager | |||||
from lark import Lark, LOGGER | |||||
from unittest import TestCase, main | |||||
try: | |||||
from StringIO import StringIO | |||||
except ImportError: | |||||
from io import StringIO | |||||
@contextmanager | |||||
def capture_log(): | |||||
stream = StringIO() | |||||
orig_handler = LOGGER.handlers[0] | |||||
del LOGGER.handlers[:] | |||||
LOGGER.addHandler(logging.StreamHandler(stream)) | |||||
yield stream | |||||
del LOGGER.handlers[:] | |||||
LOGGER.addHandler(orig_handler) | |||||
class TestLogger(TestCase): | |||||
def test_debug(self): | |||||
LOGGER.setLevel(logging.DEBUG) | |||||
collision_grammar = ''' | |||||
start: as as | |||||
as: a* | |||||
a: "a" | |||||
''' | |||||
with capture_log() as log: | |||||
Lark(collision_grammar, parser='lalr', debug=True) | |||||
log = log.getvalue() | |||||
self.assertIn("Shift/Reduce conflict for terminal", log) | |||||
self.assertIn("A: (resolving as shift)", log) | |||||
self.assertIn("Shift/Reduce conflict for terminal A: (resolving as shift)", log) | |||||
def test_non_debug(self): | |||||
LOGGER.setLevel(logging.DEBUG) | |||||
collision_grammar = ''' | |||||
start: as as | |||||
as: a* | |||||
a: "a" | |||||
''' | |||||
with capture_log() as log: | |||||
Lark(collision_grammar, parser='lalr', debug=False) | |||||
log = log.getvalue() | |||||
# no log messge | |||||
self.assertEqual(len(log), 0) | |||||
def test_loglevel_higher(self): | |||||
LOGGER.setLevel(logging.ERROR) | |||||
collision_grammar = ''' | |||||
start: as as | |||||
as: a* | |||||
a: "a" | |||||
''' | |||||
with capture_log() as log: | |||||
Lark(collision_grammar, parser='lalr', debug=True) | |||||
log = log.getvalue() | |||||
# no log messge | |||||
self.assertEqual(len(log), 0) | |||||
if __name__ == '__main__': | |||||
main() |
@@ -6,16 +6,17 @@ import logging | |||||
import os | import os | ||||
import codecs | import codecs | ||||
logging.basicConfig(level=logging.INFO) | |||||
from lark import LOGGER | |||||
from lark.tools.nearley import create_code_for_nearley_grammar, main as nearley_tool_main | from lark.tools.nearley import create_code_for_nearley_grammar, main as nearley_tool_main | ||||
LOGGER.setLevel(logging.INFO) | |||||
TEST_PATH = os.path.abspath(os.path.dirname(__file__)) | TEST_PATH = os.path.abspath(os.path.dirname(__file__)) | ||||
NEARLEY_PATH = os.path.join(TEST_PATH, 'nearley') | NEARLEY_PATH = os.path.join(TEST_PATH, 'nearley') | ||||
BUILTIN_PATH = os.path.join(NEARLEY_PATH, 'builtin') | BUILTIN_PATH = os.path.join(NEARLEY_PATH, 'builtin') | ||||
if not os.path.exists(NEARLEY_PATH): | if not os.path.exists(NEARLEY_PATH): | ||||
logging.warn("Nearley not installed. Skipping Nearley tests!") | |||||
LOGGER.warn("Nearley not installed. Skipping Nearley tests!") | |||||
raise ImportError("Skipping Nearley tests!") | raise ImportError("Skipping Nearley tests!") | ||||
import js2py # Ensures that js2py exists, to avoid failing tests | import js2py # Ensures that js2py exists, to avoid failing tests | ||||
@@ -18,13 +18,13 @@ from io import ( | |||||
open, | open, | ||||
) | ) | ||||
logging.basicConfig(level=logging.INFO) | |||||
try: | try: | ||||
import regex | import regex | ||||
except ImportError: | except ImportError: | ||||
regex = None | regex = None | ||||
from lark import LOGGER | |||||
from lark.lark import Lark | from lark.lark import Lark | ||||
from lark.exceptions import GrammarError, ParseError, UnexpectedToken, UnexpectedInput, UnexpectedCharacters | from lark.exceptions import GrammarError, ParseError, UnexpectedToken, UnexpectedInput, UnexpectedCharacters | ||||
from lark.tree import Tree | from lark.tree import Tree | ||||
@@ -32,6 +32,7 @@ from lark.visitors import Transformer, Transformer_InPlace, v_args | |||||
from lark.grammar import Rule | from lark.grammar import Rule | ||||
from lark.lexer import TerminalDef, Lexer, TraditionalLexer | from lark.lexer import TerminalDef, Lexer, TraditionalLexer | ||||
LOGGER.setLevel(logging.INFO) | |||||
__path__ = os.path.dirname(__file__) | __path__ = os.path.dirname(__file__) | ||||