diff --git a/gitgalaxy/core/detector.py b/gitgalaxy/core/detector.py index 429144085..0e2b55831 100644 --- a/gitgalaxy/core/detector.py +++ b/gitgalaxy/core/detector.py @@ -3377,6 +3377,45 @@ def _count_haskell_pattern_list(self, text: str) -> int: i += 1 return count + def _count_tcl_arg_list(self, text: str) -> int: + """ + Counts Tcl argument list parameters, aware of brace nesting. + Tcl uses {name default_value} for optional arguments. This entire + nested brace structure counts as a single parameter. + Whitespace separates parameters only at depth 0. + """ + depth = 0 + count = 0 + at_boundary = True + i = 0 + while i < len(text): + ch = text[i] + if ch == "\\": + i += 2 + continue + if ch == "{": + if depth == 0 and at_boundary: + count += 1 + at_boundary = False + depth += 1 + i += 1 + continue + if ch == "}": + if depth > 0: + depth -= 1 + i += 1 + continue + if ch in " \t\r\n": + if depth == 0: + at_boundary = True + i += 1 + continue + if depth == 0 and at_boundary: + count += 1 + at_boundary = False + i += 1 + return count + def _calculate_block_metrics( self, name: str, @@ -3479,6 +3518,7 @@ def _calculate_block_metrics( arrow_count_groups = rules.get("_args_arrow_count_groups") colon_selector_groups = rules.get("_args_colon_selector_groups") pattern_list_groups = rules.get("_args_pattern_list_groups") + tcl_pattern_list_groups = rules.get("_args_tcl_pattern_list_groups") findall_max_groups = rules.get("_args_findall_max_groups") findall_sum_groups = rules.get("_args_findall_sum_groups") if findall_max_groups and arg_match.lastindex in findall_max_groups: @@ -3526,6 +3566,11 @@ def _calculate_block_metrics( else: total += 1 args_count = total + elif tcl_pattern_list_groups and arg_match.lastindex in tcl_pattern_list_groups: + # Tcl default-value braces (#1512): + # Tcl allows nested braces like {db db} for default + # parameter values, which should count as a single argument. + args_count = self._count_tcl_arg_list(stripped) elif pattern_list_groups and arg_match.lastindex in pattern_list_groups: # Haskell signature-less equation LHS (#1505 follow-up): # a naive whitespace split would wrongly split a single diff --git a/gitgalaxy/standards/language_standards.py b/gitgalaxy/standards/language_standards.py index 197527cad..0cf5d4018 100644 --- a/gitgalaxy/standards/language_standards.py +++ b/gitgalaxy/standards/language_standards.py @@ -12734,6 +12734,8 @@ class PrismConfigSchema(TypedDict): r"^[ \t]*proc[ \t\n]+[a-zA-Z0-9_:\-!?]+[ \t\n]+\{((?:[^{}]|\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*)\}", re.M, ), + # Tcl default-value argument lists (#1512): opt-in for depth-aware braced parameter parsing. + "_args_tcl_pattern_list_groups": {1}, # 3. linear (Sequential Boundaries) # Structural boundaries. EXCLUDES: global/upvar (globals/heat). "structural_boundaries": re.compile(r"\b(?:proc|return|break|continue|namespace|variable|yield)\b"), diff --git a/tests/extraction/languages/test_tcl.py b/tests/extraction/languages/test_tcl.py index 55423d7a0..d756f752f 100644 --- a/tests/extraction/languages/test_tcl.py +++ b/tests/extraction/languages/test_tcl.py @@ -54,6 +54,8 @@ ("proc TargetFunc {a b} {", "a b"), ("proc ::namespace::TargetFunc {args} {", "args"), ("proc TargetFunc {{a 1} b} {", "{a 1} b"), + ("proc faultsim_integrity_check {{db db}} {", "{db db}"), + ("proc TargetFunc {a {b 2} {c {nested list}}} {", "a {b 2} {c {nested list}}"), ], "invalid": [ "TargetFunc a b",