Commit 8e31cd6e authored by eckhart's avatar eckhart
Browse files

error.py: Refactoring: Error-constants are now defined on the top-level of...

error.py: Refactoring: Error-constants are now defined on the top-level of error.py, rather than as class Attributes of class Error
parent 7a89b7e0
......@@ -45,7 +45,8 @@ from DHParser.preprocess import with_source_mapping, PreprocessorFunc, SourceMap
from DHParser.syntaxtree import Node, RootNode
from DHParser.transform import TransformationFunc
from DHParser.parse import Grammar
from DHParser.error import adjust_error_locations, is_error, is_fatal, Error
from DHParser.error import adjust_error_locations, is_error, is_fatal, Error, \
TREE_PROCESSING_CRASH, COMPILER_CRASH, AST_TRANSFORM_CRASH
from DHParser.log import log_parsing_history, log_ST, is_logging
from DHParser.toolkit import load_if_file, is_filename
......@@ -336,7 +337,7 @@ def compile_source(source: str,
syntax_tree.new_error(syntax_tree,
"AST-Transformation failed due to earlier parser errors. "
"Crash Message: %s: %s" % (e.__class__.__name__, str(e)),
Error.AST_TRANSFORM_CRASH)
AST_TRANSFORM_CRASH)
else:
transformer(syntax_tree)
......@@ -365,7 +366,7 @@ def compile_source(source: str,
node, "Compilation failed, most likely, due to errors earlier "
"in the processing pipeline. Crash Message: %s: %s\n%s"
% (e.__class__.__name__, str(e), trace),
Error.COMPILER_CRASH)
COMPILER_CRASH)
else:
# assume Python crashes are programming mistakes, so let
# the exceptions through
......@@ -434,7 +435,7 @@ def process_tree(tp: TreeProcessor, tree: RootNode) -> RootNode:
node, "Tree-processing failed, most likely, due to errors earlier in "
"in the processing pipeline. Crash Message: %s: %s\n%s"
% (e.__class__.__name__, str(e), trace),
Error.TREE_PROCESSING_CRASH)
TREE_PROCESSING_CRASH)
else:
# assume Python crashes are programming mistakes, so let
# the exceptions through
......
......@@ -32,7 +32,9 @@ from typing import Callable, Dict, List, Set, Tuple, Sequence, Union, Optional,
from DHParser.compile import CompilerError, Compiler, ResultTuple, compile_source, visitor_name
from DHParser.configuration import access_thread_locals, get_config_value
from DHParser.error import Error
from DHParser.error import Error, AMBIGUOUS_ERROR_HANDLING, WARNING, REDECLARED_TOKEN_WARNING, REDEFINED_DIRECTIVE, \
UNUSED_ERROR_HANDLING_WARNING, INAPPROPRIATE_SYMBOL_FOR_DIRECTIVE, DIRECTIVE_FOR_NONEXISTANT_SYMBOL, \
UNDEFINED_SYMBOL_IN_TRANSTABLE_WARNING
from DHParser.parse import Grammar, mixin_comment, mixin_nonempty, Forward, RegExp, Drop, \
Lookahead, NegativeLookahead, Alternative, Series, Option, ZeroOrMore, OneOrMore, Token, \
Capture, Retrieve, Pop, optional_last_value, GrammarError, Whitespace, INFINITE
......@@ -874,7 +876,7 @@ class EBNFCompiler(Compiler):
if entry not in symbols and not entry.startswith(":"):
messages.append(Error(('Symbol "%s" is not defined in grammar %s but appears in '
'the transformation table!') % (entry, self.grammar_name),
0, Error.UNDEFINED_SYMBOL_IN_TRANSTABLE_WARNING))
0, UNDEFINED_SYMBOL_IN_TRANSTABLE_WARNING))
return messages
def verify_compiler(self, compiler):
......@@ -970,7 +972,7 @@ class EBNFCompiler(Compiler):
nd = self.defined_directives[directive][0]
self.tree.new_error(nd, 'Directive "%s" relates to a symbol that is '
'nowhere defined!' % directive,
Error.DIRECTIVE_FOR_NONEXISTANT_SYMBOL)
DIRECTIVE_FOR_NONEXISTANT_SYMBOL)
# execute deferred tasks, for example semantic checks that cannot
# be done before the symbol table is complete
......@@ -1033,7 +1035,7 @@ class EBNFCompiler(Compiler):
else:
self.tree.new_error(nd, 'Symbol "%s" cannot be used in resume rule, since'
' it represents neither literal nor regexp!',
Error.INAPPROPRIATE_SYMBOL_FOR_DIRECTIVE)
INAPPROPRIATE_SYMBOL_FOR_DIRECTIVE)
else:
refined_rules.append(rule)
resume_rules[symbol] = refined_rules
......@@ -1065,7 +1067,7 @@ class EBNFCompiler(Compiler):
self.tree.new_error(
def_node, '"Skip-rules" for symbol "{}" will never be used, '
'because the mandatory marker "§" appears nowhere in its definiendum!'
.format(symbol), Error.UNUSED_ERROR_HANDLING_WARNING)
.format(symbol), UNUSED_ERROR_HANDLING_WARNING)
except KeyError:
pass # error has already been notified earlier!
......@@ -1094,7 +1096,7 @@ class EBNFCompiler(Compiler):
self.tree.new_error(
def_node, 'Customized error message for symbol "{}" will never be used, '
'because the mandatory marker "§" appears nowhere in its definiendum!'
.format(symbol), Error.UNUSED_ERROR_HANDLING_WARNING)
.format(symbol), UNUSED_ERROR_HANDLING_WARNING)
# except KeyError:
# def match_function(nd: Node) -> bool:
# return bool(nd.children) and nd.children[0].content.startswith(symbol + '_')
......@@ -1166,7 +1168,7 @@ class EBNFCompiler(Compiler):
for leftover in defined_symbols:
self.tree.new_error(self.rules[leftover][0],
('Rule "%s" is not connected to parser root "%s" !') %
(leftover, self.root_symbol), Error.WARNING)
(leftover, self.root_symbol), WARNING)
# set root_symbol parser and assemble python grammar definition
......@@ -1230,7 +1232,7 @@ class EBNFCompiler(Compiler):
if rule.endswith('_error') or rule.endswith('_skip') \
or rule.endswith('_resume') or rule.endswith('_filter'):
self.tree.new_error(node, 'Symbol name "%s" suggests directive, ' % rule +
'but directive marker @ is missing...', Error.WARNING)
'but directive marker @ is missing...', WARNING)
if rule in self.rules:
first = self.rules[rule][0]
if not id(first) in self.tree.error_nodes:
......@@ -1296,7 +1298,7 @@ class EBNFCompiler(Compiler):
and key in self.defined_directives:
self.tree.new_error(node, 'Directive "%s" has already been defined earlier. '
% key + 'Later definition will be ignored!',
code=Error.REDEFINED_DIRECTIVE)
code=REDEFINED_DIRECTIVE)
return ""
self.defined_directives.setdefault(key, []).append(node)
......@@ -1381,7 +1383,7 @@ class EBNFCompiler(Compiler):
if redeclared:
self.tree.new_error(node, 'Tokens %s have already been declared earlier. '
% str(redeclared) + 'Later declaration will be ignored',
code=Error.REDECLARED_TOKEN_WARNING)
code=REDECLARED_TOKEN_WARNING)
self.directives.tokens |= tokens - redeclared
elif key.endswith('_filter'):
......@@ -1476,7 +1478,7 @@ class EBNFCompiler(Compiler):
mandatory_marker.append(len(filtered_children))
if len(mandatory_marker) > 1:
self.tree.new_error(nd, 'One mandatory marker (§) is sufficient to declare '
'the rest of the elements as mandatory.', Error.WARNING)
'the rest of the elements as mandatory.', WARNING)
else:
filtered_children.append(nd)
custom_args = ['mandatory=%i' % mandatory_marker[0]] if mandatory_marker else []
......@@ -1490,7 +1492,7 @@ class EBNFCompiler(Compiler):
node, "Cannot apply customized error messages unambiguously, because "
"symbol {} contains more than one parser with a mandatory marker '§' "
"in its definiens.".format(current_symbol),
Error.AMBIGUOUS_ERROR_HANDLING)
AMBIGUOUS_ERROR_HANDLING)
else:
# use class field instead or direct representation of error messages!
custom_args.append('err_msgs={err_msgs_name}["{symbol}"]'
......@@ -1504,7 +1506,7 @@ class EBNFCompiler(Compiler):
node, "Cannot apply 'skip-rules' unambigiously, because symbol "
"{} contains more than one parser with a mandatory marker '§' "
"in its definiens.".format(current_symbol),
Error.AMBIGUOUS_ERROR_HANDLING)
AMBIGUOUS_ERROR_HANDLING)
else:
# use class field instead or direct representation of error messages!
custom_args.append('skip={skip_rules_name}["{symbol}"]'
......
......@@ -56,64 +56,65 @@ class ErrorCode(int):
pass
class Error:
__slots__ = ['message', 'code', '_pos', 'orig_pos', 'line', 'column']
# error levels
NO_ERROR = ErrorCode(0)
NOTICE = ErrorCode(1)
WARNING = ErrorCode(100)
ERROR = ErrorCode(1000)
FATAL = ErrorCode(10000)
HIGHEST = FATAL
# error levels
# notice codes
NO_ERROR = ErrorCode(0)
NOTICE = ErrorCode(1)
WARNING = ErrorCode(100)
ERROR = ErrorCode(1000)
FATAL = ErrorCode(10000)
HIGHEST = FATAL
RESUME_NOTICE = ErrorCode(50)
# notice codes
# warning codes
RESUME_NOTICE = ErrorCode(50)
REDECLARED_TOKEN_WARNING = ErrorCode(120)
UNUSED_ERROR_HANDLING_WARNING = ErrorCode(130)
LEFT_RECURSION_WARNING = ErrorCode(140)
# warning codes
UNDEFINED_SYMBOL_IN_TRANSTABLE_WARNING = ErrorCode(610)
CANNOT_VERIFY_TRANSTABLE_WARNING = ErrorCode(620)
CAPTURE_DROPPED_CONTENT_WARNING = ErrorCode(630)
OPTIONAL_REDUNDANTLY_NESTED_WARNING = ErrorCode(630)
REDECLARED_TOKEN_WARNING = ErrorCode(120)
UNUSED_ERROR_HANDLING_WARNING = ErrorCode(130)
LEFT_RECURSION_WARNING = ErrorCode(140)
# error codes
UNDEFINED_SYMBOL_IN_TRANSTABLE_WARNING = ErrorCode(610)
CANNOT_VERIFY_TRANSTABLE_WARNING = ErrorCode(620)
CAPTURE_DROPPED_CONTENT_WARNING = ErrorCode(630)
OPTIONAL_REDUNDANTLY_NESTED_WARNING = ErrorCode(630)
MANDATORY_CONTINUATION = ErrorCode(1010)
MANDATORY_CONTINUATION_AT_EOF = ErrorCode(1015)
PARSER_DID_NOT_MATCH = ErrorCode(1020)
PARSER_LOOKAHEAD_FAILURE_ONLY = ErrorCode(1030)
PARSER_STOPPED_BEFORE_END = ErrorCode(1040)
PARSER_LOOKAHEAD_MATCH_ONLY = ErrorCode(1045)
CAPTURE_STACK_NOT_EMPTY = ErrorCode(1050)
MALFORMED_ERROR_STRING = ErrorCode(1060)
AMBIGUOUS_ERROR_HANDLING = ErrorCode(1070)
REDEFINED_DIRECTIVE = ErrorCode(1080)
UNDEFINED_RETRIEVE = ErrorCode(1090)
DIRECTIVE_FOR_NONEXISTANT_SYMBOL = ErrorCode(1100)
INAPPROPRIATE_SYMBOL_FOR_DIRECTIVE = ErrorCode(1110)
# error codes
# EBNF-specific static analysis errors
MANDATORY_CONTINUATION = ErrorCode(1010)
MANDATORY_CONTINUATION_AT_EOF = ErrorCode(1015)
PARSER_DID_NOT_MATCH = ErrorCode(1020)
PARSER_LOOKAHEAD_FAILURE_ONLY = ErrorCode(1030)
PARSER_STOPPED_BEFORE_END = ErrorCode(1040)
PARSER_LOOKAHEAD_MATCH_ONLY = ErrorCode(1045)
CAPTURE_STACK_NOT_EMPTY = ErrorCode(1050)
MALFORMED_ERROR_STRING = ErrorCode(1060)
AMBIGUOUS_ERROR_HANDLING = ErrorCode(1070)
REDEFINED_DIRECTIVE = ErrorCode(1080)
UNDEFINED_RETRIEVE = ErrorCode(1090)
DIRECTIVE_FOR_NONEXISTANT_SYMBOL = ErrorCode(1100)
INAPPROPRIATE_SYMBOL_FOR_DIRECTIVE = ErrorCode(1110)
CAPTURE_WITHOUT_PARSERNAME = ErrorCode(1510)
LOOKAHEAD_WITH_OPTIONAL_PARSER = ErrorCode(1520)
BADLY_NESTED_OPTIONAL_PARSER = ErrorCode(1530)
NARY_WITHOUT_PARSERS = ErrorCode(1540)
BAD_MANDATORY_SETUP = ErrorCode(1550)
DUPLICATE_PARSERS_IN_ALTERNATIVE = ErrorCode(1560)
BAD_ORDER_OF_ALTERNATIVES = ErrorCode(1570)
# EBNF-specific static analysis errors
# fatal errors
CAPTURE_WITHOUT_PARSERNAME = ErrorCode(1510)
LOOKAHEAD_WITH_OPTIONAL_PARSER = ErrorCode(1520)
BADLY_NESTED_OPTIONAL_PARSER = ErrorCode(1530)
NARY_WITHOUT_PARSERS = ErrorCode(1540)
BAD_MANDATORY_SETUP = ErrorCode(1550)
DUPLICATE_PARSERS_IN_ALTERNATIVE = ErrorCode(1560)
BAD_ORDER_OF_ALTERNATIVES = ErrorCode(1570)
TREE_PROCESSING_CRASH = ErrorCode(10100)
COMPILER_CRASH = ErrorCode(10200)
AST_TRANSFORM_CRASH = ErrorCode(10300)
# fatal errors
TREE_PROCESSING_CRASH = ErrorCode(10100)
COMPILER_CRASH = ErrorCode(10200)
AST_TRANSFORM_CRASH = ErrorCode(10300)
class Error:
__slots__ = ['message', 'code', '_pos', 'orig_pos', 'line', 'column']
def __init__(self, message: str, pos: int, code: ErrorCode = ERROR,
orig_pos: int = -1, line: int = -1, column: int = -1) -> None:
......@@ -153,11 +154,11 @@ class Error:
@property
def severity(self):
"""Returns a string representation of the error level, e.g. "warning"."""
if self.code < Error.WARNING:
if self.code < WARNING:
return "Notice"
elif self.code < Error.ERROR:
elif self.code < ERROR:
return "Warning"
elif self.code < Error.FATAL:
elif self.code < FATAL:
return "Error"
else:
return "Fatal"
......@@ -172,22 +173,22 @@ class Error:
def is_warning(code: int) -> bool:
"""Returns True, if error is merely a warning or a message."""
return code < Error.ERROR
return code < ERROR
def is_error(code: int) -> bool:
"""Returns True, if error is a (fatal) error, not just a warning."""
return code >= Error.ERROR
return code >= ERROR
def is_fatal(code: int) -> bool:
"""Returns True, ir error is fatal. Fatal errors are typically raised
when a crash (i.e. Python exception) occurs at later stages of the
processing pipline (e.g. ast transformation, compiling). """
return code >= Error.FATAL
return code >= FATAL
# def Warning(message: str, pos, code: ErrorCode = Error.WARNING,
# def Warning(message: str, pos, code: ErrorCode = WARNING,
# orig_pos: int = -1, line: int = -1, column: int = -1) -> Error:
# """
# Syntactic sugar for creating Error-objects that contain only a warning.
......@@ -195,11 +196,11 @@ def is_fatal(code: int) -> bool:
# """
# if not is_warning(code):
# raise ValueError("Tried to create a warning with a error code {}. "
# "Warning codes must be smaller than {}".format(code, Error.ERROR))
# "Warning codes must be smaller than {}".format(code, ERROR))
# return Error(message, pos, code, orig_pos, line, column)
def has_errors(messages: Iterable[Error], level: int = Error.ERROR) -> bool:
def has_errors(messages: Iterable[Error], level: int = ERROR) -> bool:
"""
Returns True, if at least one entry in `messages` has at
least the given error `level`.
......@@ -210,7 +211,7 @@ def has_errors(messages: Iterable[Error], level: int = Error.ERROR) -> bool:
return False
def only_errors(messages: Iterable[Error], level: int = Error.ERROR) -> Iterator[Error]:
def only_errors(messages: Iterable[Error], level: int = ERROR) -> Iterator[Error]:
"""
Returns an Iterator that yields only those messages that have
at least the given error level.
......
......@@ -36,7 +36,13 @@ from typing import Callable, cast, List, Tuple, Set, Dict, \
DefaultDict, Sequence, Union, Optional, Any
from DHParser.configuration import get_config_value
from DHParser.error import Error, ErrorCode, is_error
from DHParser.error import Error, ErrorCode, is_error, MANDATORY_CONTINUATION, \
LEFT_RECURSION_WARNING, UNDEFINED_RETRIEVE, PARSER_LOOKAHEAD_FAILURE_ONLY, \
PARSER_DID_NOT_MATCH, PARSER_LOOKAHEAD_MATCH_ONLY, PARSER_STOPPED_BEFORE_END, \
MALFORMED_ERROR_STRING, MANDATORY_CONTINUATION_AT_EOF, DUPLICATE_PARSERS_IN_ALTERNATIVE, \
CAPTURE_WITHOUT_PARSERNAME, CAPTURE_DROPPED_CONTENT_WARNING, LOOKAHEAD_WITH_OPTIONAL_PARSER, \
BADLY_NESTED_OPTIONAL_PARSER, BAD_ORDER_OF_ALTERNATIVES, BAD_MANDATORY_SETUP, \
OPTIONAL_REDUNDANTLY_NESTED_WARNING, NARY_WITHOUT_PARSERS, CAPTURE_STACK_NOT_EMPTY
from DHParser.log import CallItem, HistoryRecord
from DHParser.preprocess import BEGIN_TOKEN, END_TOKEN, RX_TOKEN_NAME
from DHParser.stringview import StringView, EMPTY_STRING_VIEW
......@@ -443,7 +449,7 @@ class Parser:
elif pe.first_throw:
# TODO: Is this case still needed with module "trace"?
raise ParserError(pe.node, pe.rest, pe.error, first_throw=False)
elif grammar.tree__.errors[-1].code == Error.MANDATORY_CONTINUATION_AT_EOF:
elif grammar.tree__.errors[-1].code == MANDATORY_CONTINUATION_AT_EOF:
# try to create tree as faithful as possible
node = Node(self.tag_name, pe.node).with_pos(location)
else:
......@@ -466,7 +472,7 @@ class Parser:
node, Error("Left recursion encountered. "
"Refactor grammar to avoid slow parsing.",
node.pos if node else location,
Error.LEFT_RECURSION_WARNING))
LEFT_RECURSION_WARNING))
# error_id = id(node)
grammar.last_recursion_location__ = location
# don't overwrite any positive match (i.e. node not None) in the cache
......@@ -1299,11 +1305,11 @@ class Grammar:
self.tree__.new_error(
result, 'Parser "%s" only did not match empty document '
'because of lookahead' % str(parser),
Error.PARSER_LOOKAHEAD_FAILURE_ONLY)
PARSER_LOOKAHEAD_FAILURE_ONLY)
else:
self.tree__.new_error(
result, 'Parser "%s" did not match empty document.' % str(parser),
Error.PARSER_DID_NOT_MATCH)
PARSER_DID_NOT_MATCH)
# copy to local variable, so break condition can be triggered manually
max_parser_dropouts = self.max_parser_dropouts__
......@@ -1322,10 +1328,10 @@ class Grammar:
if lookahead_failure_only(parser):
error_msg = 'Parser "%s" only did not match because of lookahead! ' \
% str(parser) + err_info
error_code = Error.PARSER_LOOKAHEAD_FAILURE_ONLY
error_code = PARSER_LOOKAHEAD_FAILURE_ONLY
else:
error_msg = 'Parser "%s" did not match!' % str(parser) + err_info
error_code = Error.PARSER_DID_NOT_MATCH
error_code = PARSER_DID_NOT_MATCH
else:
stitches.append(result)
for h in reversed(self.history__):
......@@ -1337,7 +1343,7 @@ class Grammar:
if h.status == h.MATCH and (h.node.pos + len(h.node) == len(self.document__)):
# TODO: this case still needs unit-tests and support in testing.py
error_msg = "Parser stopped before end, but matched with lookahead."
error_code = Error.PARSER_LOOKAHEAD_MATCH_ONLY
error_code = PARSER_LOOKAHEAD_MATCH_ONLY
max_parser_dropouts = -1 # no further retries!
else:
error_msg = "Parser stopped before end" \
......@@ -1347,7 +1353,7 @@ class Grammar:
if len(stitches) < self.max_parser_dropouts__
else " too often!" if self.max_parser_dropouts__ > 1 else "!"
+ " Terminating parser.")
error_code = Error.PARSER_STOPPED_BEFORE_END
error_code = PARSER_STOPPED_BEFORE_END
stitch = Node(ZOMBIE_TAG, skip).with_pos(tail_pos(stitches))
stitches.append(stitch)
error = Error(error_msg, stitch.pos, error_code)
......@@ -1366,7 +1372,7 @@ class Grammar:
if any(self.variables__.values()):
error_msg = "Capture-stack not empty after end of parsing: " \
+ ', '.join(k for k, i in self.variables__.items() if len(i) >= 1)
error_code = Error.CAPTURE_STACK_NOT_EMPTY
error_code = CAPTURE_STACK_NOT_EMPTY
if result:
if result.children:
# add another child node at the end to ensure that the position
......@@ -1867,7 +1873,7 @@ class NaryParser(MetaParser):
if len(self.parsers) == 0:
return [self.static_error(
'Nary parser %s:%s = %s requires at least on argument'
% (self.pname, self.ptype, str(self)), Error.NARY_WITHOUT_PARSERS)]
% (self.pname, self.ptype, str(self)), NARY_WITHOUT_PARSERS)]
return None
def location_info(self) -> str:
......@@ -1922,7 +1928,7 @@ class Option(UnaryParser):
if self.parser.is_optional():
return [self.static_error(
"Redundant nesting of optional parser in " + self.location_info(),
Error.OPTIONAL_REDUNDANTLY_NESTED_WARNING)]
OPTIONAL_REDUNDANTLY_NESTED_WARNING)]
return None
......@@ -2029,7 +2035,7 @@ class OneOrMore(UnaryParser):
if self.parser.is_optional():
return [self.static_error(
"Use ZeroOrMore instead of nesting OneOrMore with an optional parser in " \
+ self.location_info(), Error.BADLY_NESTED_OPTIONAL_PARSER)]
+ self.location_info(), BADLY_NESTED_OPTIONAL_PARSER)]
return None
......@@ -2138,7 +2144,7 @@ class MandatoryNary(NaryParser):
except (ValueError, KeyError, IndexError) as e:
error = Error("Malformed error format string »{}« leads to »{}«"
.format(message, str(e)),
location, Error.MALFORMED_ERROR_STRING)
location, MALFORMED_ERROR_STRING)
grammar.tree__.add_error(err_node, error)
else:
if grammar.history_tracking__:
......@@ -2149,8 +2155,8 @@ class MandatoryNary(NaryParser):
msg = '%s expected by parser %s, »%s« found!' % (expected, repr(pname), found)
else:
msg = '%s expected, »%s« found!' % (expected, found)
error = Error(msg, location, Error.MANDATORY_CONTINUATION_AT_EOF
if (failed_on_lookahead and not text_) else Error.MANDATORY_CONTINUATION)
error = Error(msg, location, MANDATORY_CONTINUATION_AT_EOF
if (failed_on_lookahead and not text_) else MANDATORY_CONTINUATION)
grammar.tree__.add_error(err_node, error)
if reloc >= 0:
# signal error to tracer directly, because this error is not raised!
......@@ -2176,7 +2182,7 @@ class MandatoryNary(NaryParser):
if msg:
msg.insert(0, 'Illegal configuration of mandatory Nary-parser '
+ self.location_info())
return[self.static_error('\n'.join(msg), Error.BAD_MANDATORY_SETUP)]
return[self.static_error('\n'.join(msg), BAD_MANDATORY_SETUP)]
return None
......@@ -2352,13 +2358,13 @@ class Alternative(NaryParser):
if len(set(self.parsers)) != len(self.parsers):
errors.append(self.static_error(
'Duplicate parsers in ' + self.location_info(),
Error.DUPLICATE_PARSERS_IN_ALTERNATIVE))
DUPLICATE_PARSERS_IN_ALTERNATIVE))
if not all(not p.is_optional() for p in self.parsers[:-1]):
errors.append(self.static_error(
"Parser-specification Error in " + self.location_info()
+ "\nOnly the very last alternative may be optional! \nOtherwise, "
"alternatives after the first optional alternative will never be parsed.",
Error.BAD_ORDER_OF_ALTERNATIVES))
BAD_ORDER_OF_ALTERNATIVES))
return errors or None
......@@ -2509,7 +2515,7 @@ class Interleave(MandatoryNary):
return [self.static_error(
"Flow-operators and optional parsers are neither allowed nor needed inside "
"of interleave-parser " + self.location_info(),
Error.BADLY_NESTED_OPTIONAL_PARSER)]
BADLY_NESTED_OPTIONAL_PARSER)]
return None
......@@ -2546,7 +2552,7 @@ def Required(parser: Parser) -> Parser:
# text_ = text[i:]
# self.grammar.tree__.new_error(node,
# '%s expected; "%s" found!' % (str(self.parser),
# text[:10]), code=Error.MANDATORY_CONTINUATION)
# text[:10]), code=MANDATORY_CONTINUATION)
# return node, text_
#
# def __repr__(self):
......@@ -2575,7 +2581,7 @@ class Lookahead(FlowParser):
return [(self.pname, self, Error(
'Lookahead %s does not make sense with optional parser "%s"!' \
% (self.pname, str(self.parser)),
0, Error.LOOKAHEAD_WITH_OPTIONAL_PARSER))]
0, LOOKAHEAD_WITH_OPTIONAL_PARSER))]
return None
......@@ -2689,13 +2695,13 @@ class Capture(UnaryParser):
if not self.pname:
errors.append((self.pname, self, Error(
'Capture only works as named parser! Error in parser: ' + str(self),
0, Error.CAPTURE_WITHOUT_PARSERNAME
0, CAPTURE_WITHOUT_PARSERNAME
)))
if self.parser.apply(lambda plist: plist[-1].drop_content):
errors.append((self.pname, self, Error(
'Captured symbol "%s" contains parsers that drop content, '
'which can lead to unintended results!' % (self.pname or str(self)),
0, Error.CAPTURE_DROPPED_CONTENT_WARNING
0, CAPTURE_DROPPED_CONTENT_WARNING
)))
return errors or None
......@@ -2818,7 +2824,7 @@ class Retrieve(UnaryParser):
node = Node(tn, '') # .with_pos(self.grammar.document_length__ - text.__len__())
self.grammar.tree__.new_error(
node, dsl_error_msg(self, "'%s' undefined or exhausted." % self.symbol_pname),
Error.UNDEFINED_RETRIEVE)
UNDEFINED_RETRIEVE)
return node, text
if value is None:
return None, text
......
......@@ -34,7 +34,7 @@ from typing import Callable, cast, Iterator, Sequence, List, Set, Union, \
from DHParser.configuration import SERIALIZATIONS, XML_SERIALIZATION, \
SXPRESSION_SERIALIZATION, COMPACT_SERIALIZATION, JSON_SERIALIZATION, \
SMART_SERIALIZATION, get_config_value
from DHParser.error import Error, ErrorCode
from DHParser.error import Error, ErrorCode, ERROR
from DHParser.stringview import StringView # , real_indices
from DHParser.toolkit import re, cython, linebreaks, line_col
......@@ -1573,7 +1573,7 @@ class RootNode(Node):
def new_error(self,
node: Node,
message: str,
code: ErrorCode = Error.ERROR) -> 'RootNode':
code: ErrorCode = ERROR) -> 'RootNode':
"""
Adds an error to this tree, locating it at a specific node.
Parameters:
......
......@@ -38,7 +38,8 @@ import sys
from typing import Dict, List, Union, cast
from DHParser.configuration import get_config_value
from DHParser.error import Error, is_error, adjust_error_locations
from DHParser.error import Error, is_error, adjust_error_locations, PARSER_LOOKAHEAD_MATCH_ONLY, \
PARSER_LOOKAHEAD_FAILURE_ONLY, MANDATORY_CONTINUATION_AT_EOF
from DHParser.log import is_logging, clear_logs, local_log_dir, log_parsing_history
from DHParser.parse import UnknownParserError, Parser, Lookahead
from DHParser.syntaxtree import Node, RootNode, parse_tree, flatten_sxpr, ZOMBIE_TAG
......@@ -266,9 +267,9 @@ def get_report(test_unit) -> str:
POSSIBLE_ARTIFACTS = frozenset((
Error.PARSER_LOOKAHEAD_MATCH_ONLY,
Error.PARSER_LOOKAHEAD_FAILURE_ONLY,
Error.MANDATORY_CONTINUATION_AT_EOF
PARSER_LOOKAHEAD_MATCH_ONLY,
PARSER_LOOKAHEAD_FAILURE_ONLY,
MANDATORY_CONTINUATION_AT_EOF
))
......@@ -349,13 +350,13 @@ def grammar_unit(test_unit, parser_factory, transformer_factory, report='REPORT'
return False
raw_errors = cast(RootNode, syntax_tree).errors_sorted
is_artifact = ({e.code for e in raw_errors}
<= {Error.PARSER_LOOKAHEAD_FAILURE_ONLY,
# Error.PARSER_STOPPED_BEFORE_END,
Error.PARSER_LOOKAHEAD_MATCH_ONLY}
<= {PARSER_LOOKAHEAD_FAILURE_ONLY,
# PARSER_STOPPED_BEFORE_END,
PARSER_LOOKAHEAD_MATCH_ONLY}
or (len(raw_errors) == 1
and (raw_errors[-1].code == Error.PARSER_LOOKAHEAD_MATCH_ONLY
and (raw_errors[-1].code == PARSER_LOOKAHEAD_MATCH_ONLY
# case 2: mandatory lookahead failure at end of text
or raw_errors[-1].code == Error.MANDATORY_CONTINUATION_AT_EOF)))
or raw_errors[-1].code == MANDATORY_CONTINUATION_AT_EOF)))
if is_artifact:
# don't remove zombie node with error message at the end
# but change it's tag_name to indicate that it is an artifact!
......
......@@ -26,7 +26,7 @@ Grammar-object.
from typing import Tuple, Optional, List, Iterable, Union
from DHParser.error import Error
from DHParser.error import Error, RESUME_NOTICE
from DHParser.stringview import StringView
from DHParser.syntaxtree import Node, REGEXP_PTYPE, TOKEN_PTYPE, WHITESPACE_PTYPE, ZOMBIE_TAG
from DHParser.log import freeze_callstack, HistoryRecord
......@@ -64,11 +64,11 @@ def trace_history(self: Parser, text: StringView) -> Tuple[Optional[Node], Strin
notice = Error( # resume notice
'Resuming from parser "{}" at position {}:{} with parser "{}": {}'
.format(mre.node.tag_name, *lc, resumer, repr(target)),
resume_pos, Error.RESUME_NOTICE)
resume_pos, RESUME_NOTICE)
else:
notice = Error( # skip notice
'Skipping from position {}:{} within parser {}: {}'
.format(*lc, resumer, repr(target)), resume_pos, Error.RESUME_NOTICE)
.format(*lc, resumer, repr(target)), resume_pos, RESUME_NOTICE)
if grammar.resume_notices__:
grammar.tree__.add_error(mre.node, notice)
errors.append(notice)
......
......@@ -34,7 +34,7 @@ import operator
from typing import AbstractSet, Any, ByteString, Callable, cast, Container, Dict, \
Tuple, List, Sequence, Union, Optional, Text
from DHParser.error import Error, ErrorCode