Fix all 11 issue #90 parse failures; resolve issue #91's oracle-version question - #98
Conversation
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
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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
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-variablefor(i=..,j=..,k=..)—ForNoderestructured to hold a list of clauses, evaluated as nested loops viaCsgEvaluator::evalForso a later clause can reference an earlier one's variable.rotate(a, v)(angle + arbitrary axis), androtate()/mirror()with zero arguments —parseTransformpreviously required exactly one argument for every transform kind. Implemented rotate's angle-axis form viaglm::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'sbuiltin_scale()and fixed.union/difference/intersection/hull/minkowskicouldn't take any arguments at all, includingminkowski(convexity=...), a real OpenSCAD parameter.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'sh=is a genuine alias (ChiselCAD is already correct) whilesegments=is genuine but unimplemented — documented as a follow-up intests/tools/README.mdrather 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 actualtest_parser.cpp/test_csg_evaluator.cppsuites (344 cases, 1758 assertions, all passing) plus feeding all 11 corpus files through the realLexer/Parser/CsgEvaluatordirectly and confirming each parses and evaluates without error. Exact volumetric correctness against a live OpenSCAD binary remains unverified — see the environment note added todocs/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)openscad/openscad's current corpus and confirmed to parse + evaluate cleanly via a standalone driver against the realLexer/Parser/CsgEvaluatorGenerated by Claude Code