asc-community / asc-community/AngouriMath

`a != b` silently parses as `a! = b`, the factorial of a equated to b

Open
#1,225 1 comment 0 reactions 0 assignees View on GitHub
Bug
Dominant language
C#
Stars
831
Forks
79
Avg merge
3h 23m
Merged PRs (30d)
309

Description

`a != b` is accepted and parses as `a! = b` — the factorial of `a`, equated to `b`.

```
"a != b".ToEntity() → a! = b [Equalsf]
"a! = b".ToEntity() → a! = b [Equalsf] the identical tree
```

`!=` is not a token, so the lexer takes `!` as the postfix factorial and `=` as equality, and the result is well-formed. There is no diagnostic, because nothing is malformed — it is simply not what was written.

**Why this is worth refusing rather than shrugging at.** `!=` is how inequality is spelled in C, C#, Java, Python, JavaScript, Rust and most of what a caller of this library writes all day. The spelling here is `<>`, which is fine, but somebody who reaches for the one they know does not get an error — they get a factorial equation, and every downstream answer is a correct answer to a question they did not ask.

It is the same shape as [#733](https://github.com/asc-community/AngouriMath/issues/733), where `floor(x)` came back as the product of an undeclared variable named `floor` with `x` and `(floor(x) - 3 = 0).Solve("x")` answered `{ 3 / floor }`. That was fixed by refusing the name rather than letting the implicit-multiplication fallback swallow it, and the reasoning transfers exactly.

**What I think the fix is.** `!=` cannot be *given* its usual meaning while `!` is the postfix factorial — `a != b` is genuinely ambiguous between `not (a = b)` and `(a!) = b`, and picking either silently is how we got here. So it should be refused, with a message naming `<>`, exactly as `arcsinh` is refused with a message naming `arsinh`:

> there is no operator `!=`: inequality is written `<>`. It is refused rather than read as a factorial followed by an equality, which is what `a != b` would otherwise mean.

That keeps `a! = b` working when it is written with the space, which is the only way to say it unambiguously anyway.

**Scope.** One alternative in the lexer plus a test, and a `BREAKING-CHANGES.md` row, since input that parses today would stop parsing. Raised out of the notation survey on [#1212](https://github.com/asc-community/AngouriMath/issues/1212), but independent of the design question there and worth doing whichever way that goes.

Happy to take it if you want it — leaving it unassigned in case you would rather it be somebody's first contribution, since it is self-contained and the precedent to copy is right there in the grammar.

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

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

Contributor guide

Open the contributing guide

Research direction

Start with the lexer alternative that handles operators and the related grammar test, using the refusal precedent from #733 as a guide. Add coverage showing that `a != b` is rejected while `a! = b` still parses, and add the requested row to BREAKING-CHANGES.md. Done means the diagnostic names `<>` and the regression tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
compilers
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.