Skip to content

Pure GAP fallback for JSON parser - #39

Draft
fingolfin wants to merge 2 commits into
masterfrom
pure-gap-fallback
Draft

Pure GAP fallback for JSON parser#39
fingolfin wants to merge 2 commits into
masterfrom
pure-gap-fallback

Conversation

@fingolfin

Copy link
Copy Markdown
Member

This is a pure GAP JSON parser backend, which is used if the kernel extension is not loaded.

Marked as a draft as I did not yet have a chance to review the code (which Claude Opus 5 created). Also, it would make sense to also analyze and fix the crashes in the C++ code, but I am out of tokens for the next couple hours 😂

And perhaps the test suite should be imported separately, and first... (incidentally, this resolves #4)

Part of the ArtifactManager effort.

CC @ThomasBreuer @lgoettgens

fingolfin and others added 2 commits August 18, 2026 12:59
gap/utf8.gi and gap/parse.gi transcribe the picojson parser in
src/picojson/picojson.h, gap_val::from_str in src/picojson/gap-traits.h,
and getUTF8Char/outputUnicodeChar/FuncJSON_ESCAPE_STRING in src/json.cc,
so that the two implementations accept and reject the same texts, produce
the same values, and report the same errors. That includes picojson's
relaxed reading of numbers and the C code's idiosyncratic UTF-8 decoding,
both reproduced deliberately: behaviour should not depend on whether the
kernel extension happened to compile.

Speed comes from scanning with a TranslateString'd copy of the input and
the kernel's Position, so that runs of ordinary bytes are copied in one
slice rather than a character at a time, and from reading streams in
chunks and seeking back afterwards.

Two differences from the C code are on purpose. Input nested deeper than
_JSON_MAX_DEPTH is rejected with an ordinary error instead of overflowing
the C stack and taking GAP with it. And reading past the end of a string
yields 0 rather than whatever follows the buffer, so escaping a string
ending in a truncated UTF-8 sequence no longer crashes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tst/testall.g now runs the whole test directory once per available
implementation, so the GAP code is exercised even where the kernel
extension is present. All the existing tests pass unchanged under both,
including the ones asserting on exact error messages.

tst/JSONTestSuite holds the test_parsing corpus of
https://github.com/nst/JSONTestSuite (MIT). Of its 318 files, two nest
100000 levels deep and crash GAP through the kernel parser, so they are
left out; the other 316 are checked against the expectations in
tst/jsontestsuite.g, which also record the eleven number formats we
accept although RFC 8259 does not. Since testall.g runs the file under
each implementation against the same expectations, and the file also
compares the implementations against each other directly, agreement
between them is checked rather than assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.60589% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.59%. Comparing base (68c1ba7) to head (bd5c9a0).

Files with missing lines Patch % Lines
gap/parse.gi 96.85% 12 Missing ⚠️
gap/utf8.gi 99.05% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #39      +/-   ##
==========================================
+ Coverage   96.42%   97.59%   +1.16%     
==========================================
  Files           6       10       +4     
  Lines         140      791     +651     
==========================================
+ Hits          135      772     +637     
- Misses          5       19      +14     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fingolfin

Copy link
Copy Markdown
Member Author

Of course this also interacts with PR #36 -- depending on which is merged first, we'll have to rebase.

I also want to add some benchmark numbers to give a better feeling how much slower this is. But for many of my use cases, the difference is irrelevant, and having a slow-yet-good-enough JSON parser beat not having a fast JSON parser (because it wasn't compiled by the user, with the user having no idea how to even do that)

@ThomasBreuer

Copy link
Copy Markdown

If I understand this proposal right then the ideas are

  • to make a JSON parser available also if the underlying picojson has not been compiled
  • and to promise that the behaviour of the GAP parser and of picojson are equal.

Concerning the latter statement, some comments in the new code give the impression that picojson has some bugs and limitations (?), and that the new GAP code on purpose "emulates" these problems.
What will happen if somebody fixes picojson, do we then have to keep the buggy version because otherwise also the GAP parser has to be changed?

From a technical point of view, once the JSON package need not be compiled anymore I will turn it into a needed package of AtlasRep and CTblLib; then I can remove my JSON parser (which is in fact older than the JSON package) from AtlasRep. My idea for AtlasRep and CTblLib had been that I did not want a dependency on a package that needs compilation.

@fingolfin

Copy link
Copy Markdown
Member Author

@ThomasBreuer thanks for the comments. Regarding the bug-by-bug compatibility: I wanted to avoid the problem that people get different behaviour depending on which parser is active, that leads to a lot of pain.

I'd be happy to look into fixing some of those, though, but then ideally in both the GAP and C++ version.

One "bug" is important for us, though: JSON does not have big integers, but our parsers both do support them -- and there is code out there relying on this. So we should keep this, and make sure we document this as an intentional feature people can rely on.

BTW I forgot to tag @ChrisJefferson of course I also want to hear from him if he thinks this PR, and also the stuff we are discussing now, are moving in a direction he is happy with.

@ChrisJefferson

Copy link
Copy Markdown
Member

From my memory the things not supported are big integers (we should truncate them to floats, that would obviously be stupid in GAP), and some very, very unusual stuff (like BOMs, which I suppose we could support).

@ThomasBreuer

Copy link
Copy Markdown

Concerning big integers, I would say that supporting arbitrary large integers is a feature not a bug.
From my point of view, a JSON parser is a parser into a system that has notions of integers, strings, arrays, and objects such that a JSON string can be mapped to some data structure in the system.
And if the system supports arbitrary large integers then the parser can interpret arbitrary long sequences of digits as integers.
This is the case for GAP. We can document this feature, and there is no problem with large integers in JSON format files that are used by GAP.

Well, there would be a problem if we promise that the JSON format files which we create for GAP can be interpreted "correctly" by any JSON parser.
I think it is enough if parsers for systems which support arbitrary long integers can deal with such files. For example, Julia's JSON.parse turns long integers in JSON strings by default into the Julia type BigInt, thus Julia based systems will not have problems with large integers.

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.

Import or otherwise use testsuite from "Parsing JSON is a Minefield 💣"

3 participants