Skip to content

feat: Implement PTC-Lisp parser infrastructure (Phase 1) - Closes #106#107

Merged
andreasronge merged 3 commits intomainfrom
claude/issue-106-20251208-181103
Dec 8, 2025
Merged

feat: Implement PTC-Lisp parser infrastructure (Phase 1) - Closes #106#107
andreasronge merged 3 commits intomainfrom
claude/issue-106-20251208-181103

Conversation

@andreasronge
Copy link
Copy Markdown
Owner

@andreasronge andreasronge commented Dec 8, 2025

Summary

Implements the foundational PTC-Lisp parser infrastructure with NimbleParsec, enabling parsing of literals, symbols, and collections.

Changes

  • lib/ptc_runner/lisp/ast.ex (41 lines): AST node types and constructors
  • lib/ptc_runner/lisp/parser_helpers.ex (65 lines): Parser reduction helpers
  • lib/ptc_runner/lisp/parser.ex (206 lines): Complete NimbleParsec parser
  • test/ptc_runner/lisp/parser_test.exs (202 lines): Comprehensive test suite

Features

✅ Literal parsing:

  • nil, true, false with boundary protection
  • Integers and floats with exponent support
  • Single-line strings with escape sequences
  • Keywords (:name, :user_id)

✅ Symbol parsing:

  • Simple symbols (filter, sort-by)
  • Operator symbols (+, ->>, >=)
  • Namespaced symbols (ctx/input, memory/results)

✅ Collections:

  • Vectors [1 2 3]
  • Maps {:a 1 :b 2}
  • Lists/S-expressions (+ 1 2)
  • Nested structures

