A Python parsing module providing an alternative approach to creating and executing simple grammars
—
Testing utilities and debugging tools for parser development. PyParsing provides comprehensive facilities for testing parser behavior, debugging parsing issues, and validating grammar correctness through built-in test runners and diagnostic tools.
The pyparsing_test class provides methods for testing and validating parser behavior.
class pyparsing_test:
"""Testing utilities for pyparsing expressions."""
@staticmethod
def with_line_numbers(s: str, start_line: int = 1) -> str:
"""Add line numbers to a string for easier debugging."""
@staticmethod
def replace_htmlentity(t: ParseResults) -> str:
"""Replace HTML entities in parsed results."""Functions for tracing and debugging parse actions.
def trace_parse_action(f: callable) -> callable:
"""Decorator to trace parse action execution."""
def null_debug_action(*args) -> None:
"""No-op debug action for testing."""Usage examples:
# Trace parse action execution
@trace_parse_action
def convert_to_int(tokens):
return int(tokens[0])
number_parser = Word(nums).set_parse_action(convert_to_int)
# Use null action for testing
test_parser = Word(alphas).set_parse_action(null_debug_action)Built-in debugging capabilities for parser elements.
class ParserElement:
def set_debug(self, flag: bool = True) -> ParserElement:
"""Enable/disable debug output for this element."""
def run_tests(self, tests: str,
parse_all: bool = True,
comment: str = '#',
full_dump: bool = True,
print_results: bool = True,
failure_tests: bool = False) -> tuple:
"""Run a series of test strings against this parser."""Usage examples:
# Enable debugging for specific parser elements
number = Word(nums).set_debug()
operator = oneOf("+ - * /").set_debug()
expr = number + operator + number
# Run comprehensive tests
test_cases = """
# Valid expressions
5 + 3
10 - 2
7 * 4
# Invalid expressions (should fail)
5 +
* 3
5 & 3
"""
results = expr.run_tests(test_cases, failure_tests=True)Methods for running systematic tests against parsers.
Basic test running:
# Simple test cases
parser = Word(alphas) + Word(nums)
test_strings = """
hello 123
world 456
test 789
"""
# Run tests with automatic validation
results = parser.run_tests(test_strings)
print(f"Passed: {results[0]}, Failed: {results[1]}")Advanced test configuration:
# Comprehensive test setup
email_parser = Regex(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
email_tests = """
# Valid email addresses
user@example.com
test.email+tag@domain.co.uk
user123@test-domain.com
# Invalid email addresses (failure tests)
@example.com
user@
user..name@example.com
"""
# Run with custom settings
results = email_parser.run_tests(
email_tests,
parse_all=True, # Require complete string match
comment='#', # Comment character
full_dump=True, # Show full parse results
print_results=True, # Print results to console
failure_tests=True # Include tests that should fail
)Global diagnostic settings for debugging parser behavior.
class __diag__:
"""Diagnostic configuration for pyparsing."""
warn_multiple_tokens_in_named_alternation: bool
warn_ungrouped_named_tokens_in_collection: bool
warn_name_set_on_empty_Forward: bool
warn_on_parse_using_empty_Forward: bool
warn_on_assignment_to_Forward: bool
warn_on_multiple_string_args_to_oneof: bool
enable_debug_on_named_expressions: bool
def enable_diag(diag_enum) -> None:
"""Enable specific diagnostic option."""
def disable_diag(diag_enum) -> None:
"""Disable specific diagnostic option."""Usage examples:
# Enable specific diagnostics
from pyparsing import __diag__, enable_diag, disable_diag
# Enable warning for multiple tokens in named alternations
enable_diag(__diag__.warn_multiple_tokens_in_named_alternation)
# Enable debug output for all named expressions
enable_diag(__diag__.enable_debug_on_named_expressions)Utilities for interactive parser development and testing.
Interactive testing pattern:
def test_parser_interactively(parser, name="parser"):
"""Interactive testing function for development."""
print(f"Testing {name}. Enter 'quit' to exit.")
while True:
try:
test_input = input(f"{name}> ")
if test_input.lower() == 'quit':
break
result = parser.parse_string(test_input, parse_all=True)
print(f"SUCCESS: {result}")
print(f"Type: {type(result)}")
except ParseException as pe:
print(f"PARSE ERROR: {pe}")
print(f"Location: Line {pe.lineno}, Col {pe.col}")
print(f"Context: {pe.mark_input_line()}")
except Exception as e:
print(f"ERROR: {e}")
# Usage
arithmetic_parser = infix_notation(Word(nums), [
('+', 2, opAssoc.LEFT),
('*', 3, opAssoc.LEFT),
])
# test_parser_interactively(arithmetic_parser, "arithmetic")Methods for testing parser performance and optimization.
Performance measurement:
import time
from pyparsing import *
def benchmark_parser(parser, test_data, iterations=1000):
"""Benchmark parser performance."""
start_time = time.time()
for _ in range(iterations):
for test_string in test_data:
try:
parser.parse_string(test_string)
except ParseException:
pass # Ignore parsing failures for benchmarking
end_time = time.time()
total_time = end_time - start_time
print(f"Parser benchmarked:")
print(f" Total time: {total_time:.4f} seconds")
print(f" Iterations: {iterations}")
print(f" Test strings: {len(test_data)}")
print(f" Average per parse: {total_time / (iterations * len(test_data)) * 1000:.4f} ms")
# Example usage
csv_parser = delimited_list(Word(alphanums + "._-"))
test_data = [
"apple,banana,cherry",
"one,two,three,four,five",
"test.file,data.csv,results.txt"
]
benchmark_parser(csv_parser, test_data)Strategies for debugging complex recursive grammars.
Grammar debugging pattern:
def debug_grammar():
"""Example of debugging a complex grammar."""
# Enable comprehensive debugging
enable_diag(__diag__.enable_debug_on_named_expressions)
# Create grammar with meaningful names
expr = Forward().set_name("expression")
term = Forward().set_name("term")
factor = Forward().set_name("factor")
number = Word(nums).set_name("number")
identifier = Word(alphas).set_name("identifier")
factor <<= (number | identifier | ("(" + expr + ")")).set_name("factor_def")
term <<= (factor + ZeroOrMore(("*" | "/") + factor)).set_name("term_def")
expr <<= (term + ZeroOrMore(("+" | "-") + term)).set_name("expr_def")
# Enable debugging on key elements
expr.set_debug()
term.set_debug()
factor.set_debug()
# Test with problematic input
test_input = "2 + 3 * (4 - 1)"
try:
result = expr.parse_string(test_input)
print(f"Parse successful: {result}")
except ParseException as pe:
print(f"Parse failed: {pe}")
print(pe.explain())
# debug_grammar()Integration with Python's unittest framework.
import unittest
from pyparsing import *
class TestMyParsers(unittest.TestCase):
"""Unit tests for custom parsers."""
def setUp(self):
"""Set up test parsers."""
self.number_parser = Word(nums).set_parse_action(lambda t: int(t[0]))
self.email_parser = Regex(r'[^@]+@[^@]+\.[^@]+')
def test_number_parsing(self):
"""Test number parser."""
result = self.number_parser.parse_string("123")
self.assertEqual(result[0], 123)
self.assertIsInstance(result[0], int)
def test_number_parsing_failure(self):
"""Test number parser failure cases."""
with self.assertRaises(ParseException):
self.number_parser.parse_string("abc")
def test_email_parsing(self):
"""Test email parser."""
result = self.email_parser.parse_string("user@example.com")
self.assertEqual(result[0], "user@example.com")
def test_multiple_test_cases(self):
"""Test multiple cases efficiently."""
test_cases = [
("123", [123]),
("456", [456]),
("789", [789]),
]
for input_str, expected in test_cases:
with self.subTest(input_str=input_str):
result = self.number_parser.parse_string(input_str)
self.assertEqual(result.as_list(), expected)
# Run tests
if __name__ == '__main__':
unittest.main()Install with Tessl CLI
npx tessl i tessl/pypi-pyparsing