Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tools/hrw4u/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
build/
dist/
uv.lock
htmlcov/
.coverage
12 changes: 10 additions & 2 deletions tools/hrw4u/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ INIT_HRW4U=$(PKG_DIR_HRW4U)/__init__.py
INIT_U4WRH=$(PKG_DIR_U4WRH)/__init__.py
INIT_LSP=$(PKG_DIR_LSP)/__init__.py

.PHONY: all gen gen-fwd gen-inv copy-src test clean build package env setup-deps activate update
.PHONY: all gen gen-fwd gen-inv copy-src test clean build package env setup-deps activate update coverage coverage-open

all: gen

Expand Down Expand Up @@ -167,6 +167,14 @@ $(PKG_DIR_LSP)/%: src/%
test:
uv run pytest --tb=short tests

coverage:
uv run pytest --cov --cov-report=term-missing --cov-report=html tests
@echo ""
@echo "HTML report: open htmlcov/index.html"

coverage-open: coverage
uv run python -m webbrowser "file://$(shell pwd)/htmlcov/index.html"

# Build standalone binaries (optional)
build: gen
uv run pyinstaller --onefile --name hrw4u --strip $(SCRIPT_HRW4U)
Expand All @@ -180,7 +188,7 @@ package: gen
uv run python -m build --wheel --outdir $(DIST_DIR)

clean:
rm -rf build dist __pycache__ *.spec *.egg-info .venv
rm -rf build dist __pycache__ *.spec *.egg-info .venv htmlcov .coverage
find tests -name '__pycache__' -type d -exec rm -r {} +

setup-deps:
Expand Down
36 changes: 36 additions & 0 deletions tools/hrw4u/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies = [
[project.optional-dependencies]
dev = [
"pytest>=7.0,<8.0",
"pytest-cov>=4.1.0",
"pyinstaller>=5.0,<7.0",
"build>=0.8,<2.0",
]
Expand Down Expand Up @@ -83,4 +84,39 @@ dev = [
"build>=1.4.0",
"pyinstaller>=6.18.0",
"pytest>=7.4.4",
"pytest-cov>=4.1.0",
]

[tool.coverage.run]
source = [
"hrw4u",
"u4wrh",
]
omit = [
# ANTLR-generated files (not meaningful to cover)
"*/hrw4uLexer.py",
"*/hrw4uParser.py",
"*/hrw4uVisitor.py",
"*/u4wrhLexer.py",
"*/u4wrhParser.py",
"*/u4wrhVisitor.py",
# Unused/experimental modules
"*/kg_visitor.py",
# Fuzzy-matching suggestion engine (rapidfuzz dependency, hard to test meaningfully)
"*/suggestions.py",
# Package boilerplate
"*/__init__.py",
"*/__main__.py",
]

[tool.coverage.report]
show_missing = true
skip_empty = true
exclude_lines = [
"pragma: no cover",
"if __name__ == .__main__.",
"if TYPE_CHECKING:",
]

[tool.coverage.html]
directory = "htmlcov"
13 changes: 4 additions & 9 deletions tools/hrw4u/src/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class HeaderOperations:
DESTINATION_OPERATIONS: Final = (MagicStrings.RM_DESTINATION.value, MagicStrings.SET_DESTINATION.value)


class LexerProtocol(Protocol):
class LexerProtocol(Protocol): # pragma: no cover
"""Protocol for ANTLR lexers."""

def removeErrorListeners(self) -> None:
Expand All @@ -81,7 +81,7 @@ def addErrorListener(self, listener: Any) -> None:
...


class ParserProtocol(Protocol):
class ParserProtocol(Protocol): # pragma: no cover
"""Protocol for ANTLR parsers."""

def removeErrorListeners(self) -> None:
Expand All @@ -96,7 +96,7 @@ def program(self) -> Any:
errorHandler: BailErrorStrategy | DefaultErrorStrategy


class VisitorProtocol(Protocol):
class VisitorProtocol(Protocol): # pragma: no cover
"""Protocol for ANTLR visitors."""

def visit(self, tree: Any) -> list[str]:
Expand All @@ -112,12 +112,7 @@ def fatal(message: str) -> NoReturn:
def create_base_parser(description: str) -> tuple[argparse.ArgumentParser, argparse._MutuallyExclusiveGroup]:
"""Create base argument parser with common options."""
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
"input_file",
help="The input file to parse (default: stdin)",
nargs="?",
type=argparse.FileType("r", encoding="utf-8"),
default=sys.stdin)
parser.add_argument("input_file", help="Optional input file path (default: reads from stdin)", nargs="?", default=None)

output_group = parser.add_mutually_exclusive_group()
output_group.add_argument("--ast", action="store_true", help="Produce the ANTLR parse tree only")
Expand Down
11 changes: 0 additions & 11 deletions tools/hrw4u/src/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from __future__ import annotations

import sys
import types

from .common import SystemDefaults

