Easy-to-use parser combinators for parsing in pure Python
—
Helper functions and constants that support parsy's parsing operations. These utilities provide common functionality used throughout the parsing library.
Get line and column information for positions in input streams.
def line_info_at(stream, index):
"""
Get line and column information for a position in the stream.
Args:
stream: Input string to analyze
index: Position index in the stream
Returns:
tuple: (line, column) where both are 0-indexed
Raises:
ValueError: If index is greater than stream length
"""No-operation function that returns its argument unchanged.
noop: function
"""Identity function (lambda x: x) used as default transform parameter."""from parsy import line_info_at
# Get position information
text = "line1\nline2\nline3"
position = 8 # After "line1\nli"
line, col = line_info_at(text, position)
print(f"Position {position} is at line {line}, column {col}")
# Output: Position 8 is at line 1, column 2
# Use in error reporting
def parse_with_position_info(parser, text):
try:
return parser.parse(text)
except ParseError as e:
line, col = line_info_at(text, e.index)
print(f"Parse error at line {line}, column {col}: {e}")
raise
# Track positions during parsing
@generate
def positioned_content():
start_info = yield line_info
content = yield regex(r'[^\n]+')
end_info = yield line_info
return {
'content': content,
'start': start_info,
'end': end_info
}from parsy import string, noop
# Using noop as identity transform
case_sensitive = string('Hello', transform=noop)
result = case_sensitive.parse('Hello') # Returns 'Hello'
# Custom transform functions
case_insensitive = string('Hello', transform=str.lower)
result = case_insensitive.parse('HELLO') # Returns 'Hello'
# Transform function for normalization
def normalize_whitespace(s):
return ' '.join(s.split())
normalized_string = string('hello world', transform=normalize_whitespace)
result = normalized_string.parse('hello world') # Returns 'hello world'from parsy import generate, string, regex, ParseError, line_info_at
class DetailedParseError(Exception):
def __init__(self, message, line, column, context=""):
self.message = message
self.line = line
self.column = column
self.context = context
super().__init__(f"{message} at line {line}, column {column}")
def enhanced_parse(parser, text):
"""Parse with enhanced error reporting."""
try:
return parser.parse(text)
except ParseError as e:
line, col = line_info_at(text, e.index)
# Get context around error position
lines = text.split('\n')
if line < len(lines):
context = lines[line]
pointer = ' ' * col + '^'
context_display = f"{context}\n{pointer}"
else:
context_display = ""
raise DetailedParseError(
str(e),
line + 1, # Convert to 1-indexed for human reading
col + 1, # Convert to 1-indexed for human reading
context_display
)
# Usage example
@generate
def simple_assignment():
var_name = yield regex(r'[a-zA-Z_][a-zA-Z0-9_]*')
yield string('=')
value = yield regex(r'\d+').map(int)
return (var_name, value)
try:
result = enhanced_parse(simple_assignment, 'x = abc')
except DetailedParseError as e:
print(f"Error: {e}")
print(f"Context:\n{e.context}")from parsy import line_info, generate, string, any_char
# Create a parser that tracks line information
@generate
def line_tracking_parser():
content = []
while True:
# Try to get current position
pos = yield line_info
# Try to get next character
try:
char = yield any_char
content.append((char, pos))
except:
break
return content
# Parse with line tracking
text = "a\nb\nc"
result = line_tracking_parser.parse_partial(text)
# Returns [('a', (0, 0)), ('\n', (0, 1)), ('b', (1, 0)), ('\n', (1, 1)), ('c', (2, 0))]Install with Tessl CLI
npx tessl i tessl/pypi-parsy