Optimized placeholder parser with str.find scanning and LRU-cached AST.
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 14s
CI / Tests (Python 3.13) (push) Successful in 14s
CI / Tests (Python 3.14) (push) Successful in 11s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
Audit / Dependencies (push) Successful in 7s
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 14s
CI / Tests (Python 3.13) (push) Successful in 14s
CI / Tests (Python 3.14) (push) Successful in 11s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
Audit / Dependencies (push) Successful in 7s
This commit is contained in:
@@ -42,6 +42,7 @@ Example AST::
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import functools
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
@@ -209,7 +210,9 @@ def _parse_nodes(
|
|||||||
"""Core recursive parser loop.
|
"""Core recursive parser loop.
|
||||||
|
|
||||||
Scans *template* starting at *pos*, accumulating literal characters and
|
Scans *template* starting at *pos*, accumulating literal characters and
|
||||||
recognising ``$(...)`` placeholder openings.
|
recognising ``$(...)`` placeholder openings. Uses :meth:`str.find` to
|
||||||
|
skip over literal spans in a single C-level call rather than iterating
|
||||||
|
character-by-character.
|
||||||
|
|
||||||
:param template: The full template string.
|
:param template: The full template string.
|
||||||
:param pos: Current scan position.
|
:param pos: Current scan position.
|
||||||
@@ -231,22 +234,59 @@ def _parse_nodes(
|
|||||||
buf.clear()
|
buf.clear()
|
||||||
|
|
||||||
while pos < length:
|
while pos < length:
|
||||||
|
# Find the next $ (potential placeholder or escape trigger) and
|
||||||
|
# ) (potential placeholder close) using C-level str.find to skip
|
||||||
|
# over literal text without per-character Python overhead.
|
||||||
|
dollar = template.find("$", pos)
|
||||||
|
close = template.find(")", pos) if inside_placeholder else -1
|
||||||
|
|
||||||
|
# Nothing special left -- rest is literal.
|
||||||
|
if dollar == -1 and close == -1:
|
||||||
|
buf.append(template[pos:])
|
||||||
|
pos = length
|
||||||
|
break
|
||||||
|
|
||||||
|
# Normalise -1 to length for min comparison.
|
||||||
|
d = dollar if dollar != -1 else length
|
||||||
|
c = close if close != -1 else length
|
||||||
|
|
||||||
|
# Closing paren before next $ -- close the current placeholder group.
|
||||||
|
if c < d:
|
||||||
|
if c > pos:
|
||||||
|
buf.append(template[pos:c])
|
||||||
|
flush_buffer()
|
||||||
|
return nodes, c + 1, True
|
||||||
|
|
||||||
# Escaped placeholder: \$(...) becomes literal text.
|
# Escaped placeholder: \$(...) becomes literal text.
|
||||||
if template[pos] == "\\" and template[pos + 1 : pos + 3] == "$(":
|
if (
|
||||||
|
dollar > 0
|
||||||
|
and dollar - 1 >= pos
|
||||||
|
and template[dollar - 1] == "\\"
|
||||||
|
and dollar + 1 < length
|
||||||
|
and template[dollar + 1] == "("
|
||||||
|
):
|
||||||
|
# Bulk-append literal text before the backslash.
|
||||||
|
if dollar - 1 > pos:
|
||||||
|
buf.append(template[pos : dollar - 1])
|
||||||
# Find the matching close paren, accounting for inner $( pairs.
|
# Find the matching close paren, accounting for inner $( pairs.
|
||||||
close = _find_matching_close(template, pos + 3)
|
match_close = _find_matching_close(template, dollar + 2)
|
||||||
if close == -1:
|
if match_close == -1:
|
||||||
# No matching close, treat everything from here as literal.
|
# No matching close, treat everything from \ onward as literal.
|
||||||
buf.append(template[pos:])
|
buf.append(template[dollar - 1 :])
|
||||||
pos = length
|
pos = length
|
||||||
else:
|
else:
|
||||||
# Emit the content (without the leading backslash) as literal.
|
# Emit the content (without the leading backslash) as literal.
|
||||||
buf.append(template[pos + 1 : close + 1])
|
buf.append(template[dollar : match_close + 1])
|
||||||
pos = close + 1
|
pos = match_close + 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Placeholder opening: $(
|
# Placeholder opening: $(
|
||||||
if template[pos : pos + 2] == "$(":
|
if dollar + 1 < length and template[dollar + 1] == "(":
|
||||||
|
# Bulk-append literal text before the $(.
|
||||||
|
if dollar > pos:
|
||||||
|
buf.append(template[pos:dollar])
|
||||||
|
pos = dollar
|
||||||
|
|
||||||
# If we've hit the nesting limit, treat $( as literal text.
|
# If we've hit the nesting limit, treat $( as literal text.
|
||||||
if depth >= max_depth:
|
if depth >= max_depth:
|
||||||
buf.append("$(")
|
buf.append("$(")
|
||||||
@@ -267,23 +307,22 @@ def _parse_nodes(
|
|||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Closing paren while inside a placeholder's children.
|
# Lone $ not followed by ( -- bulk-append up to and including it.
|
||||||
if template[pos] == ")" and inside_placeholder:
|
buf.append(template[pos : dollar + 1])
|
||||||
flush_buffer()
|
pos = dollar + 1
|
||||||
return nodes, pos + 1, True
|
|
||||||
|
|
||||||
# Ordinary character: accumulate into the literal buffer.
|
|
||||||
buf.append(template[pos])
|
|
||||||
pos += 1
|
|
||||||
|
|
||||||
flush_buffer()
|
flush_buffer()
|
||||||
return nodes, pos, False
|
return nodes, pos, False
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=256)
|
||||||
def _parse(template: str, max_depth: int = DEFAULT_MAX_DEPTH) -> list[Node]:
|
def _parse(template: str, max_depth: int = DEFAULT_MAX_DEPTH) -> list[Node]:
|
||||||
"""Parse a template string into an AST of ``Node`` objects.
|
"""Parse a template string into an AST of ``Node`` objects.
|
||||||
|
|
||||||
This is the entry point for the parser stage.
|
This is the entry point for the parser stage. Results are cached by
|
||||||
|
``(template, max_depth)`` so repeated invocations of the same command
|
||||||
|
skip parsing entirely. The evaluator never mutates the returned AST,
|
||||||
|
so sharing cached nodes across calls is safe.
|
||||||
|
|
||||||
:param template: The response template with placeholders.
|
:param template: The response template with placeholders.
|
||||||
:param max_depth: Maximum nesting depth for placeholders. ``$(`` tokens
|
:param max_depth: Maximum nesting depth for placeholders. ``$(`` tokens
|
||||||
|
|||||||
Reference in New Issue
Block a user