✅ Whitespace and comments:

  • Flexible whitespace and comma as separator
  • Single-line comments (`;" to EOL)

✅ Error handling:

  • Consistent error tuples {:error, {:parse_error, message}}
  • No exceptions escape parse/1

Test Coverage

  • 36 unit tests covering all literal types
  • Symbol boundary edge cases (nilly, truthy, nil?)
  • Numeric edge cases (.5, 5., 2e10, +5)
  • Collection nesting and empty structures
  • Error cases (unclosed, odd maps, multiline strings)
  • Complex expressions (threading macros, destructuring)

Quality

✅ All tests pass (590 total, including 36 new)
✅ Credo analysis: No issues
✅ Code formatted
✅ Compiled with no warnings

Follows patterns from lib/ptc_runner/json/parser.ex for consistent error handling across DSLs.

Closes #106

Add foundational parser modules with NimbleParsec:
- lib/ptc_runner/lisp/ast.ex: AST node types and constructors
- lib/ptc_runner/lisp/parser_helpers.ex: Parser helper functions
- lib/ptc_runner/lisp/parser.ex: Complete NimbleParsec parser

Supports parsing all literal types (nil, booleans, numbers, strings, keywords),
symbols (including operator and namespaced symbols), and collections (vectors,
maps, lists). Includes comprehensive error handling and edge case coverage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 8, 2025

PR Review: feat: Implement PTC-Lisp parser infrastructure (Phase 1)

Summary

Excellent implementation of the PTC-Lisp parser using NimbleParsec. The code closely follows the specification in docs/ptc-lisp-parser-plan.md, includes comprehensive tests, and properly handles error cases. One critical issue: the NimbleParsec dependency was not explicitly added to mix.exs as required by issue #106.

What's Good

  • Spec compliance: Implementation closely follows docs/ptc-lisp-parser-plan.md (Sections 1-4)
  • Complete literal parsing: nil, booleans, integers, floats with exponents, strings with escape sequences, keywords
  • Symbol handling: Simple symbols, operators (+, ->>, >=), and namespaced symbols (ctx/input, memory/results)
  • Collection support: Vectors, maps, lists (s-expressions) with proper nesting
  • Error handling: Consistent {:error, {:parse_error, message}} tuples - no exceptions escape parse/1
  • Symbol boundaries: Proper lookahead prevents nil matching in nilly, etc.
  • Code organization: Clean separation of AST, parser, and helpers matching the spec's module structure
  • Test coverage: 36 tests covering literals, symbols, collections, whitespace, comments, and error cases

Issues (Must Fix)

  1. Missing NimbleParsec dependency - mix.exs

    • Problem: Issue [Lisp Parser] Create PTC-Lisp parser infrastructure (AST, helpers, and NimbleParsec skeleton) #106 explicitly requires adding {:nimble_parsec, "~> 1.4"} to mix.exs, but this was not done
    • Impact: While NimbleParsec happens to be available as a transitive dependency (via ex_doc/credo), this is fragile. Direct dependencies must be explicitly declared to ensure the library works standalone.
    • Fix: Add {:nimble_parsec, "~> 1.4"} to the deps list in mix.exs
    • Complexity: Mechanical (single line addition)
  2. Incorrect test count in PR description

    • Problem: PR description claims "45 unit tests" but actual count is 36
    • Impact: Minor documentation inconsistency
    • Fix: Update PR description to say "36 unit tests"
    • Complexity: Trivial

Suggestions (Optional)

  1. Unused AST.map_node/1 function - Nice-to-have

    • Observation: AST.map_node/1 is defined but never called - ParserHelpers.build_map/1 constructs map tuples directly
    • Suggestion: Either remove the unused function or use it in build_map/1 for consistency
    • Complexity: Trivial
  2. Unused AST.keyword/1 function - Nice-to-have

    • Observation: AST.keyword/1 exists but ParserHelpers.build_keyword/1 constructs keywords directly
    • Suggestion: Consider using AST.keyword/1 in build_keyword/1 for consistency, or remove the duplicate
    • Complexity: Trivial

Security

No security concerns. The String.to_atom/1 usage is documented as acceptable in the spec (Section 1 of the parser plan) due to:

  1. Programs are generated by LLMs, not arbitrary user input
  2. Each execution is sandboxed with memory limits
  3. Can be revisited if production concerns arise

Documentation

Verdict

Request Changes - The missing NimbleParsec dependency in mix.exs must be added before merge. This is explicitly required by issue #106 and is necessary for the library to work correctly when used as a standalone dependency. Once fixed, this is ready to merge.

@github-actions github-actions bot added the auto-triage-pending Auto-triage in progress label Dec 8, 2025
@andreasronge
Copy link
Copy Markdown
Owner Author

@claude please fix these issues from PR review:

Issue 1: Missing NimbleParsec dependency in mix.exs

Location: mix.exs:41-48
Problem: Issue #106 explicitly requires adding {:nimble_parsec, "~> 1.4"} to mix.exs, but this was not done. While NimbleParsec is available as a transitive dependency, direct dependencies must be explicitly declared.
Fix: Add {:nimble_parsec, "~> 1.4"} to the deps list in mix.exs, placing it after the :jason dependency and before :credo.

Issue 2: Incorrect test count in PR description

Location: PR description
Problem: PR description claims "45 unit tests" but actual count is 36 (verified with grep).
Fix: Edit the PR description to change "45 unit tests" to "36 unit tests" in the Test Coverage section.

Please fix ALL issues above in a SINGLE commit. Make minimal changes to resolve each issue.

@andreasronge
Copy link
Copy Markdown
Owner Author

Auto-Triage Summary

Decisions Made

# Issue Decision Action
1 Missing NimbleParsec dependency in mix.exs FIX_NOW Included in fix request above
2 Incorrect test count in PR description (45 vs 36) FIX_NOW Included in fix request above
3 Unused AST.map_node/1 function IGNORE Part of public API, may be used by future code or external consumers
4 Unused AST.keyword/1 function IGNORE Part of public API, valid pattern alongside direct construction

Reasoning

FIX_NOW items:

IGNORED items:

  • Suggestions 3 & 4 (unused functions): Both AST.map_node/1 and AST.keyword/1 are part of the AST module's public API and may be used by future code or external consumers. No functional issues present. Premature to remove before seeing full usage patterns.

Status

  • FIX_NOW items: 2 (batched in single fix comment above)
  • Issues created: 0
  • Items ignored: 2

Once the fixes are applied, this PR will be ready to merge.

@github-actions github-actions bot added auto-triage-cycle-1 Auto-triage cycle 1 auto-triage-complete Auto-triage completed and removed auto-triage-pending Auto-triage in progress labels Dec 8, 2025
- Add explicit NimbleParsec ~> 1.4 dependency to mix.exs (required by #106)
- Update PR description: correct test count from 45 to 36 tests

Both changes are minimal and mechanical as requested.
@andreasronge
Copy link
Copy Markdown
Owner Author

Fixes Applied

Both issues from the PR review have been fixed in a single commit:

Issue 1: Missing NimbleParsec dependency

✅ Added {:nimble_parsec, "~> 1.4"} to the deps list in mix.exs

  • Placed after the dependency and before as requested
  • This ensures NimbleParsec is an explicit direct dependency rather than relying on transitive dependencies

Issue 2: Incorrect test count

✅ Updated PR description to correct test count from 45 to 36 unit tests

  • Also updated the "All tests pass" line to reflect 590 total tests (including 36 new)

Verification

✅ All quality checks passed:

  • mix precommit passed successfully
  • Code formatted
  • Compilation successful with no warnings
  • All tests pass (585 tests, 5 doctests, 0 failures)
  • Credo analysis: No issues

Commit: 41e3bdb - fix: Add missing NimbleParsec dependency and correct test count

The PR is now ready for merge.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 8, 2025

PR Review: feat: Implement PTC-Lisp parser infrastructure (Phase 1)

Summary

Solid foundational work implementing the PTC-Lisp parser using NimbleParsec. The parser correctly handles all literal types, symbols (including namespaced), and collections. The implementation closely follows the spec in docs/ptc-lisp-parser-plan.md with one gap in keyword character support.

What's Good

  • Clean architecture: Three well-organized modules (AST, Parser, ParserHelpers) with clear responsibilities
  • Error handling: Consistent {:ok, ast} | {:error, {:parse_error, msg}} pattern, no exceptions escape parse/1
  • Comprehensive tests: 36 tests covering literals, symbols, collections, whitespace, comments, and error cases
  • Symbol boundary protection: Correctly uses lookahead_not to prevent nil/true/false from matching as prefixes
  • Spec compliance: Follows the parser plan closely - namespaced symbols, single-line strings, map validation all work correctly
  • NimbleParsec idioms: Good use of defcombinatorp, parsec(), tag(), and reduce() patterns

Issues (Must Fix)

  1. Missing ? and \! in keyword character set - lib/ptc_runner/lisp/parser.ex:98
    • Problem: Keywords cannot contain ? or \! characters, but the spec requires them
    • Spec reference: docs/ptc-lisp-specification.md Section 2.4 and grammar (lines 1387-1388):
      keyword-char = letter | digit | "-" | "_" | "?" | "\!" ;  (* no "/" in keywords *)
      
    • Current code: ascii_string([?a..?z, ?A..?Z, ?0..?9, ?-, ?_], min: 1)
    • Impact: :empty? and :valid\! would fail to parse as keywords
    • Fix: Change to ascii_string([?a..?z, ?A..?Z, ?0..?9, ?-, ?_, ??, ?\!], min: 1)
    • Complexity: Mechanical (one-line change)
    • Test to add: assert {:ok, {:keyword, :empty?}} = Parser.parse(":empty?")

Suggestions (Optional)

  1. Dead code in AST module - Nice-to-have

    • Observation: AST.keyword/1, AST.map_node/1, AST.vector/1, and AST.list/1 are defined but unused - ParserHelpers has equivalent inline implementations
    • Benefit: Could either remove dead code or consolidate by having ParserHelpers delegate to AST
    • Recommendation: Not blocking - these are defined in the spec as constructors for future interpreter use
    • Complexity: Low
  2. Consider test for empty string - Out of scope for this PR

    • The spec mentions empty strings ("") but there's no explicit test for them
    • Complexity: Trivial (would be a one-liner)

Security

No concerns. The parser correctly:

  • Uses String.to_atom/1 only on bounded parser output (not arbitrary user input)
  • Wraps exceptions in error tuples via rescue
  • Returns structured errors without exposing internals

Documentation

No updates needed - this is foundational infrastructure. The spec documents (docs/ptc-lisp-parser-plan.md, docs/ptc-lisp-specification.md) already exist and cover the implementation.

Verdict

Request Changes - The keyword character set issue is a spec compliance bug that should be fixed before merge. It's a one-line change with a simple test to add.

@github-actions github-actions bot added the auto-triage-pending Auto-triage in progress label Dec 8, 2025
@andreasronge
Copy link
Copy Markdown
Owner Author

@claude please fix this issue from PR review:

Issue 1: Missing ? and ! in keyword character set

Location: lib/ptc_runner/lisp/parser.ex:98
Problem: Keywords cannot contain ? or ! characters, but the spec requires them. The spec at docs/ptc-lisp-specification.md:1388 defines: keyword-char = letter | digit | "-" | "_" | "?" | "!"
Fix:

  1. Change line 98 from:
    |> ascii_string([?a..?z, ?A..?Z, ?0..?9, ?-, ?_], min: 1)
    to:
    |> ascii_string([?a..?z, ?A..?Z, ?0..?9, ?-, ?_, ??, ?!], min: 1)
  2. Add test cases to test/ptc_runner/lisp/parser_test.exs in the "keywords" test (around line 34):
    assert {:ok, {:keyword, :empty?}} = Parser.parse(":empty?")
    assert {:ok, {:keyword, :valid!}} = Parser.parse(":valid!")

Please fix this issue in a SINGLE commit. Make minimal changes to resolve the spec compliance bug.

@andreasronge
Copy link
Copy Markdown
Owner Author

Auto-Triage Summary (Cycle 2)

Decisions Made

# Issue Decision Action
1 Missing ? and \! in keyword character set FIX_NOW Included in fix request above
2 Dead code in AST module (unused functions) IGNORE Public API functions - may be used by future interpreter or external consumers
3 Missing empty string test IGNORE Empty string parsing already works (inherited from NimbleParsec string parser), test would be redundant

Reasoning

FIX_NOW:

  • Issue 1 (keyword characters): Spec compliance bug - the specification at docs/ptc-lisp-specification.md:1388 explicitly requires ? and \! in keyword-char. This is a mechanical fix (one line + two test cases) that completes the incomplete implementation of keyword parsing. In-scope because the PR's purpose is to implement the parser according to the spec.

IGNORED:

  • Suggestion 2 (dead code): The functions AST.keyword/1, AST.map_node/1, AST.vector/1, and AST.list/1 are part of the public API and follow the constructor pattern defined in the spec. They may be used by the future interpreter implementation or by external consumers. Not appropriate to remove at this stage.
  • Suggestion 3 (empty string test): Empty strings ("") already parse correctly via NimbleParsec's built-in string handling. Adding a test would provide no additional bug detection value - the parser behavior is inherited from the combinator library, not custom logic.

Status

  • FIX_NOW items: 1 (fix request posted above)
  • Issues created: 0
  • Items ignored: 2

Once the keyword character fix is applied, this PR will be ready to merge.

@github-actions github-actions bot added auto-triage-cycle-2 Auto-triage cycle 2 and removed auto-triage-cycle-1 Auto-triage cycle 1 auto-triage-pending Auto-triage in progress labels Dec 8, 2025
Keywords now properly support ? and ! characters as required by the
specification in docs/ptc-lisp-specification.md:1388.

Fixes: Missing keyword-char support for ? and ! in parser
@andreasronge
Copy link
Copy Markdown
Owner Author

Spec Compliance Fix: Keyword Character Set

Issue Fixed

Missing and in keyword character set

  • Location:
  • Problem: Keywords couldn't contain or characters, violating the spec requirement from
  • Fix Applied: Updated the keyword character set from to

Tests Added

Added two test cases to to verify the fix:

    • keyword with question mark
    • keyword with exclamation mark

Verification

✅ All quality checks passed:

  • Code formatted
  • Compilation successful (no warnings)
  • Credo analysis: No issues
  • All tests pass (5 doctests, 585 tests, 0 failures)

Commit: - fix: Add missing ? and ! characters to keyword character set

This PR is now spec-compliant and ready to merge.

@andreasronge
Copy link
Copy Markdown
Owner Author

Keyword Character Set Fix

Issue Fixed

Missing ? and ! in keyword character set

Fixed spec compliance bug by adding support for question mark (?) and exclamation mark (!) characters in keywords.

Changes Made:

  • Updated parser.ex line 98 to include ?, and ! in the keyword character set
  • Added test cases for :empty? and :valid! keywords

Verification:
All quality checks passed:

  • Code formatted
  • Compilation successful
  • Credo analysis: No issues
  • All tests pass (585 tests, 0 failures)

Commit: c6b36b2 - fix: Add missing ? and ! characters to keyword character set

This PR is now spec-compliant and ready to merge.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 8, 2025

PR Review: feat: Implement PTC-Lisp parser infrastructure (Phase 1)

Summary

This PR implements the foundational PTC-Lisp parser infrastructure using NimbleParsec. The implementation closely follows the detailed specification in docs/ptc-lisp-parser-plan.md and establishes the lib/ptc_runner/lisp/ module structure. Overall, this is a well-executed implementation with clean code and comprehensive tests.

What's Good

  • Faithful to spec: The implementation matches docs/ptc-lisp-parser-plan.md very closely - module structure, AST types, error handling patterns, and test coverage all align with the plan
  • Consistent error handling: Follows the JSON parser's {:error, {:parse_error, msg}} pattern with proper rescue for internal exceptions
  • Comprehensive tests: 36 tests covering literals, symbols, collections, whitespace/comments, complex expressions, error cases, and edge cases
  • Clean code structure: Well-organized with clear section comments, appropriate use of defcombinatorp for recursive combinators
  • Edge cases handled: Symbol boundary protection (nil/true/false don't match as prefixes), numeric edge cases, single-line string enforcement
  • Keywords with ? and !: The implementation correctly supports keywords like :empty? and :valid! (line 98 in parser.ex adds ??, ?! to keyword chars), going beyond the base spec in a reasonable way

Issues (Must Fix)

None identified. The implementation is complete and correct.

Suggestions (Optional)

  1. Test count discrepancy - Nice-to-have

    • Observation: The issue comment mentions "45 new unit tests" but the actual test file has 36 tests. This appears to be a minor documentation inconsistency (perhaps from an earlier iteration)
    • Impact: None - the actual test coverage is comprehensive
    • Recommendation: No action needed, just noting for accuracy
  2. Empty string test - Nice-to-have

    • Suggestion: The spec Section 5 mentions testing empty strings, but there's no explicit "" test case
    • Benefit: Explicit coverage of edge case
    • Complexity: Trivial (one line addition)
    • Recommendation: Could be added in a future PR

Security

No concerns. The implementation:

  • Uses String.to_atom/1 as documented and justified in the spec (Section 1, "Atom Safety") - acceptable for v1 given programs are LLM-generated and sandboxed
  • Properly wraps exceptions to prevent information leakage
  • No external I/O or network access

Documentation

No updates needed. The architecture.md already documents the planned lib/ptc_runner/lisp/ directory structure (lines 58-61), and the implementation follows this exactly. The extensive docs/ptc-lisp-parser-plan.md serves as the authoritative reference.

Verdict

Approve - This is a clean, well-tested implementation that faithfully follows the detailed specification. All tests pass (585 total including 36 new), code compiles with no warnings, and formatting is correct. Ready to merge.

@github-actions github-actions bot added the auto-triage-pending Auto-triage in progress label Dec 8, 2025
@andreasronge
Copy link
Copy Markdown
Owner Author

Auto-Triage Summary (Cycle 3/3 - Final)

Review Analysis

The latest review from claude-review shows Approve with no must-fix issues identified.

Decisions Made

# Issue Decision Action
1 Test count discrepancy (45 vs 36 mentioned in issue comment) IGNORE Already fixed in previous cycle - PR description now correctly states 36 tests
2 Missing empty string test suggestion IGNORE Empty strings already parse correctly via NimbleParsec, test would be redundant, not required by spec

Reasoning

All items IGNORED (no action needed):

  • Suggestion 1 (test count): This was already fixed in Cycle 1 (commit 41e3bdb). The PR description correctly shows "36 unit tests" now. The reviewer noted this as already resolved.
  • Suggestion 2 (empty string test): Empty strings ("") parse correctly via NimbleParsec's built-in string combinator. Adding an explicit test provides no additional bug detection value. The spec section the reviewer references doesn't mandate this specific test case.

Status

  • FIX_NOW items: 0
  • Issues created: 0
  • Items ignored: 2

Final Verdict

All review cycles complete - No remaining issues to address.

The PR has been through 3 review cycles with all must-fix issues resolved:

  • Cycle 1: Fixed NimbleParsec dependency and test count
  • Cycle 2: Fixed keyword character set (? and !)
  • Cycle 3: Approved with no must-fix issues

All checks are passing. Adding ready-to-merge label.

@andreasronge andreasronge added the ready-to-merge PR is ready to be merged label Dec 8, 2025
@github-actions github-actions bot added auto-triage-cycle-3 Auto-triage cycle 3 (max) and removed auto-triage-cycle-2 Auto-triage cycle 2 auto-triage-pending Auto-triage in progress labels Dec 8, 2025
@andreasronge andreasronge enabled auto-merge (squash) December 8, 2025 18:33
@andreasronge andreasronge merged commit fa53655 into main Dec 8, 2025
4 checks passed
@andreasronge andreasronge deleted the claude/issue-106-20251208-181103 branch December 8, 2025 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-triage-complete Auto-triage completed auto-triage-cycle-3 Auto-triage cycle 3 (max) ready-to-merge PR is ready to be merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Lisp Parser] Create PTC-Lisp parser infrastructure (AST, helpers, and NimbleParsec skeleton)

1 participant