diff --git a/docs/src/misc.md b/docs/src/misc.md index bad0db290c..dee2215f29 100644 --- a/docs/src/misc.md +++ b/docs/src/misc.md @@ -54,37 +54,59 @@ get_attribute! set_attribute! ``` -The attributes system can be utilized to change the way certain objects are printed. -We provide macros `@show_special` and `@show_name` for this purpose, both are -called with the same argument -as `show`: an `IO`-object and the object itself. Both are supposed to be -used within the usual `show` function: -``` +## Advanced printing + +### Self-given names + +We provide macros `@show_name`, `@show_special` and `@show_special_elem` to +change the way certain objects are printed. + +In compact and supercompact printing mode, `@show_name` tries to determine +a suitable name to print instead of the object (see [`AbstractAlgebra.get_name`](@ref)). + +`@show_special` checks if an attribute `:show` is present. If so, it has to be +a function taking `IO`, optionally a MIME-type, and the object. +This is then called instead of the usual `show` function. + +Similarly, `@show_special_elem` checks if an attribute `:show_elem` is present in the object's +parent. The semantics are the same as for `@show_special`. + +All are supposed to be used within the usual `show` function, where `@show_special_elem` +is only relevant for element types of algebraic structures. +```julia function show(io::IO, A::MyObj) @show_name(io, A) @show_special(io, A) - ... usual stuff -``` + # ... usual stuff +end -`@show_special` checks if an attribute `:show` is present. If so, it has to be -a function taking `IO` and the object. This is then called instead of the usual -`show` function. +function show(io::IO, ::MIME"text/plain", A::MyObj) + @show_name(io, A) + @show_special(io, MIME"text/plain"(), A) -`@show_name` will check if there is a variable in global (`Main` module) namespace -with value bound to the object. In compact printing mode, the name is then shown -instead of the object. + # ... usual stuff +end +``` -Note: if the object is stored in several variable, the first one will be used. Also -the name, once used for printing, is stored in the object - hence will not change -anymore. +#### Documentation -## Advanced printing +```@docs +AbstractAlgebra.@show_special +AbstractAlgebra.@show_special_elem +AbstractAlgebra.@show_name +AbstractAlgebra.get_name +AbstractAlgebra.set_name! +AbstractAlgebra.extra_name +AbstractAlgebra.find_name +``` + +### Indentation and Decapitalization To facilitate printing of nested mathematical structures, we provide a modified `IOCustom` object, that supports indentation and decapitalization. -### Example +#### Example We illustrate this with an example @@ -125,7 +147,7 @@ Something of type A over Hilbert thing ``` -### Documentation +#### Documentation ```@docs AbstractAlgebra.pretty diff --git a/src/AbstractAlgebra.jl b/src/AbstractAlgebra.jl index f4641e03c7..199c6de967 100644 --- a/src/AbstractAlgebra.jl +++ b/src/AbstractAlgebra.jl @@ -473,9 +473,9 @@ import .PrettyPrinting: expr_to_latex_string import .PrettyPrinting: expr_to_string import .PrettyPrinting: expressify import .PrettyPrinting: extra_name -import .PrettyPrinting: find_name import .PrettyPrinting: get_current_module import .PrettyPrinting: get_html_as_latex +import .PrettyPrinting: get_name import .PrettyPrinting: get_syntactic_sign_abs import .PrettyPrinting: is_syntactic_one import .PrettyPrinting: is_syntactic_zero @@ -498,6 +498,8 @@ import .PrettyPrinting: Lowercase import .PrettyPrinting: Indent import .PrettyPrinting: Dedent +import .PrettyPrinting: find_new_name as find_name # remove once all call-sites use get_name instead + export @enable_all_show_via_expressify ############################################################################### diff --git a/src/PrettyPrinting.jl b/src/PrettyPrinting.jl index 4e06712e19..c739462f89 100644 --- a/src/PrettyPrinting.jl +++ b/src/PrettyPrinting.jl @@ -1382,150 +1382,263 @@ end ############################################################################### -# Macros for fancy printing. to use, enable attribute storage for your struct, -# i.e.m change # -# mutable struct bla.. -# ... -# end -# -# to -# -# @attributes mutable struct bla .. -# ... -# end -# -# Then, in the `show` method, start with -# @show_name(io, obj) -# If the user assigned a name to the object (in the REPL mainly) by doing -# A = bla... -# then, in the compact printing only the name "A" is printed -# also adding -# @show_special(io, obj) -# allows, if present to call a different printing function for this instance -# See FreeModule for an example +# Macros for fancy printing. # ############################################################################### -function set_name!(G::Any, name::String) - set_attribute!(G, :name => name) +const CurrentModule = Ref(Main) + +function set_current_module(m) + CurrentModule[] = m end -function set_name!(G) - s = get_attribute(G, :name) - s === nothing || return - sy = find_name(G) - sy === nothing && return - set_name!(G, string(sy)) +function get_current_module() + return CurrentModule[] end -extra_name(G) = nothing +""" + set_name!(obj, name::String; override::Bool=true) -macro show_name(io, O) - return :( begin - local i = $(esc(io)) - local o = $(esc(O)) - s = get_attribute(o, :name) - if s === nothing - sy = find_name(o) - if sy === nothing - sy = extra_name(o) - end - if sy !== nothing - s = string(sy) - set_name!(o, s) - end - end - if s !== nothing && (get(i, :supercompact, false) || get(i, :compact, false)) - if AbstractAlgebra.PrettyPrinting._supports_io_custom(i) - print(i, LowercaseOff()) - end - print(i, s) - return - end - end ) +Sets the name of the object `obj` to `name`. +This name is used for printing using [`AbstractAlgebra.@show_name`](@ref). +If `override` is `false`, the name is only set if there is no name already set. + +This function errors if `obj` does not support attribute storage. +""" +function set_name!(obj, name::String; override::Bool=true) + override || isnothing(get_attribute(obj, :name)) || return + set_attribute!(obj, :name => name) end -const CurrentModule = Ref(Main) +""" + set_name!(obj; override::Bool=true) -function set_current_module(m) - CurrentModule[] = m +Sets the name of the object `obj` to the name of a variable in global (`Main` module) namespace +with value bound to the object `obj`, if such a variable exists (see [`AbstractAlgebra.find_name`](@ref)). +This name is used for printing using [`AbstractAlgebra.@show_name`](@ref). +If `override` is `false`, the name is only set if there is no name already set. + +This function errors if `obj` does not support attribute storage. +""" +function set_name!(obj; override::Bool=true) + override || isnothing(get_attribute(obj, :name)) || return + sy = find_name(obj) + isnothing(sy) && return + set_name!(obj, string(sy); override=true) end -function get_current_module() - return CurrentModule[] +""" + extra_name(obj) -> Union{String,Nothing} + +May be overloaded to provide a fallback name for the object `obj` in [`AbstractAlgebra.get_name`](@ref). +The default implementation returns `nothing`. +""" +extra_name(obj) = nothing + +""" + find_name(obj, M = Main; all::Bool = false) -> Union{String,Nothing} + +Return name of a variable in `M`'s namespace with value bound to the object `obj`, +or `nothing` if no such variable exists. +If `all` is `true`, private and non-exported variables are also searched. + +!!! note + If the object is stored in several variables, the first one will be used, + but a name returned once is kept until the variable no longer contains this object. + +For this to work in doctests, one should call +`AbstractAlgebra.set_current_module(@__MODULE__)` in the `value` argument of +`Documenter.DocMeta.setdocmeta!` and keep the default value of `M = Main` here. + +!!! warning + This function should not be used directly, but rather through [`AbstractAlgebra.get_name`](@ref). +""" +function find_name(obj, M=Main; all::Bool=false) + AbstractAlgebra._is_attribute_storing_type(typeof(obj)) || return find_new_name(obj, M; all) + + cached_name = get_attribute(obj, :cached_name) + if !isnothing(cached_name) + cached_name_sy = Symbol(cached_name) + if M === Main && get_current_module() != Main + if isdefined(get_current_module(), cached_name_sy) && getproperty(get_current_module(), cached_name_sy) === obj + return cached_name + end + end + if isdefined(M, cached_name_sy) && getproperty(M, cached_name_sy) === obj + return cached_name + end + end + name = find_new_name(obj, M; all) + set_attribute!(obj, :cached_name => name) + return name end -function find_name(A, M = Main; all::Bool = false) +function find_new_name(obj, M=Main; all::Bool=false) # in Documenter, the examples are not run in the REPL. # in the REPL: A = ... adds A to the global name space (Main....) # in Documenter (doctests) all examples are run in their own module # which is stored in CurrentModule, hence we need to search there as well #furthermore, they are not exported, hence the "all" option - if M === Main && AbstractAlgebra.get_current_module() != Main - a = find_name(A, AbstractAlgebra.get_current_module(), all = true) - if a !== nothing + if M === Main && get_current_module() != Main + a = find_name(obj, get_current_module(), all=true) + if !isnothing(a) return a end end - for a = names(M, all = all) + for a = names(M; all=all) a === :ans && continue - if isdefined(M, a) && getfield(M, a) === A - return a + if isdefined(M, a) && getfield(M, a) === obj + return string(a) end end + return nothing end -macro show_special(io, O) - return :( begin - local i = $(esc(io)) - local o = $(esc(O)) - s = get_attribute(o, :show) - if s !== nothing - s(i, o) - return +""" + get_name(obj) -> Union{String,Nothing} + +Returns the name of the object `obj` if it is set, or `nothing` otherwise. +This function tries to find a name in the following order: +1. The name set by [`AbstractAlgebra.set_name!`](@ref). +2. The name of a variable in global (`Main` module) namespace with value bound to the object `obj` (see [`AbstractAlgebra.find_name`](@ref)). +3. The name returned by [`AbstractAlgebra.extra_name`](@ref). +""" +function get_name(obj) + if AbstractAlgebra._is_attribute_storing_type(typeof(obj)) + name = get_attribute(obj, :name) + isnothing(name) || return name + end + + sy = find_name(obj) + isnothing(sy) || return string(sy) + + name_maybe = extra_name(obj)::Union{String,Nothing} + isnothing(name_maybe) || return name_maybe + + return nothing +end + +""" + @show_name(io::IO, obj) + +If either property `:compact` or `:supercompact` is set to `true` for `io` +(see [`IOContext`](https://docs.julialang.org/en/v1/base/io-network/#Base.IOContext)), +print the name [`get_name(obj)`](@ref AbstractAlgebra.get_name) of the object `obj` to the `io` stream. +This macro either prints the name and returns from the current scope, or does nothing. + +It is supposed to be used at the start of `show` methods as shown in the documentation. +``` +""" +macro show_name(io, obj) + return :( + begin + local i = $(esc(io)) + local o = $(esc(obj)) + if get(i, :supercompact, false) || get(i, :compact, false) + name = get_name(o) + if !isnothing(name) + if AbstractAlgebra.PrettyPrinting._supports_io_custom(i) + print(i, LowercaseOff()) + end + print(i, name) + return + end + end end - end ) + ) end -macro show_special(io, mime, O) - return :( begin - local i = $(esc(io)) - local m = $(esc(mime)) - local o = $(esc(O)) - s = get_attribute(o, :show) - if s !== nothing - s(i, m, o) - return +""" + @show_special(io::IO, obj) + +If the `obj` has a `show` attribute, this gets called with `io` and `obj` and +returns from the current scope. Otherwise, does nothing. + +It is supposed to be used at the start of `show` methods as shown in the documentation. +""" +macro show_special(io, obj) + return :( + begin + local i = $(esc(io)) + local o = $(esc(obj)) + s = get_attribute(o, :show) + if s !== nothing + s(i, o) + return + end end - end ) + ) end -macro show_special_elem(io, e) - return :( begin - local i = $(esc(io)) - local a = $(esc(e)) - local o = parent(a) - s = get_attribute(o, :show_elem) - if s !== nothing - s(i, a) - return +""" + @show_special(io::IO, mime, obj) + +If the `obj` has a `show` attribute, this gets called with `io`, `mime` and `obj` and +returns from the current scope. Otherwise, does nothing. + +It is supposed to be used at the start of `show` methods as shown in the documentation. +""" +macro show_special(io, mime, obj) + return :( + begin + local i = $(esc(io)) + local m = $(esc(mime)) + local o = $(esc(obj)) + s = get_attribute(o, :show) + if s !== nothing + s(i, m, o) + return + end end - end ) -end - -macro show_special_elem(io, mime, e) - return :( begin - local i = $(esc(io)) - local m = $(esc(mime)) - local a = $(esc(e)) - local o = parent(a) - s = get_attribute(o, :show_elem) - if s !== nothing - s(i, m, a) - return + ) +end + +""" + @show_special_elem(io::IO, obj) + +If the `parent` of `obj` has a `show_special_elem` attribute, this gets called with `io` and `obj` and +returns from the current scope. Otherwise, does nothing. + +It is supposed to be used at the start of `show` methods as shown in the documentation. +""" +macro show_special_elem(io, obj) + return :( + begin + local i = $(esc(io)) + local o = $(esc(obj)) + local p = parent(o) + s = get_attribute(p, :show_elem) + if s !== nothing + s(i, o) + return + end + end + ) +end + +""" + @show_special_elem(io::IO, mime, obj) + +If the `parent` of `obj` has a `show` attribute, this gets called with `io`, `mime` and `obj` and +returns from the current scope. Otherwise, does nothing. + +It is supposed to be used at the start of `show` methods as shown in the documentation. +""" +macro show_special_elem(io, mime, obj) + return :( + begin + local i = $(esc(io)) + local m = $(esc(mime)) + local o = $(esc(obj)) + local p = parent(o) + s = get_attribute(p, :show_elem) + if s !== nothing + s(i, m, o) + return + end end - end ) + ) end diff --git a/test/PrettyPrinting-test.jl b/test/PrettyPrinting-test.jl index 906133937d..265c603f28 100644 --- a/test/PrettyPrinting-test.jl +++ b/test/PrettyPrinting-test.jl @@ -1,6 +1,6 @@ import AbstractAlgebra.PrettyPrinting -@testset "PrettyPrinting" begin +@testset "PrettyPrinting: Expression to string" begin function just_string(x) return AbstractAlgebra.expr_to_string(x) @@ -312,8 +312,19 @@ import AbstractAlgebra.PrettyPrinting @test AbstractAlgebra.obj_to_string_wrt_times(x + y) == "(x + y)" end -# Test various examples from the Oscar manual -@testset "PrettyPrinting examples" begin +@testset "PrettyPrinting: Special printing macros" begin + # TODO + +end + + +@testset "PrettyPrinting: Unicode preferences" begin + # TODO + +end + +@testset "PrettyPrinting: Three print modes" begin + # Test various examples from the Oscar manual # # @@ -398,7 +409,7 @@ end end -let +@testset "PrettyPrinting: Intendation and Decapitalization" begin io = IOBuffer() io = AbstractAlgebra.pretty(io, force_newlines = true) @test io isa AbstractAlgebra.PrettyPrinting.IOCustom