Expand All @@ -36,16 +35,6 @@ def __call__(self, msg: str, *, levels: bool = False, out: bool = False) -> None
msg = f"</{msg}>" if out else f"<{msg}>"
print(f"{SystemDefaults.DEBUG_PREFIX} {' ' * (self.indent * SystemDefaults.INDENT_SPACES)}{msg}", file=sys.stderr)

def __enter__(self) -> "Dbg":
if self.enabled:
self.indent += 1
return self

def __exit__(
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None) -> None:
if self.enabled:
self.indent = max(0, self.indent - 1)

def enter(self, msg: str) -> None:
if self.enabled:
self(msg, levels=True)
Expand Down
8 changes: 0 additions & 8 deletions tools/hrw4u/src/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ def __init__(self, filename: str, line: int, column: int, message: str, source_l
self.column = column
self.source_line = source_line

def add_context_note(self, context: str) -> None:
"""Add contextual information using Python 3.11+ exception notes."""
self.add_note(f"Context: {context}")

def add_resolution_hint(self, hint: str) -> None:
"""Add resolution hint using Python 3.11+ exception notes."""
self.add_note(f"Hint: {hint}")

def _format_error(self, filename: str, line: int, col: int, message: str, source_line: str) -> str:
error = f"{filename}:{line}:{col}: error: {message}"

Expand Down
43 changes: 0 additions & 43 deletions tools/hrw4u/src/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from __future__ import annotations

from typing import Any
from functools import cache
from hrw4u.states import SectionType


Expand All @@ -40,33 +39,6 @@ def _clean_tag(tag: str) -> str:
"""Extract clean tag name from %{TAG:payload} format."""
return tag.strip().removeprefix('%{').removesuffix('}').split(':')[0]

def generate_reverse_condition_map(self, condition_map: tuple[tuple[str, Any], ...]) -> dict[str, str]:
"""Generate reverse condition mapping from forward condition map."""
reverse_map = {}

for ident_key, params in condition_map:
if not ident_key.endswith('.'):
tag = params.target if params else None
if tag:
clean_tag = self._clean_tag(tag)
reverse_map[clean_tag] = ident_key

return reverse_map

def generate_reverse_function_map(self, function_map: tuple[tuple[str, Any], ...]) -> dict[str, str]:
"""Generate reverse function mapping from forward function map."""
return {params.target: func_name for func_name, params in function_map}

@cache
def generate_section_hook_mapping(self) -> dict[str, str]:
"""Generate section name to hook name mapping."""
return {section.value: section.hook_name for section in SectionType}

@cache
def generate_hook_section_mapping(self) -> dict[str, str]:
"""Generate hook name to section name mapping."""
return {section.hook_name: section.value for section in SectionType}

def generate_ip_mapping(self) -> dict[str, str]:
"""Generate IP payload to identifier mapping from CONDITION_MAP."""
from hrw4u.tables import CONDITION_MAP
Expand Down Expand Up @@ -161,21 +133,6 @@ def generate_complete_reverse_resolution_map(self) -> dict[str, Any]:
_table_generator = TableGenerator()


def get_reverse_condition_map(condition_map: dict[str, tuple]) -> dict[str, str]:
"""Get reverse condition mapping."""
return _table_generator.generate_reverse_condition_map(tuple(condition_map.items()))


def get_reverse_function_map(function_map: dict[str, Any]) -> dict[str, str]:
"""Get reverse function mapping."""
return _table_generator.generate_reverse_function_map(tuple(function_map.items()))


def get_section_mappings() -> tuple[dict[str, str], dict[str, str]]:
"""Get both section->hook and hook->section mappings."""
return (_table_generator.generate_section_hook_mapping(), _table_generator.generate_hook_section_mapping())


def get_complete_reverse_resolution_map() -> dict[str, Any]:
"""Get the complete generated reverse resolution map."""
return _table_generator.generate_complete_reverse_resolution_map()
10 changes: 0 additions & 10 deletions tools/hrw4u/src/interning.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ def intern_lsp_string(cls, string: str) -> str:
"""Intern an LSP-related string, returning the interned version if available."""
return cls.LSP_STRINGS.get(string, sys.intern(string))

@classmethod
def intern_any(cls, string: str) -> str:
"""General-purpose string interning with fallback to sys.intern()."""
return sys.intern(string)


def intern_keyword(keyword: str) -> str:
"""Intern language keywords."""
Expand All @@ -105,8 +100,3 @@ def intern_modifier(modifier: str) -> str:
def intern_lsp_string(string: str) -> str:
"""Intern LSP-related strings."""
return StringInterning.intern_lsp_string(string)


def intern_any(string: str) -> str:
"""General-purpose string interning."""
return StringInterning.intern_any(string)
34 changes: 1 addition & 33 deletions tools/hrw4u/src/symbols_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from __future__ import annotations

from functools import cached_property, lru_cache
from typing import Callable, Any
from typing import Any
from hrw4u.debugging import Dbg
from hrw4u.states import SectionType
from hrw4u.common import SystemDefaults
Expand All @@ -30,11 +30,6 @@ class SymbolResolverBase:

def __init__(self, debug: bool = SystemDefaults.DEFAULT_DEBUG) -> None:
self._dbg = Dbg(debug)
# Clear caches when debug status changes to ensure consistency
if hasattr(self, '_condition_cache'):
self._condition_cache.cache_clear()
if hasattr(self, '_operator_cache'):
self._operator_cache.cache_clear()

# Cached table access for performance - Python 3.11+ cached_property
@cached_property
Expand All @@ -53,10 +48,6 @@ def _function_map(self) -> dict[str, types.MapParams]:
def _statement_function_map(self) -> dict[str, types.MapParams]:
return tables.STATEMENT_FUNCTION_MAP

@cached_property
def _reverse_resolution_map(self) -> dict[str, Any]:
return tables.REVERSE_RESOLUTION_MAP

def validate_section_access(self, name: str, section: SectionType | None, allowed_sections: set[SectionType] | None) -> None:
if section and allowed_sections and section not in allowed_sections:
raise SymbolResolutionError(name, f"{name} is not available in the {section.value} section")
Expand All @@ -65,10 +56,6 @@ def validate_section_access(self, name: str, section: SectionType | None, allowe
def _lookup_condition_cached(self, name: str) -> types.MapParams | None:
return self._condition_map.get(name)

@lru_cache(maxsize=256)
def _lookup_operator_cached(self, name: str) -> types.MapParams | None:
return self._operator_map.get(name)

@lru_cache(maxsize=128)
def _lookup_function_cached(self, name: str) -> types.MapParams | None:
return self._function_map.get(name)
Expand All @@ -90,32 +77,13 @@ def _debug_exit(self, method_name: str, result: Any = None) -> None:
else:
self._dbg.exit(method_name)

def _debug_log(self, message: str) -> None:
self._dbg(message)

def _create_symbol_error(self, symbol_name: str, message: str) -> SymbolResolutionError:
return SymbolResolutionError(symbol_name, message)

def _handle_unknown_symbol(self, symbol_name: str, symbol_type: str) -> SymbolResolutionError:
return self._create_symbol_error(symbol_name, f"Unknown {symbol_type}: '{symbol_name}'")

def _handle_validation_error(self, symbol_name: str, validation_message: str) -> SymbolResolutionError:
return self._create_symbol_error(symbol_name, validation_message)

def find_prefix_matches(self, target: str, table: dict[str, Any]) -> list[tuple[str, Any]]:
matches = []
for key, value in table.items():
if key.endswith('.') and target.startswith(key):
matches.append((key, value))
return matches

def get_longest_prefix_match(self, target: str, table: dict[str, Any]) -> tuple[str, Any] | None:
matches = self.find_prefix_matches(target, table)
if not matches:
return None
matches.sort(key=lambda x: len(x[0]), reverse=True)
return matches[0]

def debug_context(self, method_name: str, *args: Any):

class DebugContext:
Expand Down
26 changes: 0 additions & 26 deletions tools/hrw4u/src/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import re
from typing import Callable
from hrw4u.errors import SymbolResolutionError
from hrw4u import states
import hrw4u.types as types
from hrw4u.common import RegexPatterns

Expand Down Expand Up @@ -70,12 +69,6 @@ def http_token(self) -> 'ValidatorChain':
def http_header_name(self) -> 'ValidatorChain':
return self._add(self._wrap_args(Validator.http_header_name()))

def simple_token(self) -> 'ValidatorChain':
return self._add(self._wrap_args(Validator.simple_token()))

def regex_literal(self) -> 'ValidatorChain':
return self._add(self._wrap_args(Validator.regex_literal()))

def nbit_int(self, nbits: int) -> 'ValidatorChain':
return self._add(self._wrap_args(Validator.nbit_int(nbits)))

Expand Down Expand Up @@ -164,16 +157,6 @@ def validator(value: str) -> None:

return validator

@staticmethod
def simple_token() -> Callable[[str], None]:
"""Validate simple tokens (letters, digits, underscore, dash)."""
return Validator.regex_validator(Validator._SIMPLE_TOKEN_RE, "Must be a simple token (letters, digits, underscore, dash)")

@staticmethod
def regex_literal() -> Callable[[str], None]:
"""Validate regex literals in /pattern/ format."""
return Validator.regex_validator(Validator._REGEX_LITERAL_RE, "Must be a valid regex literal in /pattern/ format")

@staticmethod
def http_token() -> Callable[[str], None]:
"""Validate HTTP tokens according to RFC 7230."""
Expand Down Expand Up @@ -245,15 +228,6 @@ def needs_quotes(value: str) -> bool:
def quote_if_needed(value: str) -> str:
return f'"{value}"' if Validator.needs_quotes(value) else value

@staticmethod
def logic_modifier() -> Callable[[str], None]:

def validator(value: str) -> None:
if value.upper() not in states.ALL_MODIFIERS:
raise SymbolResolutionError(value, f"Invalid logic modifier: {value}")

return validator

@staticmethod
def percent_block() -> Callable[[str], None]:

Expand Down
Loading