feat(wam-scala): float arithmetic, more builtins, and Phase S7 fact seam#1701
Merged
feat(wam-scala): float arithmetic, more builtins, and Phase S7 fact seam#1701
Conversation
Three thematic additions in one PR. All four new smoke tests pass under
scalac+scala. Test counts: 10 generator (unchanged) + 22 e2e smoke
(was 18).
Float arithmetic — one new smoke test (`arith_float`):
- `evalArith` now returns a Num ADT (NInt | NDouble) instead of a raw
Int. Mixed Int/Double operations promote to Double; true division
`/` always returns Double (matching SWI-Prolog); integer division
is reachable via `//` and `mod` if a future emitter needs them.
- Codegen detects float literals in get_constant / put_constant /
set_constant / unify_constant via constant_to_scala_term/2 and emits
FloatTerm(N) instead of treating them as atoms.
- The CLI parser recognises floats (anything with `.` / `e` / `E` that
Java parses as a Double) and emits FloatTerm; otherwise it falls
through to the existing int / atom / list / struct logic.
- The numeric comparison family (=:=, =\\=, <, >, =<, >=) takes a
(Num, Num) => Boolean operator and uses Double-promoted comparison
whenever either operand is Double.
Bug fix uncovered by float arithmetic:
`parse_functor_arity/3` was finding the *first* "/" in the functor
token, which broke for the division operator (rendered as `//2` in
WAM text — functor `/` of arity 2). The compiled output was emitting
`Raw("put_structure //2 A2")` instead of a real PutStructure, so
`Y is X / 2.0` silently failed. Fixed to use the *last* slash as the
arity separator.
Builtin gap-fill — two new smoke tests (`builtin_fail`,
`builtin_negation`):
- `fail/0` — single-line backtrack handler.
- `\+/1` (negation-as-failure) — implemented via a meta-call helper
`metaCallSucceeds` that snapshots the relevant outer state, runs
the goal in the same WamState until completion (callStack starts
empty so Proceed marks success), then restores everything. Bindings
made during the goal are unwound on exit so \+ never propagates
partial bindings.
Phase S7 fact backend seam — one new smoke test
(`fact_source_inline_vs_sidecar`):
- New `scala_fact_sources([source(P/A, Tuples) | ...])` option, where
each Tuple is a list of Arity atoms (or numbers). The codegen
expands each source into:
* a foreign_predicates entry (so the WAM body becomes a
CallForeign stub);
* a synthesised ForeignHandler that returns ForeignMulti with one
solution per tuple (the runtime's existing applyBindings +
backtracking machinery filters tuples against the input args);
* intern_atoms entries for every atom appearing in any tuple.
- The expansion happens via expand_fact_sources_in_options/2 at the
top of write_wam_scala_project/3, so the rest of the pipeline never
needs to know about fact sources as a distinct concept. User-supplied
foreign_predicates / scala_foreign_handlers / intern_atoms are
preserved by union with the synthesised entries.
- The parity smoke test runs the same query set against:
1. wam_pair_inline/2 — WAM-compiled facts (3 ground clauses);
2. wam_pair_sidecar/2 — same tuples passed through scala_fact_sources.
Both return identical answers across positive and negative cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three thematic additions in one PR. All four new smoke tests pass under
scalac+scala.This PR closes the bottom three open items on the WAM Scala plan list
that didn't depend on external infra: float arithmetic, the
fail/\+builtin gap-fill, and the start of Phase S7 — the fact-backend seam
that lets relations be supplied as declarative tuples instead of WAM
clauses.
What's in the diff
Float arithmetic —
arith_floatsmoke testevalArithnow returns aNumADT (NInt | NDouble) instead of araw
Int. Mixed Int/Double operations promote to Double; truedivision
/always returns Double (matching SWI-Prolog); integerdivision is reachable via
//andmodif a future emitter needsthem.
get_constant/put_constant/set_constant/unify_constantviaconstant_to_scala_term/2andemits
FloatTerm(N)instead of treating them as atoms../e/EthatJava parses as a
Double) and emitsFloatTerm; otherwise it fallsthrough to the existing int / atom / list / struct logic.
=:=,=\=,<,>,=<,>=) takes a(Num, Num) => Booleanoperator and uses Double-promoted comparisonwhenever either operand is Double.
Bug fix uncovered by float arithmetic
parse_functor_arity/3was finding the first/in the functortoken, which broke for the division operator (rendered as
//2in WAMtext — functor
/of arity 2). The compiled output was emittingRaw("put_structure //2 A2")instead of a realPutStructure, soY is X / 2.0silently failed. Fixed to use the last slash as thearity separator. Other functors that use
/would have hit this too;this is a real fix, not just a float-specific patch.
Builtin gap-fill —
builtin_fail,builtin_negationsmoke testsfail/0— single-line backtrack handler.\+/1(negation-as-failure) — implemented via a meta-call helpermetaCallSucceedsthat snapshots the relevant outer state, runsthe goal in the same
WamStateuntil completion (callStackstartsempty so
Proceedmarks success), then restores everything.Bindings made during the goal are unwound on exit so
\+neverpropagates partial bindings to the outer scope.
Phase S7 fact backend seam —
fact_source_inline_vs_sidecartestscala_fact_sources([source(P/A, Tuples), ...])whereeach
Tupleis a list ofArityatoms (or numbers). The codegenexpands each source into:
foreign_predicatesentry (so the WAM body becomes aCallForeignstub);ForeignHandlerthat returnsForeignMultiwithone solution per tuple — the runtime's existing
applyBindingsintern_atomsentries for every atom appearing in any tuple.expand_fact_sources_in_options/2at the topof
write_wam_scala_project/3, so the rest of the pipeline neverneeds to know about fact sources as a distinct concept. User-supplied
foreign_predicates/scala_foreign_handlers/intern_atomsarepreserved by union with the synthesised entries.
wam_pair_inline/2— WAM-compiled facts (3 ground clauses);wam_pair_sidecar/2— same tuples passed throughscala_fact_sources.Both return identical answers across positive and negative cases.
Test plan
swipl -g 'use_module(library(plunit)),consult("tests/test_wam_scala_generator.pl"),run_tests,halt' -t 'halt(1)'→ 10/10 pass.scalacon PATH orSCALA_SMOKE_TESTS=1:swipl -g 'use_module(library(plunit)),consult("tests/test_wam_scala_runtime_smoke.pl"),run_tests,halt' -t 'halt(1)'→ 22/22 pass.scalac: smoke unit reports "No tests to run" (gated byscala_available/0).Out of scope
source(...)form is inline tuples only. File / LMDB backends slot into the same
scala_fact_sourcesoption in S8.call/1,call/2+,findall/3,bagof/3— the meta-callscaffolding from
\+/1makescall/1straightforward to add laterbut it isn't included here.
+,-,*,/,mod, andunary
+/-are supported inevalArith.🤖 Generated with Claude Code