diff --git a/docs/src/basics.md b/docs/src/basics.md index 42e7e69ce..345f7ce44 100644 --- a/docs/src/basics.md +++ b/docs/src/basics.md @@ -23,6 +23,7 @@ script. Such a script can be created in a location of your choice via [`GAP.crea ```@docs Globals evalstr +evalstr_ex GAP.prompt GAP.create_gap_sh ``` diff --git a/src/GAP.jl b/src/GAP.jl index 0693cf8d7..171ae69df 100644 --- a/src/GAP.jl +++ b/src/GAP.jl @@ -1,7 +1,9 @@ """ GAP.jl is the Julia interface to the GAP-System. - For more information about GAP see https://www.gap-system.org/ + For the package manual see https://oscar-system.github.io/GAP.jl/. + + For more information about GAP see https://www.gap-system.org/. """ module GAP # Show a more helpful error message for users on Windows. diff --git a/src/adapter.jl b/src/adapter.jl index 607c86703..ace5fd886 100644 --- a/src/adapter.jl +++ b/src/adapter.jl @@ -196,7 +196,7 @@ Return the record component of the GAP record `x` that is described by `f`. # Examples ```jldoctest -julia> r = GAP.evalstr( "rec( a:= 1 )" ) +julia> r = GapObj(Dict(:a => 1)) GAP: rec( a := 1 ) julia> r.a @@ -217,7 +217,7 @@ to the value `v`. # Examples ```jldoctest -julia> r = GAP.evalstr( "rec( a:= 1 )" ) +julia> r = GapObj(Dict(:a => 1)) GAP: rec( a := 1 ) julia> r.b = 0 @@ -241,7 +241,7 @@ and `false` otherwise. # Examples ```jldoctest -julia> r = GAP.evalstr( "rec( a:= 1 )" ) +julia> r = GapObj(Dict(:a => 1)) GAP: rec( a := 1 ) julia> hasproperty( r, :a ) @@ -529,7 +529,7 @@ julia> res1 = GAP.Globals.Random(gap_rng1, 1, 10); julia> rng1 == rng2 # the two rngs have diverged false -julia> res1 == GAP.Globals.Random(gap_rng2, GAP.GapObj(1:10)) +julia> res1 == GAP.Globals.Random(gap_rng2, GapObj(1:10)) true julia> rng1 == rng2 # now the two rngs are again in sync diff --git a/src/ccalls.jl b/src/ccalls.jl index c0179fae7..d3b79bb5e 100644 --- a/src/ccalls.jl +++ b/src/ccalls.jl @@ -40,6 +40,52 @@ function _JULIA_TO_GAP(x::Int) end +@doc raw""" + evalstr_ex(cmd::String) + +Assume that `cmd` consists of $n$ GAP statements, each terminated by `;` or `;;`. +Let GAP execute these statements and return a GAP list of length $n$ that +describes their results. +Each entry of the return value is a GAP list of length 5, +with the following meaning. + +- The first entry is `true` if the statement was executed successfully, + and `false` otherwise. +- If the first entry is `true`, then the second entry is bound to the + result of the statement if there was one, and unbound otherwise. +- The third entry is unbound if an error occured, + `true` if the statement ends in a double semicolon, + and `false` otherwise. +- The fourth entry currently is always unbound. +- The fifth entry contains the captured output of the statement as a string. + If there was no double semicolon then also the output of + `GAP.Globals.ViewObj` applied to the result value in the second entry, + if any, is part of that string. + +# Examples +```jldoctest +julia> GAP.evalstr_ex( "1+2" ) # error due to missing semicolon +GAP: [ [ false,,,, "" ] ] + +julia> GAP.evalstr_ex( "1+2;" ) # one statement with return value +GAP: [ [ true, 3, false,, "3" ] ] + +julia> GAP.evalstr_ex( "1+2;;" ) # the same with suppressed output +GAP: [ [ true, 3, true,, "" ] ] + +julia> GAP.evalstr_ex( "x:= []; Add(x, 1);" ) # two valid commands +GAP: [ [ true, [ 1 ], false,, "[ ]" ], [ true,, false,, "" ] ] + +julia> GAP.evalstr_ex( "1/0; 1+1;" ) # one error, one valid command +GAP: [ [ false,,,, "" ], [ true, 2, false,, "2" ] ] + +julia> GAP.evalstr_ex( "Print(1);" ) # no return value but output +GAP: [ [ true,, false,, "1" ] ] + +julia> GAP.evalstr_ex( "" ) # empty input +GAP: [ ] +``` +""" function evalstr_ex(cmd::String) res = ccall((:GAP_EvalString, libgap), GapObj, (Cstring,), cmd) return res @@ -61,12 +107,30 @@ julia> GAP.evalstr( "1+2" ) julia> GAP.evalstr( "x:= []" ) GAP: [ ] -julia> GAP.evalstr( "y:= 2; Add( x, 1 )" ) +julia> GAP.evalstr( "y:= 2; Add( x, y )" ) julia> GAP.evalstr( "x" ) -GAP: [ 1 ] +GAP: [ 2 ] +julia> GAP.evalstr( "Print( x )" ) ``` + +Note that screen outputs caused by evaluating `cmd` are not shown +by `evalstr`; use [`evalstr_ex`](@ref) for accessing both the outputs +and the return values of the command(s). + +Note also that using `evalstr` is often not the best way to create +GAP objects or to call GAP functions. +(In fact, it is likely that something is missing from GAP.jl +if `evalstr` is the only way to formulate some lines of code.) +Alternatively, use `GAP.GapObj` or `GAP.Obj` for constructing GAP objects +that correspond to given Julia objects, +and call GAP functions directly in the Julia session. +For example, executing `GAP.evalstr( "x:= []; Add( x, 2 )" )` +can be replaced by the Julia code `x = GAP.GapObj([]); GAP.Globals.Add(x, 2)`. +Note that the variable `x` in the former example lives in the GAP session, +i.e., it can be accessed as `GAP.Globals.x` after the call of `GAP.evalstr`, +whereas `x` in the latter example lives in the Julia session. """ function evalstr(cmd::String) res = evalstr_ex(cmd * ";") diff --git a/src/constructors.jl b/src/constructors.jl index 056a68201..b17c9496a 100644 --- a/src/constructors.jl +++ b/src/constructors.jl @@ -16,20 +16,20 @@ their conversion with `BigInt` is handled by Julia's methods.) # Examples ```jldoctest -julia> val = GAP.evalstr("2^64") -GAP: 18446744073709551616 +julia> val = GAP.Globals.Factorial(25) +GAP: 15511210043330985984000000 julia> BigInt(val) -18446744073709551616 +15511210043330985984000000 -julia> val = GAP.evalstr("2^59") -576460752303423488 +julia> val = GAP.Globals.Factorial(10) +3628800 julia> isa(val, GapObj) false julia> BigInt(val) -576460752303423488 +3628800 ``` """ @@ -68,12 +68,14 @@ their conversion is not handled by methods installed in GAP.jl.) # Examples ```jldoctest -julia> val = GAP.evalstr("2^80") -GAP: 1208925819614629174706176 +julia> val = GAP.Globals.Factorial(25) +GAP: 15511210043330985984000000 julia> Int128(val) -1208925819614629174706176 +15511210043330985984000000 +julia> Int(val) +ERROR: InexactError: Int64(15511210043330985984000000) ``` """ Int128 @@ -86,16 +88,16 @@ the [GAP rational](GAP_ref(ref:Rationals)) `obj`, # Examples ```jldoctest -julia> val = GAP.evalstr("2^64") -GAP: 18446744073709551616 +julia> val = GAP.Globals.Factorial(25) +GAP: 15511210043330985984000000 julia> Rational{Int128}(val) -18446744073709551616//1 +15511210043330985984000000//1 julia> Rational{BigInt}(val) -18446744073709551616//1 +15511210043330985984000000//1 -julia> val = GAP.evalstr("1/3") +julia> val = GAP.Obj(1//3) GAP: 1/3 julia> Rational{Int64}(val) @@ -118,7 +120,7 @@ Return the float converted from the [GAP float](GAP_ref(ref:Floats)) `obj`. # Examples ```jldoctest -julia> val = GAP.evalstr("2.2") +julia> val = GAP.Obj(2.2) GAP: 2.2 julia> Float64(val) @@ -143,7 +145,7 @@ Return the character converted from the # Examples ```jldoctest -julia> val = GAP.evalstr("'x'") +julia> val = GAP.Obj('x') GAP: 'x' julia> Char(val) @@ -164,7 +166,7 @@ Return the `UInt8` that belongs to the # Examples ```jldoctest -julia> val = GAP.evalstr("'x'") +julia> val = GAP.Obj('x') GAP: 'x' julia> Cuchar(val) @@ -188,13 +190,13 @@ this behaviour is not intended for this `String` constructor. # Examples ```jldoctest -julia> val = GAP.evalstr("\\"abc\\"") +julia> val = GAP.Obj("abc") GAP: "abc" julia> String(val) "abc" -julia> val = GAP.evalstr("[]") +julia> val = GAP.Obj([]) GAP: [ ] julia> String(val) # an empty GAP list is a string @@ -217,7 +219,7 @@ Return the symbol converted from the # Examples ```jldoctest -julia> str = GAP.evalstr("\\"abc\\"") +julia> str = GAP.Obj("abc") GAP: "abc" julia> Symbol(str) @@ -235,7 +237,7 @@ Return the bit vector converted from the # Examples ```jldoctest -julia> val = GAP.evalstr("[ true, false, true ]") +julia> val = GAP.Obj([true, false, true]) GAP: [ true, false, true ] julia> BitVector(val) @@ -273,7 +275,7 @@ If `T` is `UInt8` then `obj` may be a # Examples ```jldoctest -julia> val = GAP.evalstr("[ [ 1 ], [ 2 ] ]") +julia> val = GAP.Obj([[1], [2]]; recursive=true) GAP: [ [ 1 ], [ 2 ] ] julia> Vector{Any}(val) @@ -281,21 +283,26 @@ julia> Vector{Any}(val) Any[1] Any[2] -julia> Vector{Any}(val, recursive = false) +julia> Vector{Any}(val; recursive=false) 2-element Vector{Any}: GAP: [ 1 ] GAP: [ 2 ] +julia> Vector{Vector{Int64}}(val) +2-element Vector{Vector{Int64}}: + [1] + [2] + julia> val = GAP.evalstr( "NewVector( IsPlistVectorRep, Integers, [ 0, 2, 5 ] )" ) GAP: -julia> Vector{Int64}( val ) +julia> Vector{Int64}(val) 3-element Vector{Int64}: 0 2 5 -julia> val = GAP.evalstr("\\"abc\\"") +julia> val = GAP.Obj("abc") GAP: "abc" julia> Vector{UInt8}(val) @@ -322,7 +329,7 @@ converted recursively, otherwise non-recursively. # Examples ```jldoctest -julia> val = GAP.evalstr("[ [ 1, 2 ], [ 3, 4 ] ]") +julia> val = GAP.Obj([[1, 2], [3, 4]]; recursive=true) GAP: [ [ 1, 2 ], [ 3, 4 ] ] julia> Matrix{Int64}(val) @@ -360,26 +367,25 @@ Dealing with results containing GAP objects will be inefficient. # Examples ```julia -julia> Set{Int}(GAP.evalstr("[ 1, 2, 1 ]")) +julia> Set{Int}(GAP.Obj([1, 2, 1])) Set{Int64} with 2 elements: 2 1 -julia> Set{Vector{Int}}(GAP.evalstr("[[1], [2], [1]]")) +julia> Set{Vector{Int}}(GAP.Obj([[1], [2], [1]]; recursive=true)) Set{Vector{Int64}} with 2 elements: [1] [2] -julia> Set{String}(GAP.evalstr("[\\"a\\", \\"b\\"]")) +julia> Set{String}(GAP.Obj(["a", "b"]; recursive=true)) Set{String} with 2 elements: "b" "a" -julia> Set{Any}(GAP.evalstr("[[1], [2], [1]]")) +julia> Set{Any}(GAP.Obj([[1], [2], [1]]; recursive=true)) Set{Any} with 2 elements: Any[1] Any[2] - ``` """ Base.Set{T}(obj::GapObj; recursive::Bool = true) where {T} = @@ -397,19 +403,19 @@ converted recursively, otherwise non-recursively. # Examples ```jldoctest -julia> val = GAP.evalstr("[ 1, 5 ]") +julia> val = GAP.Obj([1, 5]) GAP: [ 1, 5 ] julia> Tuple{Int64,Int64}(val) (1, 5) -julia> val = GAP.evalstr("[ [ 1 ], [ 2 ] ]") +julia> val = GAP.Obj([[1], [2]]; recursive=true) GAP: [ [ 1 ], [ 2 ] ] julia> Tuple{Any,Any}(val) (Any[1], Any[2]) -julia> Tuple{GapObj,GapObj}(val, recursive = false) +julia> Tuple{GapObj,GapObj}(val; recursive=false) (GAP: [ 1 ], GAP: [ 2 ]) ``` @@ -426,7 +432,7 @@ Return the unit range converted from the # Examples ```jldoctest -julia> val = GAP.evalstr("[ 1 .. 10 ]") +julia> val = GAP.Obj(1:10) GAP: [ 1 .. 10 ] julia> UnitRange(val) @@ -464,7 +470,7 @@ Return the step range converted from the # Examples ```jldoctest -julia> val = GAP.evalstr("[ 1, 3 .. 11 ]") +julia> val = GAP.Obj(1:2:11) GAP: [ 1, 3 .. 11 ] julia> StepRange(val) @@ -514,7 +520,7 @@ using [`gap_to_julia`](@ref), otherwise they are kept as they are. # Examples ```jldoctest -julia> val = GAP.evalstr("rec( a:= 1, b:= 2 )") +julia> val = GAP.Obj(Dict(:a => 1, :b => 2)) GAP: rec( a := 1, b := 2 ) julia> Dict{Symbol,Int}(val) @@ -522,18 +528,18 @@ Dict{Symbol, Int64} with 2 entries: :a => 1 :b => 2 -julia> val = GAP.evalstr("rec( l:= [ 1, 2 ] )") +julia> val = GAP.Obj(Dict(:l => GAP.Obj([1, 2]))) GAP: rec( l := [ 1, 2 ] ) -julia> Dict{Symbol,Any}(val, recursive = false) +julia> Dict{Symbol,Any}(val; recursive=false) Dict{Symbol, Any} with 1 entry: :l => GAP: [ 1, 2 ] -julia> Dict{Symbol,Any}(val, recursive = true) +julia> Dict{Symbol,Any}(val; recursive=true) Dict{Symbol, Any} with 1 entry: :l => Any[1, 2] -julia> Dict{Symbol,Vector{Int}}(val, recursive = true) +julia> Dict{Symbol,Vector{Int}}(val; recursive=true) Dict{Symbol, Vector{Int64}} with 1 entry: :l => [1, 2] diff --git a/src/gap_to_julia.jl b/src/gap_to_julia.jl index ddd3a905e..d3eab21f4 100644 --- a/src/gap_to_julia.jl +++ b/src/gap_to_julia.jl @@ -20,7 +20,7 @@ const RecDict = IdDict{Any,Any} gap_to_julia(type, x, recursion_dict::Union{Nothing,RecDict}=nothing; recursive::Bool=true) Try to convert the object `x` to a Julia object of type `type`. -If `x` is a `GAP.GapObj` then the conversion rules are defined in the +If `x` is a `GapObj` then the conversion rules are defined in the manual of the GAP package JuliaInterface. If `x` is another `GAP.Obj` (for example a `Int64`) then the result is defined in Julia by `type`. @@ -34,10 +34,10 @@ the behaviour is controlled by `recursive`, which can be `true` or `false`. # Examples ```jldoctest -julia> GAP.gap_to_julia( GAP.evalstr( "1/3" ) ) +julia> GAP.gap_to_julia(GapObj(1//3)) 1//3 -julia> GAP.gap_to_julia( GAP.evalstr( "\\"abc\\"" ) ) +julia> GAP.gap_to_julia(GapObj("abc")) "abc" julia> val = GapObj([ 1 2 ; 3 4 ]) @@ -122,7 +122,7 @@ gap_to_julia(::Type{Any}, x::Any) = x gap_to_julia(::T, x::Nothing) where {T<:Type} = nothing gap_to_julia(::Type{Any}, x::Nothing) = nothing -## Handle "conversion" to GAP.Obj and GAP.GapObj (may occur in recursions). +## Handle "conversion" to GAP.Obj and GapObj (may occur in recursions). gap_to_julia(::Type{Obj}, x::Obj) = x gap_to_julia(::Type{GapObj}, x::GapObj) = x diff --git a/src/macros.jl b/src/macros.jl index b24bd0b12..59174a100 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -59,7 +59,7 @@ GAP: (1,2)(3,4) ``` """ macro gap(str) - return evalstr(string(str)) + return :(evalstr($(string(str)))) end export @gap @@ -101,7 +101,7 @@ not all strings representing valid GAP strings can be processed. ```jldoctest julia> g"\\\\" -ERROR: LoadError: Error thrown by GAP: Syntax error: String must end with " before end of file in stream:1 +ERROR: Error thrown by GAP: Syntax error: String must end with " before end of file in stream:1 [...] ``` @@ -116,7 +116,7 @@ GAP: "\\c" ``` """ macro g_str(str) - return gap_string_macro_helper(str) + return :(gap_string_macro_helper($str)) end export @g_str @@ -216,7 +216,7 @@ in whose scope the macro is called. # Examples ```jldoctest -julia> @gapattribute isstrictlysortedlist(obj::GAP.GapObj) = GAP.Globals.IsSSortedList(obj)::Bool; +julia> @gapattribute isstrictlysortedlist(obj::GapObj) = GAP.Globals.IsSSortedList(obj)::Bool; julia> l = GapObj([ 1, 3, 7 ]); diff --git a/src/packages.jl b/src/packages.jl index bc39a350b..5f23f8830 100644 --- a/src/packages.jl +++ b/src/packages.jl @@ -2,7 +2,7 @@ module Packages using Downloads -import ...GAP: Globals, GapObj, sysinfo +import ...GAP: Globals, GapObj, RNamObj, sysinfo, Wrappers const DEFAULT_PKGDIR = Ref{String}() @@ -26,7 +26,7 @@ function init_packagemanager() return GapObj(Dict{Symbol, Any}(:success => false), recursive=true) end end - Globals.MakeReadOnlyGlobal(GapObj("PKGMAN_DownloadURL")) + Wrappers.MakeReadOnlyGlobal(GapObj("PKGMAN_DownloadURL")) # Install a method (based on Julia's Downloads package) as the first choice # for the `Download` function from GAP's utils package, @@ -57,14 +57,17 @@ function init_packagemanager() # put the new method in the first position meths = Globals.Download_Methods - Globals.Add(meths, GapObj(r, recursive=true), 1) + Wrappers.Add(meths, GapObj(r, recursive=true), 1) end end """ load(spec::String, version::String = ""; install::Bool = false, quiet::Bool = true) -Try to load the GAP package with name `spec`. +Try to load the GAP package given by `spec`, which can be either the name +of the package or a local path where the package is installed +(a directory that contains the package's `PackageInfo.g` file). + If `version` is specified then try to load a version of the package that is compatible with `version`, in the sense of [GAP's CompareVersionNumbers function](GAP_ref(ref:CompareVersionNumbers)), @@ -72,8 +75,8 @@ otherwise try to load the newest installed version. Return `true` if this is successful, and `false` otherwise. If `install` is set to `true` and (the desired version of) the required -GAP package is not yet installed then [`install`](@ref) is called first, -in order to install the package; +GAP package is not yet installed and `spec` is the package name +then [`install`](@ref) is called first, in order to install the package; if no version is prescribed then the newest released version of the package will be installed. @@ -82,27 +85,98 @@ If `quiet` is set to `false` then package banners are shown for all packages being loaded. It is also passed on to [`install`](@ref). """ function load(spec::String, version::String = ""; install::Bool = false, quiet::Bool = true) - # Try to load the package. + # Decide whether `spec` is a path to a directory that contains + # a `PackageInfo.g` file. + package_info = joinpath(spec, "PackageInfo.g") + spec_is_path = isdir(spec) && isfile(package_info) + + # If `spec` contains a slash then it is not a package name. + '/' in spec && ! spec_is_path && return false + + # The interpretation of `spec` as a package name has precedence + # over the interpretation as a path. + # Try to load the package, assuming that `spec` is its name. gspec = GapObj(spec) gversion = GapObj(version) - loaded = Globals.LoadPackage(gspec, gversion, !quiet) - if loaded == true - return true - elseif Globals.IsPackageLoaded(gspec) - # Another version is already loaded. - # Perhaps we could install the required version, - # but then we would not be able to load it into the current session - # and thus in any case `false` must be returned. - # It would be a strange side effect if the required version - # would afterwards be loadable in a fresh Julia session, - # thus we do not try to install the package here. - return false - elseif install == true + if spec_is_path + # If there is no package `gspec` and if the info level of + # `GAP.Globals.InfoWarning` is at least 1 then GAP prints a warning. + # Avoid this warning. + warning_level_orig = Wrappers.InfoLevel(Globals.InfoWarning) + Wrappers.SetInfoLevel(Globals.InfoWarning, 0) + end + loaded = Wrappers.LoadPackage(gspec, gversion, !quiet) + if spec_is_path + Wrappers.SetInfoLevel(Globals.InfoWarning, warning_level_orig) + end + + loaded == true && return true + + if Wrappers.IsPackageLoaded(gspec) + # Another version is already loaded. + # Perhaps we could install the required version, + # but then we would not be able to load it into the current session + # and thus in any case `false` must be returned. + # It would be a strange side effect if the required version + # would afterwards be loadable in a fresh Julia session, + # thus we do not try to install the package here. + return false + end + + if spec_is_path + # Assume that the package is installed in the given path. + # In order to call `GAP.Globals.SetPackagePath`, + # we have to determine the package name. + # (`Wrappers.SetPackagePath` does the same, + # but it needs the package name as an argument.) + gap_info = Globals.GAPInfo::GapObj + Wrappers.UNB_REC(gap_info, RNamObj("PackageInfoCurrent")) + Wrappers.Read(GapObj(package_info)) + record = gap_info.PackageInfoCurrent + Wrappers.UNB_REC(gap_info, RNamObj("PackageInfoCurrent")) + pkgname = Wrappers.NormalizedWhitespace(Wrappers.LowercaseString( + record.PackageName)) + rnam_pkgname = Wrappers.RNamObj(pkgname) + + # If the package with name `pkgname` is already loaded then check + # whether the installation path is equal to `spec`. + # (Note that `Wrappers.SetPackagePath` throws an error if a different + # version of the package is already loaded.) + if Wrappers.IsPackageLoaded(pkgname) && + Wrappers.ISB_REC(gap_info.PackagesLoaded, rnam_pkgname) + install_path = Wrappers.ELM_REC(gap_info.PackagesLoaded, rnam_pkgname)[1] + return joinpath(string(install_path), "PackageInfo.g") == package_info + end +#TODO: What shall happen when `spec` is a symbolic link that points to +# the installation path of the loaded package? + + # First save the available records for the package in question, ... + old_records = nothing + if Wrappers.ISB_REC(gap_info.PackagesInfo, rnam_pkgname) + old_records = Wrappers.ELM_REC(gap_info.PackagesInfo, rnam_pkgname) + end + + # ... then try to load the package, ... + Wrappers.SetPackagePath(pkgname, gspec) + loaded = Wrappers.LoadPackage(pkgname, gversion, !quiet) + + # ..., and reinstall the old info records + # (which were removed by `Wrappers.SetPackagePath`). + if old_records isa GapObj + Wrappers.Append(Wrappers.ELM_REC(gap_info.PackagesInfo, rnam_pkgname), + old_records) + end + + loaded == true && return true + Wrappers.IsPackageLoaded(gspec) && return false + end + + if install == true # Try to install the given version of the package, # without showing messages. if Packages.install(spec, version; interactive = false, quiet) # Make sure that the installed version is admissible. - return Globals.LoadPackage(gspec, gversion, !quiet) == true + return Wrappers.LoadPackage(gspec, gversion, !quiet) == true end end @@ -147,16 +221,16 @@ function install(spec::String, version::String = ""; mkpath(pkgdir) if quiet - oldlevel = Globals.InfoLevel(Globals.InfoPackageManager) - Globals.SetInfoLevel(Globals.InfoPackageManager, 0) + oldlevel = Wrappers.InfoLevel(Globals.InfoPackageManager) + Wrappers.SetInfoLevel(Globals.InfoPackageManager, 0) end if version == "" - res = Globals.InstallPackage(GapObj(spec), interactive) + res = Wrappers.InstallPackage(GapObj(spec), interactive) else - res = Globals.InstallPackage(GapObj(spec), GapObj(version), interactive) + res = Wrappers.InstallPackage(GapObj(spec), GapObj(version), interactive) end if quiet - Globals.SetInfoLevel(Globals.InfoPackageManager, oldlevel) + Wrappers.SetInfoLevel(Globals.InfoPackageManager, oldlevel) end return res end @@ -187,13 +261,13 @@ function update(spec::String; interactive::Bool = true, quiet::Bool = false, mkpath(pkgdir) if quiet - oldlevel = Globals.InfoLevel(Globals.InfoPackageManager) - Globals.SetInfoLevel(Globals.InfoPackageManager, 0) - res = Globals.UpdatePackage(GapObj(spec), interactive) - Globals.SetInfoLevel(Globals.InfoPackageManager, oldlevel) + oldlevel = Wrappers.InfoLevel(Globals.InfoPackageManager) + Wrappers.SetInfoLevel(Globals.InfoPackageManager, 0) + res = Wrappers.UpdatePackage(GapObj(spec), interactive) + Wrappers.SetInfoLevel(Globals.InfoPackageManager, oldlevel) return res else - return Globals.UpdatePackage(GapObj(spec), interactive) + return Wrappers.UpdatePackage(GapObj(spec), interactive) end end # note that the updated version cannot be used in the current GAP session, @@ -222,13 +296,13 @@ function remove(spec::String; interactive::Bool = true, quiet::Bool = false, mkpath(pkgdir) if quiet - oldlevel = Globals.InfoLevel(Globals.InfoPackageManager) - Globals.SetInfoLevel(Globals.InfoPackageManager, 0) - res = Globals.RemovePackage(GapObj(spec), interactive) - Globals.SetInfoLevel(Globals.InfoPackageManager, oldlevel) + oldlevel = Wrappers.InfoLevel(Globals.InfoPackageManager) + Wrappers.SetInfoLevel(Globals.InfoPackageManager, 0) + res = Wrappers.RemovePackage(GapObj(spec), interactive) + Wrappers.SetInfoLevel(Globals.InfoPackageManager, oldlevel) return res else - return Globals.RemovePackage(GapObj(spec), interactive) + return Wrappers.RemovePackage(GapObj(spec), interactive) end end diff --git a/src/types.jl b/src/types.jl index 169fef50b..0213aa228 100644 --- a/src/types.jl +++ b/src/types.jl @@ -34,7 +34,6 @@ GAP: Z(3) julia> typeof(x) FFE - ``` """ primitive type FFE 64 end @@ -48,10 +47,10 @@ This is the Julia type of all those GAP objects that are not # Examples ```jldoctest -julia> typeof( GAP.evalstr( "[ 1, 2 ]" ) ) # a GAP list +julia> typeof(GapObj([1, 2])) # a GAP list GapObj -julia> typeof( GAP.evalstr( "rec()" ) ) # a GAP record +julia> typeof(GapObj(Dict(:a => 1))) # a GAP record GapObj julia> typeof( GAP.evalstr( "(1,2,3)" ) ) # a GAP permutation @@ -68,7 +67,6 @@ FFE julia> typeof( GAP.evalstr( "true" ) ) # a boolean Bool - ``` Note that this is Julia's viewpoint on GAP objects. @@ -83,7 +81,6 @@ Base julia> typeof( GAP.evalstr( "Julia.Base" ) ) # native Julia object Module - ``` One can use `GapObj` as a constructor, @@ -105,15 +102,14 @@ GAP: 1/3 julia> GapObj([1 2; 3 4]) GAP: [ [ 1, 2 ], [ 3, 4 ] ] -julia> GAP.GapObj([[1, 2], [3, 4]]) +julia> GapObj([[1, 2], [3, 4]]) GAP: [ , ] -julia> GAP.GapObj([[1, 2], [3, 4]], recursive = true) +julia> GapObj([[1, 2], [3, 4]], recursive=true) GAP: [ [ 1, 2 ], [ 3, 4 ] ] julia> GapObj(42) ERROR: TypeError: in typeassert, expected GapObj, got a value of type Int64 - ``` """ GapObj @@ -142,7 +138,7 @@ GAP: [ [ 1, 2 ], [ 3, 4 ] ] julia> GAP.Obj([[1, 2], [3, 4]]) GAP: [ , ] -julia> GAP.Obj([[1, 2], [3, 4]], recursive = true) +julia> GAP.Obj([[1, 2], [3, 4]], recursive=true) GAP: [ [ 1, 2 ], [ 3, 4 ] ] julia> GAP.Obj(42) @@ -155,7 +151,7 @@ const Obj = Union{GapObj,FFE,Int64,Bool} """ GapInt -Any GAP integer object is represened in Julia as either a `GapObj` (if it +Any GAP integer object is represented in Julia as either a `GapObj` (if it is a "large" integer) or as an `Int` (if it is a "small" integer). This type union can be used to express this conveniently, e.g. when one wants to help type stability. diff --git a/src/wrappers.jl b/src/wrappers.jl index 21d44cc64..62760bd41 100644 --- a/src/wrappers.jl +++ b/src/wrappers.jl @@ -3,7 +3,9 @@ module Wrappers using GAP import GAP: @wrap +@wrap Add(x::GapObj, y::GapObj, z::Int)::Nothing @wrap AdditiveInverseSameMutability(x::Any)::Any +@wrap Append(x::GapObj, y::GapObj)::Nothing @wrap ASS_LIST(x::Any, i::Int, v::Any)::Nothing @wrap ASS_MAT(x::Any, i::Int, j::Int, v::Any)::Nothing @wrap ASS_REC(x::Any, y::Int, v::Any)::Nothing @@ -20,7 +22,10 @@ import GAP: @wrap @wrap ELMS_LIST(x::Any, y::Any)::Any @wrap EQ(x::Any, y::Any)::Bool @wrap IN(x::Any, y::Any)::Bool +@wrap InfoLevel(x::GapObj)::Int @wrap INT_CHAR(x::Any)::Int +@wrap InstallPackage(x::GapObj, y::Bool)::Bool +@wrap InstallPackage(x::GapObj, y::GapObj, z::Bool)::Bool @wrap InverseSameMutability(x::Any)::Any @wrap IS_JULIA_FUNC(x::Any)::Bool @wrap ISB_LIST(x::Any, i::Int)::Bool @@ -33,6 +38,7 @@ import GAP: @wrap @wrap IsEmpty(x::Any)::Bool @wrap IsList(x::Any)::Bool @wrap IsMatrixObj(x::Any)::Bool +@wrap IsPackageLoaded(x::GapObj)::Bool @wrap IsRange(x::Any)::Bool @wrap IsRangeRep(x::Any)::Bool @wrap IsRecord(x::Any)::Bool @@ -42,10 +48,14 @@ import GAP: @wrap @wrap IsVectorObj(x::Any)::Bool @wrap Iterator(x::Any)::Any @wrap Length(x::Any)::GapInt +@wrap LoadPackage(x::GapObj, y::GapObj, z::Bool)::Any +@wrap LowercaseString(x::GapObj)::GapObj @wrap LQUO(x::Any, y::Any)::Any @wrap LT(x::Any, y::Any)::Bool +@wrap MakeReadOnlyGlobal(x::Any)::Nothing @wrap MOD(x::Any, y::Any)::Any @wrap NextIterator(x::Any)::Any +@wrap NormalizedWhitespace(x::GapObj)::GapObj @wrap NumberColumns(x::Any)::GapInt @wrap NumberRows(x::Any)::GapInt @wrap NumeratorRat(x::Any)::GapInt @@ -55,14 +65,20 @@ import GAP: @wrap @wrap PROD(x::Any, y::Any)::Any @wrap PushOptions(x::Any)::Nothing @wrap QUO(x::Any, y::Any)::Any +@wrap Read(x::GapObj)::Nothing @wrap RecNames(x::Any)::Any +@wrap RemovePackage(x::GapObj, y::Bool)::Bool @wrap RNamObj(x::Any)::Int +@wrap SetInfoLevel(x::GapObj, y::Int)::Nothing +@wrap SetPackagePath(x::GapObj, y::GapObj)::Nothing @wrap ShallowCopy(x::Any)::Any @wrap String(x::Any)::Any @wrap StringDisplayObj(x::Any)::Any @wrap StringViewObj(x::Any)::Any @wrap StructuralCopy(x::Any)::Any @wrap SUM(x::Any, y::Any)::Any +@wrap UNB_REC(x::GapObj, y::Int)::Nothing +@wrap UpdatePackage(x::GapObj, y::Bool)::Bool @wrap ZeroSameMutability(x::Any)::Any end diff --git a/test/macros.jl b/test/macros.jl index a494bedc1..4b2f7dd3e 100644 --- a/test/macros.jl +++ b/test/macros.jl @@ -48,16 +48,12 @@ end @test x == GAP.evalstr("[1,2,3]") x = @gap(SymmetricGroup)(3) @test GAP.Globals.Size(x) == 6 - # Errors thrown by the GAP.@gap macro cannot be tested directly, - # the following test does not work as intended. - # @test_throws ErrorException @gap (1,2)(3,4) + @test_throws ErrorException @gap (1,2)(3,4) x = GAP.g"foo" @test x == GAP.julia_to_gap("foo") x = GAP.g"1:\n, 2:\", 3:\\, 4:\b, 5:\r, 6:\c, 7:\001" @test x == GAP.julia_to_gap("1:\n, 2:\", 3:\\, 4:\b, 5:\r, 6:\003, 7:\001") - # Errors thrown by the GAP.@g_str macro cannot be tested directly, - # and the string "\\" can be handed over in the macro. - @test_throws ErrorException GAP.gap_string_macro_helper("\\") + @test_throws ErrorException g"\\" end diff --git a/test/packages.jl b/test/packages.jl index c341d7e12..0fe55b74a 100644 --- a/test/packages.jl +++ b/test/packages.jl @@ -11,4 +11,21 @@ # @test GAP.Packages.install("fga", interactive = false, pkgdir = pkgdir) # @test GAP.Packages.remove("fga", interactive = false, pkgdir = pkgdir) + # Load packages via their local paths. + # - a package that was already loaded, with the same path + path = string(GAP.Globals.GAPInfo.PackagesLoaded.ctbllib[1]) + @test GAP.Packages.load(path) + + # - a package that was already loaded, with another installation path +#TODO: How to guarantee two installed versions with different paths? + + # - a package that was not yet loaded (only once in a Julia session) + if ! GAP.Globals.IsPackageLoaded(GAP.GapObj("autodoc")) + path = string(GAP.Globals.GAPInfo.PackagesInfo.autodoc[1].InstallationPath) + @test GAP.Packages.load(path) + end + + # - a nonexisting path + path = path * "xxx" + @test ! GAP.Packages.load(path) end