Skip to content

fix(#170): stop rewriting "-" as "-1*" before compiling - #223

Merged
sspickle merged 1 commit into
masterfrom
fix/170-negative-sign
Nov 1, 2026
Merged

sspickle merged 1 commit into
masterfrom
fix/170-negative-sign

Conversation

@sspickle

@sspickle sspickle commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #170. Before compiling, the preprocessor rewrote every - as -1*, except after e/E in a number, after **, and before =. The goal was for -a**.5 to mean -(a**.5). But -1* binds tighter than the operator in front of it, so any negative operand after / or // came out wrong:

Program Rewritten Result Python
3/-3 3/-1*3 −9 −1
7 // -2 7//-1*2 −14 −4
v/-2 <−2, −4, −6> <−0.5, −1, −1.5>

RapydScript already gives unary minus the correct precedence relative to ** (-10**2 == -100, -x**-2 == -0.04). The vector operator overloading handles unary minus itself. So this PR just removes the rewrite.

Testing

I used a 39-case program run through GlowScriptOffline, with Python's own results as the expected answers.

master this PR
7 division cases (3/-3, 7 // -2, x/-y, 1/-x**2, 2/-(x), v/-2, …) ❌ all wrong ✅
32 cases the rewrite was protecting: -10**2, -x**-2, -2**0.5, 10**-2, 1e-3, 2E-2, x -= 3, f(-1), [-1, -2], range(-3, 0), plus vectors -v, -v.x**2, -mag(v)**2, -y*a, 3*-v, v - -v, -dot(v, a), a*-x/y ✅ ✅
JavaScript-mode programs (12 cases) ✅ ✅ (unchanged)
23 bundled demos no errors no errors

package/ and the GlowScriptOffline RScompiler and compiler files were rebuilt with build_original_py3.py. Rebuilding master with the same toolchain reproduces its committed files byte for byte, so the diff in those files is only this change.

Not fixed here

  • % with a negative operand still follows JavaScript's remainder: 7 % -3 == 1, where Python gives −2. That's RapydScript's own behavior, not the preprocessor's.
  • 2**-1**2 gives 0.25, where Python gives 0.5. This is also inside RapydScript.

Both belong with #217, the RapydScript refresh.

🤖 Generated with Claude Code

The preprocessor turned every unary or binary minus into "-1*" (except
after e/E in a number, after "**", and before "="), so that -a**.5
would mean -(a**.5). But "-1*" binds tighter than the operator before
it, so any negative operand after / or // was evaluated wrong:

    3/-3      -> 3/-1*3      = -9   (Python: -1)
    7 // -2   -> 7//-1*2     = -14  (Python: -4)
    v/-2      -> <-2,-4,-6>         (Python: <-0.5,-1,-1.5>)

RapydScript already gives unary minus the right precedence relative
to "**" (-10**2 == -100, -x**-2 == -0.04), and the vector operator
overloading handles unary minus itself, so the rewrite is simply
removed.

Checked with a 39-case program through GlowScriptOffline (Python
semantics as the oracle): master fails the 7 division cases, this
passes all 39, including -10**2, -2**0.5, 10**-2, 1e-3, x -= 3,
-v, -v.x**2, -mag(v)**2, 3*-v and v - -v. JavaScript-mode programs
are unaffected (same results before and after), and all 23 bundled
demos run without errors.

Not fixed here: % with a negative operand still follows JavaScript's
remainder (7 % -3 == 1, Python: -2); that is RapydScript's own
behavior, tracked separately.

package/ and GlowScriptOffline RScompiler + compiler rebuilt with
build_original_py3.py (master rebuilds byte-identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QNw6KLiif2r9zSoSQug4iL
@sspickle
sspickle merged commit c14f759 into master Nov 1, 2026
sspickle added a commit to vpython/rsWVPRunner that referenced this pull request Nov 2, 2026
…ipt#223)

GScompiler.js rewrote every unary minus as "-1*" before handing the
program to RapydScript. Inside a division that changes the meaning:
3/-3 became 3/-1*3 = -9, 7 // -2 became -14, and v/-2 multiplied a
vector by -2. RapydScript 0.7.22 already gets -10**2 = -100 right on
its own, so the rewrite isn't needed.

lib/compiling/GScompiler.js is copied from glowscript c14f759. It was
identical to glowscript's copy before that commit. RScompiler.3.2.min.js
and compiler.3.2.min.js are rebuilt with build_package.py.

Tested with 39 negative-number cases on GlowScriptOffline using these
package files: 7 wrong before, 0 after. All 23 bundled demos run with
no errors before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QNw6KLiif2r9zSoSQug4iL
sspickle added a commit that referenced this pull request Nov 3, 2026
RapydScript gave unary operators the highest precedence, so "-x**2"
parsed as "(-x)**2", and the output stage turned that back into
"-Math.pow(x, 2)" only when the unary had no parentheses. Everything
else came out with the wrong sign:

    -(x+1)**2  -> 36    (Python: -36)
    -(x)**2    -> 25    (Python: -25)
    - -x**2    -> -25   (Python: 25)
    exp(-(x-3)**2/8) -> 1.65  (Python: 0.61)

The "-" -> "-1*" rewrite removed in #223 hid all of this, so #223
made it visible. maybe_unary now parses Python's "primary ** factor"
itself: after an atom, "**" takes the next unary expression as its
right operand. That binds ** tighter than a unary on its left and makes
it group right to left, which also fixes 2**3**2 (was 64, now 512) and
2**-1**2 (was 0.25, now 0.5) from #224.

tests/test_compiler.py: 21 power/unary expressions against CPython; 9
failed before the fix, all pass after. Through GlowScriptOffline: the
122-row arithmetic table now differs from CPython only in the 13 known
cases (% with negatives, round halves, int(bool), e), the #170 39-case
program has 0 failures, and all 23 demos run. **kwargs in calls and
definitions, comprehensions and conditionals with ** are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QNw6KLiif2r9zSoSQug4iL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improper handling of negative signs with exponents and modulo

1 participant