Skip to content

Fix all 11 issue #90 parse failures; resolve issue #91's oracle-version question - #98

Merged
particlesector merged 3 commits into
mainfrom
claude/issue-list-review-dzh93p
Jul 30, 2026
Merged

Fix all 11 issue #90 parse failures; resolve issue #91's oracle-version question#98
particlesector merged 3 commits into
mainfrom
claude/issue-list-review-dzh93p

Conversation

@particlesector

Copy link
Copy Markdown
Owner

Summary

Root-caused and fixed every construct in issue #90's 11-file corpus list that was taking down parsing:

  • for() with an empty argument list, and multi-variable for(i=..,j=..,k=..)ForNode restructured to hold a list of clauses, evaluated as nested loops via CsgEvaluator::evalFor so a later clause can reference an earlier one's variable.
  • rotate(a, v) (angle + arbitrary axis), and rotate()/mirror() with zero arguments — parseTransform previously required exactly one argument for every transform kind. Implemented rotate's angle-axis form via glm::rotate(angle, axis), and gave each transform its own OpenSCAD-documented default for a missing argument instead of one shared {0,0,0}.
  • scale(2) (a bare scalar) was only scaling the Z axis instead of broadcasting uniformly — a real, silent correctness bug found while fixing the above; confirmed against real OpenSCAD's builtin_scale() and fixed.
  • union/difference/intersection/hull/minkowski couldn't take any arguments at all, including minkowski(convexity=...), a real OpenSCAD parameter.
  • module definitions required a brace-block body; a single-statement body with no braces (a very common one-liner idiom) failed to parse.
  • a leaf primitive (cube/sphere/.../polygon) followed by a trailing child statement failed to parse.
  • polygon()'s positional (unnamed) points list was fundamentally broken: parseParamList's generic [x,y,z]-triple decoding silently misrouted a 3-point polygon's points under the wrong keys, and hard-failed to parse 4+ points outright (the common case for any real polygon).

Also resolves issue #91: checked real OpenSCAD's current source directly and confirmed linear_extrude's h= is a genuine alias (ChiselCAD is already correct) while segments= is genuine but unimplemented — documented as a follow-up in tests/tools/README.md rather than guessed at without a way to verify it.

Closes #90
Closes #91

Verification

This environment has no live OpenSCAD oracle and no buildable Manifold (network access is scoped to this repo only), so every fix here was instead verified by compiling the GPU/Manifold-free language+CSG subsystem for real against glm/nlohmann-json/zlib/a from-source Catch2, and running the actual test_parser.cpp/test_csg_evaluator.cpp suites (344 cases, 1758 assertions, all passing) plus feeding all 11 corpus files through the real Lexer/Parser/CsgEvaluator directly and confirming each parses and evaluates without error. Exact volumetric correctness against a live OpenSCAD binary remains unverified — see the environment note added to docs/roadmap.md's v3.10 section.

Test plan

  • test_parser.cpp / test_csg_evaluator.cpp — 344 cases, 1758 assertions, all passing (new [bugfix]-tagged regression tests added for every fix above)
  • All 11 files from issue [Medium] Parser fails on several 3D/features corpus files (for-loop / transform-argument grammar edge cases) #90's list re-fetched from openscad/openscad's current corpus and confirmed to parse + evaluate cleanly via a standalone driver against the real Lexer/Parser/CsgEvaluator
  • Volumetric comparison against a live OpenSCAD build (not possible in this environment — flagged as follow-up)

Generated by Claude Code

claude added 2 commits July 26, 2026 18:27
Parser::parseFor() unconditionally expected an identifier right after
'(', so a bare `for()` — valid OpenSCAD, since the argument list is a
generic module-call argument list that's allowed to be empty, as seen
in upstream's for-tests.scad — took down parsing of the whole file.

Now `for()` parses with an empty ForNode::var sentinel (never otherwise
possible, since a parsed loop variable is always a non-empty
identifier), and CsgEvaluator::evalFor() treats that as zero
iterations, matching real OpenSCAD's builtin_for() (checks
`!inst->arguments.empty()` and skips the body entirely rather than
running it once vacuously).

