Summary
Add support for extracting and validating multi-line PTC-Lisp examples from the specification markdown, enabling comprehensive testing of complex examples like let bindings with multiple lines.
Context
Architecture reference: lib/ptc_runner/lisp/spec_validator.ex - current single-line extraction logic
Dependencies: None
Related issues: None
Current State
The SpecValidator extracts examples line-by-line using extract_example_from_line/1 (lines 290-311). It looks for the pattern code ; => expected on a single line.
Multi-line examples in the spec like this are not tested:
(let [x 10
y (+ x 5)] ; y can use x
(* x y)) ; => 150
Only the last line is seen, detected as a "fragment" via fragment?/1, and skipped. The fragment?/1 function (lines 317-337) is a workaround for this limitation.
Verified: Current extraction yields 129 examples. Examples like (* x y)) ; => 150 (the multi-line let) and name) ; => "Dan" (nested destructuring) are confirmed missing - fragments are correctly filtered but the full expressions are never assembled.
Acceptance Criteria
Implementation Hints
Approach: Two-pass parsing
Separate concerns into two passes for cleaner implementation:
Pass 1: Extract code blocks from markdown
- Scan for
clojure ... blocks
- Track current section header (
## N. Title)
- Output: List of
%{section: String.t(), content: String.t()} maps
Pass 2: Extract tests from each block
- Within each block's content, find lines ending with
; => expected
- For each match, scan backward to find the complete balanced expression
- Use parenthesis/bracket counting to find expression start
- Handle strings (don't count parens inside quotes)
Skip mechanism for known-failing tests:
- Add
; => SKIP or ; => TODO(#issue) marker that SpecValidator recognizes
- When encountered, skip the test but track it in results as
skipped
- Example:
(some-buggy-code) ; => SKIP or (unimplemented) ; => TODO(#314)
- This allows CI to pass while documenting what needs fixing
Files to modify:
lib/ptc_runner/lisp/spec_validator.ex:
- Add
extract_code_blocks/1 for Pass 1
- Modify
extract_examples_from_content/1 to use two-pass approach
- Add
extract_tests_from_block/2 for Pass 2
- Add
find_expression_start/2 for backward scanning with paren balancing
- Add skip detection in
parse_expected/1 for SKIP and TODO(#N) markers
- Consider removing or simplifying
fragment?/1
Patterns to follow:
- Current
extract_section_header/1 for section tracking
- Current
has_unbalanced_parens?/1 for paren counting (extend for backward scan)
Edge cases to consider:
- Parentheses inside string literals:
"(not a paren)"
- Comments before
; =>: ; comment here vs ; => result
- Multiple
; => in same block (each is a separate test)
- Nested expressions with multiple closing parens
- Empty code blocks
- Code blocks with only comments (no executable code)
Handling Discovered Failures
When multi-line tests are extracted, some may fail due to:
- Spec typos/errors - Fix directly in this PR
- Implementation bugs - Fix in this PR if small (< 30 min), otherwise:
- Create separate issue for the bug
- Mark example with
; => TODO(#issue) to skip temporarily
- Reference the issue number so it's tracked
- Unimplemented features - Create separate issue, mark with
; => TODO(#issue)
Guideline: CI must pass. Use skip markers for anything that can't be fixed in this PR.
Test Plan
Unit tests:
extract_code_blocks/1 correctly extracts blocks with section info
find_expression_start/2 handles:
- Simple single-line:
(+ 1 2)
- Multi-line let:
(let [x 1\n y 2]\n (+ x y))
- Nested parens:
(map (fn [x] (inc x)) [1 2 3])
- Strings with parens:
(str "hello (world)")
- Skip markers (
SKIP, TODO(#N)) are recognized and counted as skipped
Integration tests:
extract_examples/0 returns more examples than before (target: 140+)
- All extracted examples pass validation (no regressions)
- Specific multi-line examples from spec are captured:
(let [x 10 y (+ x 5)] (* x y)) ; => 150
- Complex destructuring examples
- Threading macro examples
E2E test:
- Run
mix ptc.validate_spec - should show increased pass count and any skipped tests
Out of Scope
- Changing the
; => marker format in the specification
- Adding
; => TYPE ERROR for negative tests (future enhancement)
- Extracting examples from non-clojure code blocks
- Performance optimization (current approach is fast enough)
Documentation Updates
None - this is an internal testing infrastructure change. The specification format remains unchanged.
Summary
Add support for extracting and validating multi-line PTC-Lisp examples from the specification markdown, enabling comprehensive testing of complex examples like
letbindings with multiple lines.Context
Architecture reference:
lib/ptc_runner/lisp/spec_validator.ex- current single-line extraction logicDependencies: None
Related issues: None
Current State
The
SpecValidatorextracts examples line-by-line usingextract_example_from_line/1(lines 290-311). It looks for the patterncode ; => expectedon a single line.Multi-line examples in the spec like this are not tested:
Only the last line is seen, detected as a "fragment" via
fragment?/1, and skipped. Thefragment?/1function (lines 317-337) is a workaround for this limitation.Verified: Current extraction yields 129 examples. Examples like
(* x y)) ; => 150(the multi-line let) andname) ; => "Dan"(nested destructuring) are confirmed missing - fragments are correctly filtered but the full expressions are never assembled.Acceptance Criteria
fragment?/1workaround can be removed or simplifiedextract_examples/0captures more examples than before (current: 129, target: 140+)spec_validator_test.exspassImplementation Hints
Approach: Two-pass parsing
Separate concerns into two passes for cleaner implementation:
Pass 1: Extract code blocks from markdown
clojure ...blocks## N. Title)%{section: String.t(), content: String.t()}mapsPass 2: Extract tests from each block
; => expectedSkip mechanism for known-failing tests:
; => SKIPor; => TODO(#issue)marker that SpecValidator recognizesskipped(some-buggy-code) ; => SKIPor(unimplemented) ; => TODO(#314)Files to modify:
lib/ptc_runner/lisp/spec_validator.ex:extract_code_blocks/1for Pass 1extract_examples_from_content/1to use two-pass approachextract_tests_from_block/2for Pass 2find_expression_start/2for backward scanning with paren balancingparse_expected/1forSKIPandTODO(#N)markersfragment?/1Patterns to follow:
extract_section_header/1for section trackinghas_unbalanced_parens?/1for paren counting (extend for backward scan)Edge cases to consider:
"(not a paren)"; =>:; comment herevs; => result; =>in same block (each is a separate test)Handling Discovered Failures
When multi-line tests are extracted, some may fail due to:
; => TODO(#issue)to skip temporarily; => TODO(#issue)Guideline: CI must pass. Use skip markers for anything that can't be fixed in this PR.
Test Plan
Unit tests:
extract_code_blocks/1correctly extracts blocks with section infofind_expression_start/2handles:(+ 1 2)(let [x 1\n y 2]\n (+ x y))(map (fn [x] (inc x)) [1 2 3])(str "hello (world)")SKIP,TODO(#N)) are recognized and counted as skippedIntegration tests:
extract_examples/0returns more examples than before (target: 140+)(let [x 10 y (+ x 5)] (* x y)) ; => 150E2E test:
mix ptc.validate_spec- should show increased pass count and any skipped testsOut of Scope
; =>marker format in the specification; => TYPE ERRORfor negative tests (future enhancement)Documentation Updates
None - this is an internal testing infrastructure change. The specification format remains unchanged.