Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 17 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,20 @@ jobs:
- uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}

# Exercise the pure GAP fallback on its own, by not building the kernel
# extension at all.
test-without-kernel-extension:
name: no kernel extension
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: gap-actions/setup-gap@v3
- uses: gap-actions/run-pkg-tests@v4
env:
JSON_EXPECT_NO_KERNEL: "true"
- uses: gap-actions/process-coverage@v3
- uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
10 changes: 0 additions & 10 deletions PackageInfo.g
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,6 @@ Dependencies := rec(
ExternalConditions := [ ],
),

AvailabilityTest := function()
if IsKernelExtensionAvailable("json") = false then
LogPackageLoadingMessage( PACKAGE_WARNING,
[ "kernel functions for json are not available." ] );
return false;
else
return true;
fi;
end,

TestFile := "tst/testall.g",

#Keywords := [ "TODO" ],
Expand Down
15 changes: 15 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,30 @@ This package uses the following mapping between GAP and JSON.
* JSON strings are mapped to GAP strings
* Integers are mapped to GAP integers, non-integer numbers are mapped to Floats
* true, false and null are mapped to true, false and fail respectively
* Non-finite GAP floats use the Python-compatible non-standard spellings NaN, Infinity and -Infinity.
* The historical lowercase spellings nan, inf and -inf are accepted on input.

Note that this library will not map any other GAP types, such as groups,
permutations, to or from JSON. If you wish to map between more complex
types, look at the openmath package, or IO_Pickle in the IO package.

Parsing and string escaping have equivalent C and pure GAP implementations.
The C backend is used when the kernel extension has been compiled; otherwise
the GAP backend takes over automatically. The existing GAP serialisation
methods are shared by both paths, with the kernel accelerating common objects.


Updates
=======

unreleased
==========

- Make the kernel extension optional. Without it, the package falls back to a
JSON parser and string escaper written in GAP.

- Add the JSONTestSuite parsing corpus.

v2.5.0
======

Expand Down
21 changes: 21 additions & 0 deletions gap/impl.gi
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#############################################################################
##
#W impl.gi json Package
##
## Select the kernel implementation when available, otherwise the pure GAP
## fallback. The GAP output backend reuses _GapToJsonStreamInternal.
##

_JSON_BACKEND.StringToGap := _JSON_PureStringToGap;
_JSON_BACKEND.StreamToGap := _JSON_PureStreamToGap;
_JSON_BACKEND.ObjToString := _JSON_ObjToString;
_JSON_BACKEND.EscapeString := _JSON_PureEscapeString;
_JSON_BACKEND.ListToString := fail;

if _JSON_KERNEL_AVAILABLE then
_JSON_BACKEND.StringToGap := ValueGlobal("JSON_STRING_TO_GAP");
_JSON_BACKEND.StreamToGap := ValueGlobal("JSON_STREAM_TO_GAP");
_JSON_BACKEND.ObjToString := ValueGlobal("GAP_OBJ_TO_JSON_STRING");
_JSON_BACKEND.EscapeString := ValueGlobal("JSON_ESCAPE_STRING");
_JSON_BACKEND.ListToString := ValueGlobal("GAP_LIST_TO_JSON_STRING");
fi;
9 changes: 8 additions & 1 deletion gap/json.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#! * JSON strings are mapped to GAP strings
#! * Integers are mapped to GAP integers, non-integer numbers are mapped to Floats
#! * true, false and null are mapped to true, false and fail respectively
#! * Non-finite GAP floats use the Python-compatible non-standard spellings NaN, Infinity and -Infinity.
#! * The historical lowercase spellings nan, inf and -inf are accepted on input.
#!
#! Note that this library is *NOT* intended to provide a general purpose library
#! for transmitting any GAP object. If you wish to do this, look at
Expand Down Expand Up @@ -39,4 +41,9 @@ DeclareGlobalFunction("JsonStringToGap");
#! @Returns value
#! @Description
#! Reads a single JSON object from a <A>stream</A> and converts it to a GAP value.
DeclareGlobalFunction("JsonStreamToGap");
DeclareGlobalFunction("JsonStreamToGap");


# The selected backend. gap/impl.gi fills this record after all implementation
# functions have been read.
_JSON_BACKEND := rec();
40 changes: 33 additions & 7 deletions gap/json.gi
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,30 @@ end );

InstallMethod(_GapToJsonStreamInternal, [IsOutputStream, IsFloat],
function(o, d)
WriteAll(o, String(d));
local dot, s;
if not IsRealFloat(d) then
ErrorNoReturn("cannot encode non-real float as JSON");
elif IsNaN(d) then
WriteAll(o, "NaN");
return;
elif not IsFinite(d) then
if d > 0.0 then
WriteAll(o, "Infinity");
else
WriteAll(o, "-Infinity");
fi;
return;
fi;

s := String(d);
dot := Position(s, '.');
if dot = Length(s) then
s := Concatenation(s, "0");
elif dot <> fail and s[dot + 1] in "eE" then
s := Concatenation(s{[1..dot]}, "0", s{[dot + 1..Length(s)]});
fi;

WriteAll(o, s);
end );

InstallMethod(_GapToJsonStreamInternal, [IsOutputStream, IsBool],
Expand All @@ -71,16 +94,19 @@ function(o, s)
fi;
else
WriteAll(o, "\"");
WriteAll(o, JSON_ESCAPE_STRING(s));
WriteAll(o, _JSON_BACKEND.EscapeString(s));
WriteAll(o, "\"");
fi;
end );

InstallMethod(_GapToJsonStreamInternal, [IsOutputStream, IsList],
function(o, l)
local i, first;
if IsOutputTextStringRep(o) and IsStringRep(o![1]) then
GAP_LIST_TO_JSON_STRING(o![1], o, l);
# the kernel fast path is a pure optimisation producing byte-identical
# output, so the pure GAP implementation just falls through to the loop
if _JSON_BACKEND.ListToString <> fail
and IsOutputTextStringRep(o) and IsStringRep(o![1]) then
_JSON_BACKEND.ListToString(o![1], o, l);
else
first := true;
WriteAll(o, "[");
Expand Down Expand Up @@ -126,15 +152,15 @@ end );

InstallGlobalFunction(GapToJsonString,
function(obj)
return GAP_OBJ_TO_JSON_STRING(obj);
return _JSON_BACKEND.ObjToString(obj);
end );

InstallGlobalFunction(JsonStringToGap,
function(str)
return JSON_STRING_TO_GAP(str);
return _JSON_BACKEND.StringToGap(str);
end );

InstallGlobalFunction(JsonStreamToGap,
function(str)
return JSON_STREAM_TO_GAP(str);
return _JSON_BACKEND.StreamToGap(str);
end );
Loading
Loading