CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyparsing

A Python parsing module providing an alternative approach to creating and executing simple grammars

Pending
Overview
Eval results
Files

testing-debugging.mddocs/

Testing and Debugging

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.

Capabilities

Testing Utilities

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."""

Parse Action Debugging

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)

Parser Element Debugging

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)

Test Running and Validation

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
)

Diagnostic Configuration

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)

Interactive Testing and Development

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")

Performance Testing

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)

Debugging Complex Grammars

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()

Unit Testing Integration

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

docs

common-expressions.md

core-elements.md

enhancement.md

exceptions.md

helpers.md

index.md

testing-debugging.md

tile.json