Also resolves the v3.9 oracle-version caveat for linear_extrude's h=
and segments= parameters (issue #91) by checking real OpenSCAD's
current source directly: h= is a genuine alias for height (ChiselCAD
is correct, not ahead of spec), while segments= is genuine but
entirely unimplemented in ChiselCAD — filed as a follow-up rather than
guessed at blind, documented in docs/roadmap.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pn9TSaMhpqzA7iSKiQskrr
…on question

Root-caused and fixed every construct in the issue #90 corpus file list that
was taking down parsing:

- for() with an empty argument list, and multi-variable for(i=..,j=..,k=..)
  (ForNode restructured to hold a list of clauses, evaluated as nested loops
  via CsgEvaluator::evalFor so a later clause can reference an earlier one's
  variable).
- rotate(a, v) (angle + arbitrary axis), and rotate()/mirror() with zero
  arguments — parseTransform previously required exactly one argument for
  every transform kind. Implemented rotate's angle-axis form via
  glm::rotate(angle, axis), and gave each transform its own OpenSCAD-
  documented default for a missing argument instead of one shared {0,0,0}.
- scale(2) (a bare scalar) was only scaling the Z axis instead of
  broadcasting uniformly — a real, silent correctness bug found while fixing
  the above; confirmed against real OpenSCAD's builtin_scale() and fixed.
- union/difference/intersection/hull/minkowski couldn't take any arguments,
  including minkowski(convexity=...), a real OpenSCAD parameter.
- module definitions required a brace-block body; a single-statement body
  with no braces (a very common one-liner idiom) failed to parse.
- a leaf primitive (cube/sphere/.../polygon) followed by a trailing child
  statement failed to parse.
- polygon()'s positional (unnamed) points list was fundamentally broken:
  parseParamList's generic [x,y,z]-triple decoding silently misrouted a
  3-point polygon's points under the wrong keys, and hard failed to parse
  4+ points outright (the common case for any real polygon).

Also resolves issue #91: checked real OpenSCAD's current source directly
and confirmed linear_extrude's h= is a genuine alias (ChiselCAD is already
correct) while segments= is genuine but unimplemented — documented as a
follow-up in tests/tools/README.md rather than guessed at without a way to
verify it.

This environment has no live OpenSCAD oracle and no buildable Manifold
(network access is scoped to this repo only), so every fix here was instead
verified by compiling the GPU/Manifold-free language+CSG subsystem for real
against glm/nlohmann-json/zlib/a from-source Catch2, and running the actual
test_parser.cpp/test_csg_evaluator.cpp suites (344 cases, 1758 assertions,
all passing) plus feeding all 11 corpus files through the real Lexer/Parser/
CsgEvaluator directly. Exact volumetric correctness against a live OpenSCAD
binary remains unverified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pn9TSaMhpqzA7iSKiQskrr

@particlesector particlesector left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff (Parser.cpp/.h, AST.h, CsgEvaluator.cpp, tests, docs). This is a solid, well-scoped fix set — each bug is root-caused against real OpenSCAD semantics, and every fix has a matching regression test (checked ForClause refactor didn't leave any stale ForNode::var/range references elsewhere in the tree — it didn't).

One minor note: parseTransform's new "single positional arg is now optional" branch also makes multmatrix() (no arguments) parse successfully, silently falling back to an identity matrix (CsgEvaluator::makeMatrix's Kind::Matrix case treats a null/non-vector t.vec as identity). Unlike translate()/scale()/mirror(), multmatrix's matrix argument is always required in real OpenSCAD, so this wasn't an intentional target of this PR — worth a quick check on whether that's desired permissiveness or should still error, but not blocking.

Nice, thorough work otherwise — approving in spirit, nothing else stood out.


Generated by Claude Code

…ntity

Verified against real OpenSCAD's source (TransformNode.cc's
builtin_multmatrix()): it also silently defaults to identity when its "m"
argument is absent or not a vector, no error or warning. ChiselCAD's
existing behavior already matched; added a regression test to make that
explicit rather than accidental.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pn9TSaMhpqzA7iSKiQskrr

@particlesector particlesector left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging the multmatrix()-with-no-args case. Checked real OpenSCAD's source directly (src/core/TransformNode.cc's builtin_multmatrix()): it binds a single "m" parameter and only applies it when parameters["m"].type() == Value::Type::VECTOR — when m is absent (or any other non-vector), that block is skipped with no error or warning, and the node's matrix stays at its constructor default of Identity().

So a bare multmatrix() silently becoming identity isn't unintended permissiveness from this PR's parser change — it's exactly real OpenSCAD's own documented behavior. Pushed fe12c26 adding a regression test (CsgEval:multmatrix() with no arguments is identity) that pins this explicitly and cites the source, so it's no longer just an accidental side effect of the more general fix.


Generated by Claude Code

@particlesector
particlesector merged commit f73cc59 into main Jul 30, 2026
6 checks passed
@particlesector
particlesector deleted the claude/issue-list-review-dzh93p branch July 30, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants