From ab1cde616bf432ffbd00e6139ee2278daa9f0d6f Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Wed, 27 Sep 2023 17:32:46 -0700 Subject: [PATCH 01/12] the great designification --- src/GATlab.jl | 8 +- src/syntax/Scopes.jl | 322 ++++++++++++++---------------------------- src/syntax/module.jl | 20 +-- test/syntax/Scopes.jl | 8 +- 4 files changed, 120 insertions(+), 238 deletions(-) diff --git a/src/GATlab.jl b/src/GATlab.jl index 39bdf22a..4c5a2169 100644 --- a/src/GATlab.jl +++ b/src/GATlab.jl @@ -5,12 +5,12 @@ using Reexport # because it's too small to refactor out. include("util/module.jl") include("syntax/module.jl") -include("models/module.jl") -include("stdlib/module.jl") +# include("models/module.jl") +# include("stdlib/module.jl") @reexport using .Util @reexport using .Syntax -@reexport using .Models -@reexport using .Stdlib +# @reexport using .Models +# @reexport using .Stdlib end # module GATlab diff --git a/src/syntax/Scopes.jl b/src/syntax/Scopes.jl index 99729ebf..42becd58 100644 --- a/src/syntax/Scopes.jl +++ b/src/syntax/Scopes.jl @@ -3,13 +3,13 @@ export ScopeTag, newscopetag, retag, rename, ScopeTagError, LID, - Ident, gettag, getlid, isnamed, - Binding, getvalue, setvalue, getsignature, getline, setline, + Ident, Alias, gettag, getlid, isnamed, + Binding, getvalue, setvalue, getline, setline, Context, getscope, nscopes, getlevel, hasname, hastag, HasContext, getcontext, hasident, ident, getidents, idents, canonicalize, HasScope, haslid, getscope, getbindings, getbinding, - sigtype, identvalues, namevalues, + identvalues, namevalues, Scope, ScopeList, HasScopeList, AppendScope, EmptyContext @@ -171,37 +171,28 @@ rename(tag::ScopeTag, replacements::Dict{Symbol, Symbol}, x::Ident) = # Bindings ########## +@struct_hash_equal struct Alias + ref::Ident +end + """ -`Binding{T, Sig}` +`Binding{T}` -A binding associates some `T`-typed value to a name, -disambiguated by a signature in `Sig` in the case of overloading. +A binding associates some `T`-typed value to a name. -`primary` is an optional distinguished name +`name` is an optional distinguished name `value` is the element -`sig` is a way of uniquely distinguishing this element from others with the same name -(for example, ⊗ : Ob x Ob -> Ob and Hom x Hom -> Hom) """ -@struct_hash_equal struct Binding{T, Sig} - primary::Union{Symbol, Nothing} - value::T - sig::Sig +@struct_hash_equal struct Binding{T} + name::Union{Symbol, Nothing} + value::Union{T, Alias} line::Union{LineNumberNode, Nothing} - function Binding{T, Sig}( - primary::Union{Symbol, Nothing}, - value::T, - sig::Sig=nothing, - line::Union{LineNumberNode, Nothing}=nothing - ) where {T, Sig} - new{T, Sig}(primary, value, sig, line) - end function Binding{T}( - primary::Union{Symbol, Nothing}, - value::T, - sig::Sig=nothing, + name::Union{Symbol, Nothing}, + value::Union{T, Alias}, line::Union{LineNumberNode, Nothing}=nothing - ) where {T, Sig} - Binding{T,Sig}(primary, value, sig, line) + ) where {T} + new{T}(name, value, line) end end @@ -222,32 +213,30 @@ function Base.show(io::IO, b::Binding; crayon=nothing) print(io, crayon, bname) print(io, inv(crayon)) end - print(io, isnothing(getsignature(b)) ? "" : "::$(getsignature(b))") print(io, " => $(repr(getvalue(b)))") end -Base.nameof(b::Binding) = b.primary +Base.nameof(b::Binding) = b.name +aliases(b::Binding) = b.aliases -MetaUtils.setname(b::Binding{T, Sig}, name::Symbol) where {T, Sig} = - Binding{T, Sig}(name, b.value, b.sig, b.line) +function MetaUtils.setname(b::Binding{T}, name::Symbol) where {T} + Binding{T}(name, b.value, b.line) +end getvalue(b::Binding) = b.value -getsignature(b::Binding) = b.sig - -retag(replacements::Dict{ScopeTag, ScopeTag}, binding::Binding{T, Sig}) where {T, Sig} = - Binding{T,Sig}( +retag(replacements::Dict{ScopeTag, ScopeTag}, binding::Binding{T}) where {T} = + Binding{T}( nameof(binding), retag(replacements, getvalue(binding)), - retag(replacements, getsignature(binding)) ) getline(b::Binding) = b.line -setline(b::Binding{T, Sig}, line::Union{LineNumberNode, Nothing}) where {T, Sig} = - Binding{T, Sig}(b.primary, b.value, b.sig, line) -setvalue(b::Binding{T, Sig}, t::T) where {T,Sig} = - Binding{T, Sig}(b.primary, t, b.sig, b.line) +setline(b::Binding{T}, line::Union{LineNumberNode, Nothing}) where {T} = + Binding{T}(b.name, b.value, line) +setvalue(b::Binding{T}, t::T) where {T} = + Binding{T}(b.name, t, b.line) # Context ######### @@ -267,9 +256,9 @@ this list. - `getlevel(c::Context, tag::ScopeTag) -> Int` - `getlevel(c::Context, name::Symbol) -> Int` """ -abstract type Context{T, Sig} end +abstract type Context{T} end -abstract type HasContext{T, Sig} <: Context{T, Sig} end +abstract type HasContext{T} <: Context{T} end function getcontext end @@ -295,14 +284,14 @@ Must overload `getscope(hs::HasScope) -> Scope` """ -abstract type HasScope{T, Sig} <: Context{T, Sig} end +abstract type HasScope{T} <: Context{T} end getscope(hs::HasScope, x::Int) = x == 1 ? getscope(hs) : throw(BoundsError(hs, x)) hastag(hs::HasScope, tag::ScopeTag) = getscope(hs).tag == tag -hasname(hs::HasScope, name::Symbol) = haskey(getscope(hs).primary, name) || haskey(getscope(hs).names, name) +hasname(hs::HasScope, name::Symbol) = haskey(getscope(hs).names, name) getlevel(hs::HasScope, tag::ScopeTag) = if hastag(hs, tag) @@ -324,49 +313,39 @@ nscopes(hs::HasScope) = 1 ######## """ -`Scope{T, Sig}` +`Scope{T}` -In GATlab, we handle overloading and shadowing with a notion of *scope*. -Names are allowed to overload within a scope, and shadow between scopes. +In GATlab, we handle shadowing with a notion of *scope*. +Names shadow between scopes. Anything which binds variables introduces a scope, for instance a `@theory` declaration or a context. For example, here is a scope with 3 elements: ``` -x::Int = 3 -y::String = "hello" -x::String = "ex" +x = 3 +y = "hello" +z = x ``` -This is a valid scope even though there are name collisions, because the -signature (in this case, a datatype) disambiguates. Of course, it may not be -wise to disambiguate by type, because it is not always possible to infer -expected type. In general, one should pick something that can be inferred, and -`Nothing` is always a reasonable choice, which disallows any name collisions. +Here z is introduced as an alias for x. It is illegal to shadow within a scope. +Overloading is not explicitly treated but can be managed by having values which +refer to identifiers earlier / the present scope. See GATs.jl, for example. """ -struct Scope{T, Sig} <: HasScope{T, Sig} +struct Scope{T} <: HasScope{T} # unique identifier for the scope tag::ScopeTag # ordered sequence of name assignments - bindings::Vector{Binding{T, Sig}} - # a cached mapping which takes a primary name and a disambiguator (i.e. signature) - # and returns the index in `bindings` - names::Dict{Symbol, Dict{Sig, LID}} - # Maps a primary name to its aliases - aliases::Dict{Symbol, Set{Symbol}} - # Maps an alias to its primary name - primary::Dict{Symbol, Symbol} - function Scope{T, Sig}(tag, bindings, names, - aliases=Dict{Symbol, Set{Symbol}}(), - primary=nothing) where {T,Sig} - primary = isnothing(primary) ? make_primary_map(aliases) : primary - check_names(bindings, names, aliases, primary) - new{T,Sig}(tag, bindings, names, aliases, primary) - end -end - -check_names(s::Scope) = check_names(s.bindings, s.names, s.aliases, s.primary) + bindings::Vector{Binding{T}} + # cached mapping + names::Dict{Symbol, LID} + function Scope{T}(tag, bindings, names) where {T} + check_names(bindings, names) + new{T}(tag, bindings, names) + end +end + +check_names(s::Scope) = check_names(s.bindings, s.names) -function check_names(bindings, names, aliases, primary) +function check_names(bindings, names) # Check bindings don't shadow each other namevals = Set([(nameof(b), getvalue(b)) for b in bindings]) shadow = "Scopes do not permit shadowing: $namevals" @@ -378,47 +357,23 @@ function check_names(bindings, names, aliases, primary) missing_names = setdiff(allnames, keys(names)) isempty(extra_names) || error("Extra names: $extra_names") isempty(missing_names) || error("Missing names: $missing_names") - - # Check alias names are valid - bad_a_names = setdiff(keys(aliases), allnames) - isempty(bad_a_names) || error("Bad alias keys: $bad_a_names") - - # Check alias values are valid - aliasvalues = union(Symbol[], values(aliases)...) - bad_a_values = intersect(aliasvalues, allnames) - isempty(bad_a_values) || error("Alias / name collision: $bad_a_values") - - # Check that primary names are all valid - bad_pvals = setdiff(values(primary), keys(names)) - isempty(bad_pvals) || error("Bad primary name values: $bad_pvals") - - # Check that primary aliases are all valid - bad_pkeys = setdiff(keys(primary), aliasvalues) - isempty(bad_pkeys) || error("Bad primary name values: $bad_pkeys") end Base.:(==)(s1::Scope, s2::Scope) = s1.tag == s2.tag Base.hash(s::Scope, h::UInt64) = hash(s.tag, h) -Scope{T, Sig}() where {T, Sig} = - Scope{T, Sig}(newscopetag(), Binding{T, Sig}[], Dict{Symbol, Dict{Sig, LID}}()) +Scope{T}() where {T} = + Scope{T}(newscopetag(), Binding{T}[], Dict{Symbol, LID}()) -function make_name_dict(bindings::AbstractVector{Binding{T, Sig}}) where {T, Sig} - d = Dict{Symbol, Dict{Sig, LID}}() +function make_name_dict(bindings::AbstractVector{Binding{T}}) where {T} + d = Dict{Symbol, LID}() for (i, binding) in enumerate(bindings) name = nameof(binding) if isnothing(name) continue end - if !(name ∈ keys(d) ) - d[name] = Dict{Sig, LID}() - end - sig = getsignature(binding) - if sig ∈ keys(d[name]) - error("already defined $name with signature $sig") - end - d[name][sig] = LID(i) + d[name] = LID(i) end d end @@ -433,37 +388,34 @@ function make_primary_map(aliases::Dict{Symbol, Set{Symbol}}) primary end -function Scope(bindings::Vector{Binding{T, Sig}}; - aliases=Dict{Symbol, Set{Symbol}}(), - tag=newscopetag()) where {T, Sig} - Scope{T, Sig}(tag, bindings, make_name_dict(bindings), aliases, +function Scope(bindings::Vector{Binding{T}}; + tag=newscopetag()) where {T} + Scope{T}(tag, bindings, make_name_dict(bindings), make_primary_map(aliases)) end function Scope(pairs::Pair{Symbol, T}...; - aliases=Dict{Symbol, Set{Symbol}}(), tag=newscopetag()) where {T} Scope( - Binding{T, Nothing}[Binding{T, Nothing}(x, v) for (x, v) in pairs]; aliases, tag) + Binding{T}[Binding{T}(x, v) for (x, v) in pairs]; tag) end function Scope{T}(pairs::Pair{Symbol, <:T}...; - aliases=Dict{Symbol, Set{Symbol}}(), tag=newscopetag()) where {T} - Scope(Binding{T, Nothing}[Binding{T, Nothing}(x, v) for (x, v) in pairs]; aliases, tag) + Scope(Binding{T}[Binding{T}(x, v) for (x, v) in pairs]; tag) end -function Scope(symbols::Symbol...; aliases=Dict{Symbol, Set{Symbol}}(), tag=newscopetag()) - Scope(Pair{Symbol, Nothing}[x => nothing for x in symbols]...; aliases, tag) +function Scope(symbols::Symbol...; tag=newscopetag()) + Scope(Pair{Symbol}[x => nothing for x in symbols]...; tag) end -retag(replacements::Dict{ScopeTag,ScopeTag}, s::Scope{T,Sig}) where {T,Sig} = - Scope{T,Sig}(get(replacements, gettag(s), gettag(s)), +retag(replacements::Dict{ScopeTag,ScopeTag}, s::Scope{T}) where {T} = + Scope{T}(get(replacements, gettag(s), gettag(s)), retag.(Ref(replacements), s.bindings), - s.names, s.aliases, s.primary) + s.names) function Base.show(io::IO, s::Scope) - n = length(s.bindings) + length(s.primary) + n = length(s.bindings) print(io, "{") for (i, b) in enumerate(s.bindings) print(io, ScopedBinding(gettag(s), b)) @@ -471,12 +423,6 @@ function Base.show(io::IO, s::Scope) print(io, ", ") end end - for (i, (alias, name)) in enumerate(sort(collect(pairs(s.primary)))) - print(io, "$alias = $name") - if i + length(s.bindings) < n - print(io, ", ") - end - end print(io, "}") end @@ -491,45 +437,21 @@ getscope(s::Scope) = s """ Add a new binding to the end of Scope `s`. """ -function unsafe_pushbinding!(s::Scope{T, Sig}, b::Binding{T,Sig}) where {T, Sig} - if haskey(s.primary, nameof(b)) - b = setname(b, s.primary[nameof(b)]) +function unsafe_pushbinding!(s::Scope{T}, b::Binding{T}) where {T} + if haskey(s.names, nameof(b)) + error("name $name already defined in scope $s") end name = nameof(b) if !isnothing(name) - if !haskey(s.names, name) - s.names[name] = Dict{Sig, LID}() - end if !haskey(s.aliases, name) s.aliases[name] = Set{Symbol}() end - sig = getsignature(b) - if haskey(s.names[name], sig) - error("name $name already defined with signature $sig in scope $s") - end - s.names[name][sig] = LID(length(s.bindings) + 1) + s.names[name] = LID(length(s.bindings) + 1) end push!(s.bindings, b) b end -""" -Adds a new alias to the name `x`. This works even if no binding with the name `x` -has been added yet; when a binding is added with name `x`, it will have this alias. -""" -function unsafe_addalias!(s::Scope, name::Symbol, alias::Symbol) - !haskey(s.names, alias) || - error("cannot add $alias as an alias for $name: $alias is already a primary name in $s") - if haskey(s.primary, name) - name = s.primary[name] - end - if !haskey(s.aliases, name) - s.aliases[name] = Set{Symbol}() - end - s.primary[alias] = name - push!(s.aliases[name], alias) -end - # HasScope utilities #################### @@ -537,41 +459,22 @@ gettag(hs::HasScope) = getscope(hs).tag haslid(hs::HasScope, lid::LID) = lid.val ∈ eachindex(getbindings(hs)) -function normalize_name(hs::HasScope, name::Symbol) - s = getscope(hs) - haskey(s.names, name) ? name : (haskey(s.primary, name) ? s.primary[name] : nothing) -end - function hasname(hs::HasScope, name::Symbol, lid::LID) s = getscope(hs) - name = normalize_name(s, name) - !(isnothing(name)) && lid ∈ values(s.names[name]) + lid == s.names[name] end +hasname(hs::HasScope, name::Symbol) = haskey(getscope(hs).names, name) + hasident(hs::HasScope, x::Ident) = gettag(hs) == gettag(x) && haslid(hs, getlid(x)) && (isnothing(nameof(x)) || hasname(hs, nameof(x), getlid(x))) """ -Determine the level of a binding given the name and possibly the signature +Determine the level of a binding given the name """ -function getlid( - hs::HasScope{T, Sig}, name::Symbol; - sig::Union{Sig, Nothing}=nothing, - isunique::Bool=false -) where {T,Sig} - s = getscope(hs) - name = normalize_name(s, name) - if !isnothing(name) - if sig ∈ keys(s.names[name]) - return s.names[name][sig] - elseif isunique && length(s.names[name]) == 1 - return only(values(s.names[name])) - end - end - throw(KeyError((name, sig))) -end +getlid(hs::HasScope, name::Symbol) = getscope(hs).names[name] function getlid( hs::HasScope; @@ -579,15 +482,13 @@ function getlid( name::Union{Symbol, Nothing}=nothing, lid::Union{LID, Nothing}=nothing, level::Union{Int, Nothing}=nothing, - sig::Any=nothing, - isunique::Bool=false, ) s = getscope(hs) isnothing(level) || level == 1 || throw(BoundsError(level, hs)) isnothing(tag) || tag == gettag(s) || throw(ScopeTagError(hs, tag)) if isnothing(lid) if !isnothing(name) - getlid(s, name; sig, isunique) + getlid(s, name) end else if haslid(hs, lid) @@ -614,7 +515,7 @@ getbinding(hs::HasScope, x::Ident) = throw(ScopeTagError(hs, x)) end - getbinding(hs::HasScope, name::Symbol) = getbinding(hs, getlid(hs, name; isunique=true)) + getbinding(hs::HasScope, name::Symbol) = getbinding(hs, getlid(hs, name)) getbinding(hs::HasScope, xs::AbstractVector) = getbinding.(Ref(hs), xs) function ident( @@ -623,16 +524,9 @@ function ident( name::Union{Symbol, Nothing}=nothing, lid::Union{LID, Nothing}=nothing, level::Union{Int, Nothing}=nothing, - sig::Any=nothing, - isunique::Bool=false, ) - lid = getlid(hs; tag, name, lid, level, sig, isunique) - binding = getbinding(hs, lid) - if !isnothing(name) - return Ident(gettag(hs), lid, name) - else - return Ident(gettag(hs), lid, nameof(binding)) - end + lid = getlid(hs; tag, name, lid, level) + Ident(gettag(hs), lid, name) end function identvalues(hs::HasScope) @@ -661,7 +555,6 @@ function Base.values(hs::HasScope) end Base.valtype(::HasScope{T}) where {T} = T -sigtype(::HasScope{T, Sig}) where {T, Sig} = Sig # Overloads of methods in Base @@ -714,11 +607,7 @@ Keywords arguments: - `name::Union{Symbol, Nothing}`. The name of the identifier. - `lid::Union{LID, Nothing}`. The lid of the identifier within its scope. - `level::Union{Int, Nothing}`. The level of the scope within the context. -- `sig::Any`. The signature of the identifier, to disambiguate between multiple - identifiers with the same name within the same scope. - `strict::Bool`. If `strict` is true, throw an error if not found, else return nothing. -- `isunique::Bool`. If `isunique` is true, then don't use the signature to disambiguate, - instead fail if their are multiple identifiers with the same name in a scope. """ function ident( c::Context; @@ -726,8 +615,6 @@ function ident( name::Union{Symbol, Nothing}=nothing, lid::Union{LID, Nothing}=nothing, level::Union{Int, Nothing}=nothing, - sig::Any=nothing, - isunique::Bool=false, ) if isnothing(level) if !isnothing(tag) @@ -745,11 +632,11 @@ function ident( end end if !isnothing(level) - return ident(getscope(c, level); tag, name, lid, sig, isunique) + return ident(getscope(c, level); tag, name, lid) else error( "insufficient information provided to determine the scope for building an identifier:" * - "$((;tag, name, lid, level, sig))" + "$((;tag, name, lid, level))" ) end end @@ -762,7 +649,7 @@ function hasident(c::Context; kwargs...) try ident(c; kwargs...) true - catch e + catch _ false end end @@ -774,12 +661,10 @@ function idents( name=Ref(nothing), lid=Ref(nothing), level=Ref(nothing), - sig=Ref(nothing), - isunique=Ref(false), ) broadcast( - (tag, name, lid, level, sig, isunique) -> ident(c; tag, name, lid, level, sig, isunique), - tag, name, lid, level, sig, isunique + (tag, name, lid, level) -> ident(c; tag, name, lid, level), + tag, name, lid, level ) end @@ -800,13 +685,13 @@ Must implement: `getscopelist(hsl::HasScopeList) -> ScopeList` """ -abstract type HasScopeList{T, Sig} <: Context{T, Sig} end +abstract type HasScopeList{T} <: Context{T} end -struct ScopeList{T, Sig} <: HasScopeList{T, Sig} - scopes::Vector{HasScope{T,Sig}} +struct ScopeList{T} <: HasScopeList{T} + scopes::Vector{HasScope{T}} taglookup::Dict{ScopeTag, Int} namelookup::Dict{Symbol, Int} - function ScopeList(scopes::Vector{<:HasScope{T,Sig}}) where {T,Sig} + function ScopeList(scopes::Vector{<:HasScope{T}}) where {T} allunique(gettag.(scopes)) || error("tags of scopes in ScopeList must all be unique") taglookup = Dict{ScopeTag, Int}() namelookup = Dict{Symbol, Int}() @@ -816,11 +701,8 @@ struct ScopeList{T, Sig} <: HasScopeList{T, Sig} for name in keys(s.names) namelookup[name] = i # overwrite most recent end - for alias in keys(s.primary) - namelookup[alias] = i - end end - new{T, Sig}(scopes, taglookup, namelookup) + new{T}(scopes, taglookup, namelookup) end end @@ -866,9 +748,9 @@ getidents(hsl::HasScopeList; kw...) = vcat(getidents.(getscopelist(hsl))...) Flatten a scopelist if possible. This will fail if any of the bindings shadow bindings in earlier scopes. """ -function flatten(hsl::HasScopeList{T, Sig}) where {T, Sig} +function flatten(hsl::HasScopeList{T}) where {T} if nscopes(hsl) == 0 - Scope{T, Sig}() + Scope{T}() else res = Scope(getbindings(deepcopy(getscope(hsl, 1)))) newtag = gettag(res) @@ -890,21 +772,23 @@ flatten(s::Scope) = s # AppendScope ############# -struct AppendScope{T₁, Sig₁, T₂, Sig₂} <: Context{Union{T₁,T₂}, Union{Sig₁,Sig₂}} - context::Context{T₁, Sig₁} - last::Scope{T₂, Sig₂} - function AppendScope(context::Context{T₁, Sig₁}, last::Scope{T₂, Sig₂}) where {T₁, Sig₁, T₂, Sig₂} +struct AppendScope{T₁, T₂} <: Context{Union{T₁,T₂}} + context::Context{T₁} + last::Scope{T₂} + function AppendScope(context::Context{T₁}, last::Scope{T₂}) where {T₁, T₂} !hastag(context, gettag(last)) || error("All scopes in context must have unique tags: collision with level $(getlevel(context, gettag(last)))") - new{T₁, Sig₁, T₂, Sig₂}(context, last) + new{T₁, T₂}(context, last) end end -function AppendScope(context::Context{T₁, Sig₁}, last::ScopeList{T₂, Sig₂}) where {T₁, Sig₁, T₂, Sig₂} + +function AppendScope(context::Context, last::ScopeList) res = context for scope in getscope.(Ref(last), 1:nscopes(last)) res = AppendScope(res, scope) end res end + getscope(c::AppendScope, level::Int) = if level == nscopes(c) c.last @@ -937,7 +821,7 @@ getidents(scope::AppendScope; kw...) = # EmptyContext ############## -struct EmptyContext{T, Sig} <: Context{T, Sig} +struct EmptyContext{T} <: Context{T} end getscope(c::EmptyContext, level::Int) = throw(BoundsError(c, level)) diff --git a/src/syntax/module.jl b/src/syntax/module.jl index b5ee07a3..5c95dc98 100644 --- a/src/syntax/module.jl +++ b/src/syntax/module.jl @@ -3,17 +3,17 @@ module Syntax using Reexport include("Scopes.jl") -include("ExprInterop.jl") -include("GATs.jl") -include("Presentations.jl") -include("TheoryInterface.jl") -include("TheoryMaps.jl") +# include("ExprInterop.jl") +# include("GATs.jl") +# include("Presentations.jl") +# include("TheoryInterface.jl") +# include("TheoryMaps.jl") @reexport using .Scopes -@reexport using .ExprInterop -@reexport using .GATs -@reexport using .Presentations -@reexport using .TheoryInterface -@reexport using .TheoryMaps +# @reexport using .ExprInterop +# @reexport using .GATs +# @reexport using .Presentations +# @reexport using .TheoryInterface +# @reexport using .TheoryMaps end diff --git a/test/syntax/Scopes.jl b/test/syntax/Scopes.jl index 203f2ef9..af536c6d 100644 --- a/test/syntax/Scopes.jl +++ b/test/syntax/Scopes.jl @@ -67,12 +67,10 @@ bind_y = Binding{String}(:y, "why") @test nameof(bind_x) == :x @test getvalue(bind_x) == "ex" -@test isnothing(getsignature(bind_x)) @test basicprinted(bind_x) == "x => \"ex\"" -bind_z = Binding{String, Int}(:x, "ex", 1) +bind_z = Binding{String}(:x, "ex") -@test getsignature(bind_z) == 1 @test getline(setline(bind_z, LineNumberNode(1))) == LineNumberNode(1) # Context @@ -121,9 +119,9 @@ value_scope = Scope{Union{Int, String}}(:x => 1, :y => 1) @test values(value_scope) == [1, 1] @test getvalue(value_scope, :x) == 1 -s = Scope{String, Int}() +s = Scope{String}() -bind_x_typed = Binding{String, Int}(:x, "ex", 2) +bind_x_typed = Binding{String}(:x, "ex") Scopes.unsafe_pushbinding!(s, bind_x_typed) @test_throws ErrorException Scopes.unsafe_pushbinding!(s, bind_x_typed) From 97b1b962a6da135590a2e00de0aa278c6c026bd8 Mon Sep 17 00:00:00 2001 From: Owen Lynch Date: Tue, 3 Oct 2023 14:45:34 -0700 Subject: [PATCH 02/12] basic theories and models working --- src/GATlab.jl | 8 +- src/models/ModelInterface.jl | 462 ++++++++-------- src/models/module.jl | 8 +- src/stdlib/module.jl | 12 +- src/stdlib/theories/Categories.jl | 63 ++- src/stdlib/theories/module.jl | 12 +- src/syntax/ExprInterop.jl | 13 +- src/syntax/GATs.jl | 887 +----------------------------- src/syntax/Scopes.jl | 255 +++++---- src/syntax/TheoryInterface.jl | 64 +-- src/syntax/gats/algorithms.jl | 235 ++++++++ src/syntax/gats/ast.jl | 167 ++++++ src/syntax/gats/exprinterop.jl | 349 ++++++++++++ src/syntax/gats/gat.jl | 168 ++++++ src/syntax/gats/judgments.jl | 142 +++++ src/syntax/module.jl | 12 +- test/models/ModelInterface.jl | 6 +- test/syntax/GATs.jl | 62 ++- test/syntax/Scopes.jl | 33 +- test/syntax/TheoryInterface.jl | 2 +- 20 files changed, 1569 insertions(+), 1391 deletions(-) create mode 100644 src/syntax/gats/algorithms.jl create mode 100644 src/syntax/gats/ast.jl create mode 100644 src/syntax/gats/exprinterop.jl create mode 100644 src/syntax/gats/gat.jl create mode 100644 src/syntax/gats/judgments.jl diff --git a/src/GATlab.jl b/src/GATlab.jl index 4c5a2169..39bdf22a 100644 --- a/src/GATlab.jl +++ b/src/GATlab.jl @@ -5,12 +5,12 @@ using Reexport # because it's too small to refactor out. include("util/module.jl") include("syntax/module.jl") -# include("models/module.jl") -# include("stdlib/module.jl") +include("models/module.jl") +include("stdlib/module.jl") @reexport using .Util @reexport using .Syntax -# @reexport using .Models -# @reexport using .Stdlib +@reexport using .Models +@reexport using .Stdlib end # module GATlab diff --git a/src/models/ModelInterface.jl b/src/models/ModelInterface.jl index 682379ed..7496b18b 100644 --- a/src/models/ModelInterface.jl +++ b/src/models/ModelInterface.jl @@ -132,6 +132,7 @@ end """ macro instance(head, model, body) # Parse the head of @instance to get theory and instance types + # TODO: should we allow instance types to be nothing? Is this in Catlab? (theory_module, instance_types) = @match head begin :($ThX{$(Ts...)}) => (ThX, Ts) :($ThX) => (ThX, nothing) @@ -147,23 +148,26 @@ macro instance(head, model, body) # Get the model type that we are overloading for, or nothing if this is the # default instance for `instance_types` model_type, whereparams = parse_model_param(model) + # Parse the body into functions defined here and functions defined elsewhere functions, ext_functions = parse_instance_body(body) + # The old (Catlab) style of instance, where there is no explicit model oldinstance = isnothing(model) - # Checks that all the functions are defined with the correct types Add default + # Checks that all the functions are defined with the correct types. Adds default # methods for type constructors and type argument accessors if these methods # are missing typechecked_functions = if !isnothing(jltype_by_sort) - typecheck_instance(theory, functions, ext_functions, jltype_by_sort; - oldinstance) + typecheck_instance(theory, functions, ext_functions, jltype_by_sort; oldinstance) else [functions..., ext_functions...] # skip typechecking and expand_fail end # Adds keyword arguments to the functions, and qualifies them by - # `theory_module`, i.e. changes `Ob(x) = blah` to `ThCategory.Ob(x; model::M, - # context=nothing) = blah` + # `theory_module`, i.e. changes + # `Ob(x) = blah` + # to + # `ThCategory.Ob(m::WithModel{M}, x; context=nothing) = let model = m.model in blah end` qualified_functions = map(fun -> qualify_function(fun, theory_module, model_type, whereparams), typechecked_functions) @@ -224,30 +228,32 @@ function parse_instance_body(expr::Expr) return (funs, ext_funs) end +function args_from_sorts(sorts::Vector{AlgSort}, jltype_by_sort::Dict{AlgSort}) + Expr0[Expr(:(::), gensym(), jltype_by_sort[s]) for s in sorts] +end + function default_typecon_impl(X::Ident, theory::GAT, jltype_by_sort::Dict{AlgSort}) typecon = getvalue(theory[X]) - valname = gensym(:val) - args = [ - Expr(:(::), name, jltype_by_sort[x]) - for (name, x) in [(valname, AlgSort(X)); [(gensym(), x) for x in sortsignature(typecon)]] - ] + sort = AlgSort(getdecl(typecon), X) + jltype = jltype_by_sort[sort] + args = args_from_sorts([sort; sortsignature(typecon)], jltype_by_sort) JuliaFunction( - nameof(X), - args, - Expr0[], Expr0[], jltype_by_sort[AlgSort(X)], :(return $valname), nothing + name = nameof(getdecl(typecon)), + args = args, + return_type = jltype, + impl = :(return $(args[1].args[1])), ) end -function default_accessor_impl( - X::Ident, - accessor::Symbol, - jltype_by_sort::Dict{AlgSort} -) - jltype = jltype_by_sort[AlgSort(X)] - errormsg = "$(accessor) not defined for $(jltype)" - JuliaFunction( - accessor, Expr0[Expr(:(::), jltype)], Expr0[], Expr0[], - nothing, :(error($errormsg * " in model $model")) +function default_accessor_impl(x::Ident, theory::GAT, jltype_by_sort::Dict{AlgSort}) + acc = getvalue(theory[x]) + sort = AlgSort(acc.typecondecl, acc.typecon) + jltype = jltype_by_sort[sort] + errormsg = "$(acc) not defined for $(jltype)" + JuliaFunction(; + name = nameof(getdecl(acc)), + args = Expr0[Expr(:(::), jltype)], + impl = :(error($errormsg * " in model $model")) ) end @@ -255,11 +261,9 @@ julia_signature(theory::GAT, x::Ident, jltype_by_sort::Dict{AlgSort}) = julia_signature(theory, x, getvalue(theory[x]), jltype_by_sort) function julia_signature( - theory::GAT, - x::Ident, termcon::AlgTermConstructor, jltype_by_sort::Dict{AlgSort}; - oldinstance=false + oldinstance=false, kw... ) sortsig = sortsignature(termcon) args = if oldinstance && isempty(sortsig) @@ -268,30 +272,31 @@ function julia_signature( Expr0[jltype_by_sort[sort] for sort in sortsignature(termcon)] end JuliaFunctionSig( - nameof(x), + nameof(getdecl(termcon)), args ) end function julia_signature( - theory::GAT, - X::Ident, typecon::AlgTypeConstructor, - jltype_by_sort::Dict{AlgSort} + jltype_by_sort::Dict{AlgSort}; + X, kw... ) + decl = getdecl(typecon) + sort = AlgSort(decl, X) JuliaFunctionSig( - nameof(X), - Expr0[jltype_by_sort[sort] for sort in [AlgSort(X), sortsignature(typecon)...]] + nameof(decl), + Expr0[jltype_by_sort[sort] for sort in [sort, sortsignature(typecon)...]] ) end function julia_signature( - theory::GAT, - X::Ident, - a::Symbol, - jltype_by_sort::Dict{AlgSort} + acc::AlgAccessor, + jltype_by_sort::Dict{AlgSort}; + kw... ) - JuliaFunctionSig(a, [jltype_by_sort[AlgSort(X)]]) + jlargtype = jltype_by_sort[AlgSort(acc.typecondecl, acc.typecon)] + JuliaFunctionSig(nameof(getdecl(acc)), [jlargtype]) end function ExprInterop.toexpr(sig::JuliaFunctionSig) @@ -327,96 +332,74 @@ function typecheck_instance( )::Vector{JuliaFunction} typechecked = JuliaFunction[] - undefined_signatures = Dict{JuliaFunctionSig, Union{Ident, Tuple{Ident, Symbol}}}() + # The overloads that we have to provide + undefined_signatures = Dict{JuliaFunctionSig, Tuple{Ident, Ident}}() overload_errormsg = "the types for this model declaration do not permit Julia overloading to distinguish between GAT overloads" - for x in theory.termcons - if nameof(x) ∉ ext_functions - sig = julia_signature(theory, x, getvalue(theory[x]), jltype_by_sort; oldinstance) - if haskey(undefined_signatures, sig) - error(overload_errormsg) - end - undefined_signatures[sig] = x - end - end - for X in theory.typecons - if nameof(X) ∉ ext_functions - sig = julia_signature(theory, X, getvalue(theory[X]), jltype_by_sort) + for (decl, resolver) in theory.resolvers + for (_, x) in allmethods(resolver) + sig = julia_signature(getvalue(theory[x]), jltype_by_sort; oldinstance, X=x) if haskey(undefined_signatures, sig) error(overload_errormsg) end - undefined_signatures[sig] = X - end - end - for (a, Xs) in pairs(theory.accessors) - if a ∉ ext_functions - for X in Xs - sig = julia_signature(theory, X, a, jltype_by_sort) - if haskey(undefined_signatures, sig) - error(overload_errormsg) - end - undefined_signatures[sig] = (X,a) - end + undefined_signatures[sig] = (decl, x) end end - expected_signatures = DefaultDict{Symbol, Set{Expr0}}(()->Set{Expr0}()) + expected_signatures = DefaultDict{Ident, Set{Expr0}}(()->Set{Expr0}()) - for (sig, n) in undefined_signatures - name = if n isa Ident - nameof(n) - else - last(n) - end - push!(expected_signatures[name], toexpr(sig)) + for (sig, (decl, _)) in undefined_signatures + push!(expected_signatures[decl], toexpr(sig)) end for f in functions sig = parse_function_sig(f) - if !haskey(undefined_signatures, sig) - try - x = ident(theory; name=f.name) - catch e - throw(SignatureMismatchError(f.name, toexpr(sig), expected_signatures[f.name])) - end - judgment = getvalue(theory, x) - # We allow overloading for type constructors - if !(judgment isa AlgTypeConstructor) - throw(SignatureMismatchError(f.name, toexpr(sig), expected_signatures[f.name])) - end - - push!(typechecked, expand_fail(theory, x, f)) - else - x = undefined_signatures[sig] + if haskey(undefined_signatures, sig) + (decl, method) = undefined_signatures[sig] - if x isa Ident - judgment = getvalue(theory, x) + judgment = getvalue(theory, method) - if judgment isa AlgTypeConstructor - f = expand_fail(theory, x, f) - end + if judgment isa AlgTypeConstructor + f = expand_fail(theory, decl, f) end delete!(undefined_signatures, sig) push!(typechecked, f) + else + if hasname(theory, f.name) + x = ident(theory; name=f.name) + throw(SignatureMismatchError(f.name, toexpr(sig), expected_signatures[x])) + else + error("no declaration in the theory has name $f.name") + end + # TODO: allow extra overloads for type constructors to provide additional coercions + # try + # x = ident(theory; name=f.name) + # catch e + # throw(SignatureMismatchError(f.name, toexpr(sig), expected_signatures[f.name])) + # end + + # methods = last.(allmethods(theory.resolvers[x])) + + # if !(any(getvalue(theory[m]) isa AlgTypeConstructor for m in methods)) + # end + + # push!(typechecked, expand_fail(theory, x, f)) end end - for (_, n) in undefined_signatures - if n isa Ident - judgment = getvalue(theory[n]) - if judgment isa AlgTermConstructor - error("Failed to implement $(nameof(n))") - elseif judgment isa AlgTypeConstructor - push!(typechecked, default_typecon_impl(n, theory, jltype_by_sort)) - end - else - (X, a) = n - push!(typechecked, default_accessor_impl(X, a, jltype_by_sort)) + for (_, (decl, method)) in undefined_signatures + judgment = getvalue(theory[method]) + if judgment isa AlgTermConstructor + error("Failed to implement $(nameof(n))") + elseif judgment isa AlgTypeConstructor + push!(typechecked, default_typecon_impl(method, theory, jltype_by_sort)) + elseif judgment isa AlgAccessor + push!(typechecked, default_accessor_impl(method, theory, jltype_by_sort)) end end @@ -431,7 +414,12 @@ function expand_fail(theory::GAT, x::Ident, f::JuliaFunction) let $(fail_var) = reason -> throw( $(TypeCheckFail)( - model, $theory, $x, $(argname(f.args[1])), $(Expr(:vect, argname.(f.args[2:end])...)), reason + model, + $theory, + $x, + $(argname(f.args[1])), + $(Expr(:vect, argname.(f.args[2:end])...)), + reason )) $(f.impl) end @@ -511,140 +499,140 @@ macro withmodel(model, subsexpr, body) end -""" -Given a Theory Morphism T->U and a type Mᵤ (whose values are models of U), -obtain a type Mₜ which has one parameter (of type Mᵤ) and is a model of T. - -E.g. given NatIsMonoid: ThMonoid->ThNatPlus and IntPlus <: Model{Tuple{Int}} -and IntPlus implements ThNatPlus: - -``` -@migrate IntPlusMonoid = NatIsMonoid(IntPlus){Int} -``` - -Yields: - -``` -struct IntPlusMonoid <: Model{Tuple{Int}} - model::IntPlus -end - -@instance ThMonoid{Int} [model::IntPlusMonoid] begin ... end -``` - -Future work: There is some subtlety in how accessor functions should be handled. -TODO: The new instance methods do not yet handle the `context` keyword argument. -""" -macro migrate(head) - # Parse - (name, mapname, modelname) = @match head begin - Expr(:(=), name, Expr(:call, mapname, modelname)) => - (name, mapname, modelname) - _ => error("could not parse head of @theory: $head") - end - codom_types = :(only(supertype($(esc(modelname))).parameters).types) - # Unpack - tmap = macroexpand(__module__, :($mapname.@map)) - dom_module = macroexpand(__module__, :($mapname.@dom)) - codom_module = macroexpand(__module__, :($mapname.@codom)) - dom_theory, codom_theory = TheoryMaps.dom(tmap), TheoryMaps.codom(tmap) - - codom_jltype_by_sort = Dict{Ident,Expr0}(map(enumerate(sorts(codom_theory))) do (i,v) - v.ref => Expr(:ref, codom_types, i) - end) - _x = gensym("val") - - dom_types = map(sorts(dom_theory)) do s - codom_jltype_by_sort[typemap(tmap)[s.ref].trm.head] - end - jltype_by_sort = Dict(zip(sorts(dom_theory), dom_types)) - - # TypeCons for @instance macro - funs = map(collect(typemap(tmap))) do (x, fx) - xname = nameof(x) - fxname = nameof(fx.trm.head) - tc = getvalue(dom_theory[x]) - jltype_by_sort[AlgSort(fx.trm.head)] = jltype_by_sort[AlgSort(x)] - sig = julia_signature(dom_theory, x, jltype_by_sort) - - argnames = [_x, nameof.(argsof(tc))...] - args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] - - impls = to_call_impl.(fx.trm.args, Ref(termcons(codom_theory)), Ref(codom_module)) - impl = Expr(:call, Expr(:ref, :($codom_module.$fxname), :(model.model)), _x, impls...) - JuliaFunction(;name=xname, args=args, return_type=sig.types[1], impl=impl) - end - - # TermCons for @instance macro - funs2 = map(collect(termmap(tmap))) do (x, fx) - tc = getvalue(dom_theory[x]) - - sig = julia_signature(dom_theory, x, jltype_by_sort) - argnames = nameof.(argsof(tc)) - ret_type = jltype_by_sort[AlgSort(typemap(tmap)[tc.type.head].trm.head)] - - args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] - - impl = to_call_impl(fx.trm, termcons(codom_theory), codom_module) - - JuliaFunction(;name=nameof(x), args=args, return_type=ret_type, impl=impl) - end - - funs3 = [] # accessors - for (x, fx) in pairs(typemap(tmap)) - tc = getvalue(dom_theory[x]) - eq = equations(codom_theory, fx) - args = [:($_x::$(jltype_by_sort[AlgSort(fx.trm.head)]))] - scopedict = Dict{ScopeTag,ScopeTag}(gettag(tc.localcontext)=>gettag(fx.ctx)) - for accessor in idents(tc.localcontext; lid=tc.args) - accessor = retag(scopedict, accessor) - a = nameof(accessor) - # If we have a default means of computing the accessor... - if !isempty(eq[accessor]) - rtype = tc.localcontext[ident(tc.localcontext; name=a)] - ret_type = jltype_by_sort[AlgSort(getvalue(rtype))] - impl = to_call_impl(first(eq[accessor]), _x, codom_module) - jf = JuliaFunction(;name=a, args=args, return_type=ret_type, impl=impl) - push!(funs3, jf) - end - end - end - - model_expr = Expr( - :curly, - GlobalRef(Syntax.TheoryInterface, :Model), - Expr(:curly, :Tuple, dom_types...) - ) - - quote - struct $(esc(name)) <: $model_expr - model :: $(esc(modelname)) - end - - @instance $dom_module [model :: $(esc(name))] begin - $(generate_function.([funs...,funs2..., funs3...])...) - end - end -end - -""" -Compile an AlgTerm into a Julia call Expr where termcons (e.g. `f`) are -interpreted as `mod.f[model.model](...)`. -""" -function to_call_impl(t::AlgTerm, termcons, mod::Module) - args = to_call_impl.(t.args, Ref(termcons), Ref(mod)) - name = nameof(headof(t)) - if t.head in termcons - Expr(:call, Expr(:ref, :($mod.$name), :(model.model)), args...) - else - isempty(args) || error("Bad term $t (termcons=$termcons)") - name - end -end - -function to_call_impl(t::GATs.AccessorApplication, x::Symbol, mod::Module) - rest = t.to isa Ident ? x : to_call_impl(t.to, x, mod) - Expr(:call, Expr(:ref, :($mod.$(nameof(t.accessor))), :(model.model)), rest) -end +# """ +# Given a Theory Morphism T->U and a type Mᵤ (whose values are models of U), +# obtain a type Mₜ which has one parameter (of type Mᵤ) and is a model of T. + +# E.g. given NatIsMonoid: ThMonoid->ThNatPlus and IntPlus <: Model{Tuple{Int}} +# and IntPlus implements ThNatPlus: + +# ``` +# @migrate IntPlusMonoid = NatIsMonoid(IntPlus){Int} +# ``` + +# Yields: + +# ``` +# struct IntPlusMonoid <: Model{Tuple{Int}} +# model::IntPlus +# end + +# @instance ThMonoid{Int} [model::IntPlusMonoid] begin ... end +# ``` + +# Future work: There is some subtlety in how accessor functions should be handled. +# TODO: The new instance methods do not yet handle the `context` keyword argument. +# """ +# macro migrate(head) +# # Parse +# (name, mapname, modelname) = @match head begin +# Expr(:(=), name, Expr(:call, mapname, modelname)) => +# (name, mapname, modelname) +# _ => error("could not parse head of @theory: $head") +# end +# codom_types = :(only(supertype($(esc(modelname))).parameters).types) +# # Unpack +# tmap = macroexpand(__module__, :($mapname.@map)) +# dom_module = macroexpand(__module__, :($mapname.@dom)) +# codom_module = macroexpand(__module__, :($mapname.@codom)) +# dom_theory, codom_theory = TheoryMaps.dom(tmap), TheoryMaps.codom(tmap) + +# codom_jltype_by_sort = Dict{Ident,Expr0}(map(enumerate(sorts(codom_theory))) do (i,v) +# v.ref => Expr(:ref, codom_types, i) +# end) +# _x = gensym("val") + +# dom_types = map(sorts(dom_theory)) do s +# codom_jltype_by_sort[typemap(tmap)[s.ref].trm.head] +# end +# jltype_by_sort = Dict(zip(sorts(dom_theory), dom_types)) + +# # TypeCons for @instance macro +# funs = map(collect(typemap(tmap))) do (x, fx) +# xname = nameof(x) +# fxname = nameof(fx.trm.head) +# tc = getvalue(dom_theory[x]) +# jltype_by_sort[AlgSort(fx.trm.head)] = jltype_by_sort[AlgSort(x)] +# sig = julia_signature(dom_theory, x, jltype_by_sort) + +# argnames = [_x, nameof.(argsof(tc))...] +# args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] + +# impls = to_call_impl.(fx.trm.args, Ref(termcons(codom_theory)), Ref(codom_module)) +# impl = Expr(:call, Expr(:ref, :($codom_module.$fxname), :(model.model)), _x, impls...) +# JuliaFunction(;name=xname, args=args, return_type=sig.types[1], impl=impl) +# end + +# # TermCons for @instance macro +# funs2 = map(collect(termmap(tmap))) do (x, fx) +# tc = getvalue(dom_theory[x]) + +# sig = julia_signature(dom_theory, x, jltype_by_sort) +# argnames = nameof.(argsof(tc)) +# ret_type = jltype_by_sort[AlgSort(typemap(tmap)[tc.type.head].trm.head)] + +# args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] + +# impl = to_call_impl(fx.trm, termcons(codom_theory), codom_module) + +# JuliaFunction(;name=nameof(x), args=args, return_type=ret_type, impl=impl) +# end + +# funs3 = [] # accessors +# for (x, fx) in pairs(typemap(tmap)) +# tc = getvalue(dom_theory[x]) +# eq = equations(codom_theory, fx) +# args = [:($_x::$(jltype_by_sort[AlgSort(fx.trm.head)]))] +# scopedict = Dict{ScopeTag,ScopeTag}(gettag(tc.localcontext)=>gettag(fx.ctx)) +# for accessor in idents(tc.localcontext; lid=tc.args) +# accessor = retag(scopedict, accessor) +# a = nameof(accessor) +# # If we have a default means of computing the accessor... +# if !isempty(eq[accessor]) +# rtype = tc.localcontext[ident(tc.localcontext; name=a)] +# ret_type = jltype_by_sort[AlgSort(getvalue(rtype))] +# impl = to_call_impl(first(eq[accessor]), _x, codom_module) +# jf = JuliaFunction(;name=a, args=args, return_type=ret_type, impl=impl) +# push!(funs3, jf) +# end +# end +# end + +# model_expr = Expr( +# :curly, +# GlobalRef(Syntax.TheoryInterface, :Model), +# Expr(:curly, :Tuple, dom_types...) +# ) + +# quote +# struct $(esc(name)) <: $model_expr +# model :: $(esc(modelname)) +# end + +# @instance $dom_module [model :: $(esc(name))] begin +# $(generate_function.([funs...,funs2..., funs3...])...) +# end +# end +# end + +# """ +# Compile an AlgTerm into a Julia call Expr where termcons (e.g. `f`) are +# interpreted as `mod.f[model.model](...)`. +# """ +# function to_call_impl(t::AlgTerm, termcons, mod::Module) +# args = to_call_impl.(t.args, Ref(termcons), Ref(mod)) +# name = nameof(headof(t)) +# if t.head in termcons +# Expr(:call, Expr(:ref, :($mod.$name), :(model.model)), args...) +# else +# isempty(args) || error("Bad term $t (termcons=$termcons)") +# name +# end +# end + +# function to_call_impl(t::GATs.AccessorApplication, x::Symbol, mod::Module) +# rest = t.to isa Ident ? x : to_call_impl(t.to, x, mod) +# Expr(:call, Expr(:ref, :($mod.$(nameof(t.accessor))), :(model.model)), rest) +# end end # module diff --git a/src/models/module.jl b/src/models/module.jl index d44fc8d2..0e3ec494 100644 --- a/src/models/module.jl +++ b/src/models/module.jl @@ -3,11 +3,11 @@ module Models using Reexport include("ModelInterface.jl") -include("SymbolicModels.jl") -include("GATExprUtils.jl") +# include("SymbolicModels.jl") +# include("GATExprUtils.jl") @reexport using .ModelInterface -@reexport using .SymbolicModels -@reexport using .GATExprUtils +# @reexport using .SymbolicModels +# @reexport using .GATExprUtils end diff --git a/src/stdlib/module.jl b/src/stdlib/module.jl index 742306b3..f8dc9a6e 100644 --- a/src/stdlib/module.jl +++ b/src/stdlib/module.jl @@ -3,13 +3,13 @@ module Stdlib using Reexport include("theories/module.jl") -include("models/module.jl") -include("theorymaps/module.jl") -include("derivedmodels/module.jl") +# include("models/module.jl") +# include("theorymaps/module.jl") +# include("derivedmodels/module.jl") @reexport using .StdTheories -@reexport using .StdModels -@reexport using .StdTheoryMaps -@reexport using .StdDerivedModels +# @reexport using .StdModels +# @reexport using .StdTheoryMaps +# @reexport using .StdDerivedModels end diff --git a/src/stdlib/theories/Categories.jl b/src/stdlib/theories/Categories.jl index cb474116..34a1a329 100644 --- a/src/stdlib/theories/Categories.jl +++ b/src/stdlib/theories/Categories.jl @@ -4,67 +4,70 @@ export ThClass, ThGraph, ThLawlessCat, ThAscCat, ThCategory, ThThinCategory using ....Syntax # Category theory -@theory ThClass begin - Ob::TYPE ⊣ [] -end +################# -@doc """ ThClass +""" ThClass A Class is just a Set that doesn't worry about foundations. -""" ThClass +""" +@theory ThClass begin + Ob::TYPE ⊣ [] +end + +""" ThGraph +A graph where the edges are typed depending on dom/codom. Contains all +necessary types for the theory of categories. +""" @theory ThGraph <: ThClass begin @op (→) := Hom Hom(dom::Ob, codom::Ob)::TYPE end +""" ThLawlessCat + +The data of a category without any axioms of associativity or identities. +""" @theory ThLawlessCat <: ThGraph begin @op (⋅) := compose compose(f::(a → b), g::(b → c))::(a → c) ⊣ [(a,b,c)::Ob] end -@doc """ ThLawlessCat -The data of a category without any axioms of associativity or identities. -""" ThLawlessCat +""" ThAsCat +The theory of a category with the associative law for composition. +""" @theory ThAscCat <: ThLawlessCat begin - assoc := ((f ⋅ g) ⋅ h) == (f ⋅ (g ⋅ h)) ⊣ - [a::Ob, b::Ob, c::Ob, d::Ob, f::Hom(a,b), g::Hom(b,c), h::Hom(c,d)] + assoc := ((f ⋅ g) ⋅ h) == (f ⋅ (g ⋅ h)) :: Hom(a, d) ⊣ + [(a, b, c, d)::Ob, f::(a→b), g::(b→c), h::(c→d)] end -@doc """ ThAsCat - -The theory of a category with the associative law for composition. -""" ThAscCat +""" ThIdLawlessCat +The theory of a category without identity axioms. +""" @theory ThIdLawlessCat <: ThAscCat begin id(a::Ob)::Hom(a,a) end -@doc """ ThIdLawlessCat - -The theory of a category without identity axioms. -""" ThIdLawlessCat +""" ThCategory +The theory of a category with composition operations and associativity and identity axioms. +""" @theory ThCategory <: ThIdLawlessCat begin - idl := id(a) ⋅ f == f ⊣ [a::Ob, b::Ob, f::Hom(a,b)] - idr := f ⋅ id(b) == f ⊣ [a::Ob, b::Ob, f::Hom(a,b)] + idl := id(a) ⋅ f == f :: (a → b) ⊣ [a::Ob, b::Ob, f::Hom(a,b)] + idr := f ⋅ id(b) == f :: (a → b) ⊣ [a::Ob, b::Ob, f::Hom(a,b)] end -@doc """ ThCategory - -The theory of a category with composition operations and associativity and identity axioms. -""" ThCategory +""" ThThinCategory +The theory of a thin category meaning that if two morphisms have the same domain and codomain they are equal as morphisms. +These are equivalent to preorders. +""" @theory ThThinCategory <: ThCategory begin - thineq := f == g ⊣ [A::Ob, B::Ob, f::Hom(A,B), g::Hom(A,B)] + thineq := f == g :: Hom(a, b) ⊣ [a::Ob, b::Ob, f::Hom(a,b), g::Hom(a,b)] end -@doc """ ThThinCategory - -The theory of a thin category meaning that if two morphisms have the same domain and codomain they are equal as morphisms. -These are equivalent to preorders. -""" ThThinCategory end diff --git a/src/stdlib/theories/module.jl b/src/stdlib/theories/module.jl index 919bb813..76273ef4 100644 --- a/src/stdlib/theories/module.jl +++ b/src/stdlib/theories/module.jl @@ -3,13 +3,13 @@ module StdTheories using Reexport include("Categories.jl") -include("Algebra.jl") -include("Monoidal.jl") -include("Naturals.jl") +# include("Algebra.jl") +# include("Monoidal.jl") +# include("Naturals.jl") @reexport using .Categories -@reexport using .Algebra -@reexport using .Monoidal -@reexport using .Naturals +# @reexport using .Algebra +# @reexport using .Monoidal +# @reexport using .Naturals end diff --git a/src/syntax/ExprInterop.jl b/src/syntax/ExprInterop.jl index de6b0417..818d54ad 100644 --- a/src/syntax/ExprInterop.jl +++ b/src/syntax/ExprInterop.jl @@ -22,9 +22,8 @@ Converts a Julia Expr into type T, in a certain scope. """ function fromexpr end -function toexpr(c::Context, x::Ident; showing=false) +function toexpr(c::Context, x::Ident) if !hasident(c, x) - showing || error("Unknown ident $x in context $c") return x end tag_level = getlevel(c, gettag(x)) @@ -46,7 +45,7 @@ end const explicit_level_regex = r"^(.*)!(\d+)$" const unnamed_var_regex = r"^#(\d+)$" -function fromexpr(c::Context, e, ::Type{Ident}; sig=nothing) +function fromexpr(c::Context, e, ::Type{Ident}) e isa Ident && return e e isa Symbol || error("expected a Symbol, got: $e") s = string(e) @@ -55,16 +54,16 @@ function fromexpr(c::Context, e, ::Type{Ident}; sig=nothing) scope = getscope(c, parse(Int, m[2])) m2 = match(unnamed_var_regex, m[1]) if !isnothing(m2) - ident(scope; lid=LID(parse(Int, m2[1])), sig) + ident(scope; lid=LID(parse(Int, m2[1]))) else - ident(scope; name=Symbol(m[1]), sig) + ident(scope; name=Symbol(m[1])) end else m2 = match(unnamed_var_regex, s) if !isnothing(m2) - ident(c; lid=LID(parse(Int, m2[1])), sig, level=nscopes(c)) + ident(c; lid=LID(parse(Int, m2[1])), level=nscopes(c)) else - ident(c; name=e, sig) + ident(c; name=e) end end end diff --git a/src/syntax/GATs.jl b/src/syntax/GATs.jl index 3bc7d233..4e099af1 100644 --- a/src/syntax/GATs.jl +++ b/src/syntax/GATs.jl @@ -1,9 +1,11 @@ module GATs export Constant, AlgTerm, AlgType, TypeScope, TypeCtx, AlgSort, AlgSorts, - AlgTermConstructor, AlgTypeConstructor, AlgAxiom, sortsignature, - JudgmentBinding, GATSegment, GAT, sortcheck, allnames, sorts, sortname, - termcons, typecons, accessors, equations, build_infer_expr, compile, + AlgDeclaration, AlgTermConstructor, AlgTypeConstructor, AlgAccessor, AlgAxiom, + sortsignature, getdecl, + GATSegment, GAT, GATContext, allmethods, + termcons, typecons, accessors, + equations, build_infer_expr, compile, sortcheck, allnames, sorts, sortname, InCtx, TermInCtx, TypeInCtx, headof, argsof, argcontext using ..Scopes @@ -14,879 +16,10 @@ import ..Scopes: retag, rename using StructEquality using MLStyle -# TODO: `toexpr` and `fromexpr` from ExprInterop should be defined after each -# term here, and `ExprInterop` should be loaded before this. - -# GAT ASTs -########## - -""" -We need this to resolve a mutual reference loop; the only subtype is Constant -""" -abstract type AbstractConstant end -abstract type TrmTyp end # AlgTerm or AlgType - -""" -`AlgTerm` - -One syntax tree to rule all the terms. -Head can be a reference to an AlgTermConstructor, to a Binding{AlgType, Nothing}, or simply an AbstractConstant -""" -@struct_hash_equal struct AlgTerm <: TrmTyp - head::Union{Ident, AbstractConstant} - args::Vector{AlgTerm} - function AlgTerm(head::Union{Ident, AbstractConstant}, args::Vector{AlgTerm}=EMPTY_ARGS) - new(head, args) - end -end - -const EMPTY_ARGS = AlgTerm[] - -fromexpr(c::Context, e, T::Type{<:TrmTyp}; kw...) = - fromexpr!(c, e, TypeScope(), T; kw...) - - -""" -`AlgType` - -One syntax tree to rule all the types. -`head` must be reference to a `AlgTypeConstructor` -""" -@struct_hash_equal struct AlgType <: TrmTyp - head::Ident - args::Vector{AlgTerm} - function AlgType(head::Ident, args::Vector{AlgTerm}=EMPTY_ARGS) - new(head, args) - end -end - -# Common code to Terms and Types -#------------------------------- -headof(t::TrmTyp) = t.head -argsof(t::TrmTyp) = t.args - -rename(tag::ScopeTag, reps::Dict{Symbol,Symbol}, t::T) where T<:TrmTyp = - T(rename(tag, reps, headof(t)), AlgTerm[rename.(Ref(tag), Ref(reps), argsof(t))...]) - -function Base.show(io::IO, type::T) where T<:TrmTyp - print(io, "$(nameof(T))(") - print(io, toexpr(TypeScope(), type; showing=true)) - print(io, ")") -end - -retag(replacements::Dict{ScopeTag,ScopeTag}, t::T) where {T<:TrmTyp} = - T(retag(replacements, t.head), AlgTerm[retag.(Ref(replacements), t.args)...]) - -""" -`Constant` - -A Julia value in an algebraic context. Checked elsewhere. -""" -@struct_hash_equal struct Constant <: AbstractConstant - value::Any - type::AlgType -end - - -""" -`AlgSort` - -A *sort*, which is essentially a type constructor without arguments -`ref` must be reference to a `AlgTypeConstructor` -""" -@struct_hash_equal struct AlgSort - ref::Ident -end - -AlgSort(t::AlgType) = AlgSort(t.head) - -function AlgSort(c::Context, t::AlgTerm) - if t.head isa AbstractConstant - AlgSort(t.head.type.head) - else - binding = c[t.head] - value = getvalue(binding) - if value isa AlgType - AlgSort(value.head) - elseif value isa AlgTermConstructor - AlgSort(value.type.head) - else - error("head of AlgTerm $value is not a term constructor or variable") - end - end -end - -""" -`TypeScope` - -A scope where variables are assigned to `AlgType`s, and no overloading is -permitted. -""" -const TypeScope = Scope{AlgType, Nothing} -const TypeCtx = Context{AlgType, Nothing} - - -# These methods belong above but have `TypeScope` as an argtype -""" -Some expr may have already been bound (e.g. by an explicit context) and thus -oughtn't be interpreted as `default` type. - -In some contexts, we want to interpret ϕ(x::T) as a constant x of type T and -in others (where constants are not permitted, such as the LHS of a @theorymap -or in an @theory term constructor) where it can be convenient to annotate -argument types directly rather than place them in an explicit context. -""" -function fromexpr!(c::Context, e, bound::TypeScope, ::Type{AlgTerm}; constants=true) - @match e begin - s::Symbol => begin - c′ = AppendScope(c, bound) - scope = getscope(c′, getlevel(c′, s)) - ref = if sigtype(scope) == Union{Nothing, AlgSorts} - fromexpr(c′, s, Ident; sig=AlgSort[]) - else - fromexpr(c′, s, Ident) - end - AlgTerm(ref) - end - Expr(:call, head, argexprs...) => begin - args = Vector{AlgTerm}(fromexpr!.(Ref(c), argexprs, Ref(bound), Ref(AlgTerm); constants)) - argsorts = AlgSorts(AlgSort.(Ref(AppendScope(c,bound)), args)) - AlgTerm(fromexpr(AppendScope(c,bound), head, Ident; sig=argsorts), args) - end - Expr(:(::), val, type) => begin - algtype = fromexpr!(c, type, bound, AlgType) - if constants - AlgTerm(Constant(val, algtype)) - else - Scopes.unsafe_pushbinding!(bound, Binding{AlgType, Nothing}(val, algtype)) - AlgTerm(Ident(gettag(bound), LID(length(bound)), val)) - end - end - e::Expr => error("could not parse AlgTerm from $e") - constant::Constant => AlgTerm(constant) - end -end - -""" -Some expr may have already been bound (e.g. by an explicit context) and thus -oughtn't be interpreted as `default` type. -""" -function fromexpr!(c::Context, e, bound::TypeScope, ::Type{AlgType}; kw...)::AlgType - bound = isnothing(bound) ? Dict{Symbol, AlgType}() : bound - @match e begin - s::Symbol => AlgType(fromexpr(c, s, Ident)) - Expr(:call, head, args...) => - AlgType(fromexpr(c, head, Ident), - Vector{AlgTerm}(fromexpr!.(Ref(c), args, Ref(bound), Ref(AlgTerm); kw...))) - _ => error("could not parse AlgType from $e") - end -end - -""" -`SortScope` - -A scope where variables are assigned to `AlgSorts`s, and no overloading is -permitted. -""" -const SortScope = Scope{AlgSort, Nothing} - -""" -A type or term with an accompanying type scope, e.g. - - (a,b)::R (a,b)::Ob ------------ or ---------- - a*(a+b) Hom(a,b) -""" -@struct_hash_equal struct InCtx{T<:TrmTyp} - ctx::TypeCtx - trm::T -end - -const TermInCtx = InCtx{AlgTerm} -const TypeInCtx = InCtx{AlgType} - -""" -`sortcheck(ctx::Context, t::AlgTerm)` - -Throw an error if a the head of an AlgTerm (which refers to a term constructor) -has arguments of the wrong sort. Returns the sort of the term. -""" -function sortcheck(ctx::Context, t::AlgTerm)::AlgSort - if t.head isa Ident - judgment = ctx[t.head] |> getvalue - if judgment isa AlgType - isempty(t.args) || error("Cannot apply a variable to arguments: $t") - elseif judgment isa AlgTermConstructor - argsorts = sortcheck.(Ref(ctx), t.args) - argsorts == sortsignature(judgment) || error("Sorts don't match") - else - error("Unexpected judgment type $ref for AlgTerm $t") - end - end - return AlgSort(ctx, t) -end - - -sortcheck(ctx::Context, t::TermInCtx)::AlgSort = - sortcheck(AppendScope(ctx, t.ctx), t.trm) - -""" -`sortcheck(ctx::Context, t::AlgType)` - -Throw an error if a the head of an AlgType (which refers to a type constructor) -has arguments of the wrong sort. -""" -function sortcheck(ctx::Context, t::AlgType) - ref = ctx[t.head] |> getvalue - ref isa AlgTypeConstructor || error("AlgType head must refer to AlgTypeConstructor: $ref") - argsorts = sortcheck.(Ref(ctx), t.args) - expected = AlgSort.([a.head for a in getvalue.(argsof(ref))]) - argsorts == expected || error("Sorts don't match: $argsorts != $expected") -end - - -abstract type TrmTypConstructor <: HasScope{AlgType, Nothing} end -argsof(t::TrmTypConstructor) = t[t.args] -Scopes.getscope(t::TrmTypConstructor) = t.localcontext - -""" -`AlgTypeConstructor` - -A declaration of a type constructor -""" -@struct_hash_equal struct AlgTypeConstructor <: TrmTypConstructor - localcontext::TypeScope - args::Vector{LID} -end - -""" -`AlgTermConstructor` - -A declaration of a term constructor -""" -@struct_hash_equal struct AlgTermConstructor <: TrmTypConstructor - localcontext::TypeScope - args::Vector{LID} - type::AlgType -end - - -sortsignature(tc::Union{AlgTypeConstructor, AlgTermConstructor}) = - AlgSort.(headof.(getvalue.(argsof(tc)))) - -""" -`AlgAxiom` - -A declaration of an axiom -""" -@struct_hash_equal struct AlgAxiom <: HasScope{AlgType, Nothing} - localcontext::TypeScope - type::AlgType - equands::Vector{AlgTerm} -end -Scopes.getscope(t::AlgAxiom) = t.localcontext - -""" -`Judgment` - -A judgment is either a type constructor, term constructor, or axiom; a GAT -is composed of a list of judgments. -""" -const Judgment = Union{AlgTypeConstructor, AlgTermConstructor, AlgAxiom} - -""" -`AlgSorts` - -A description of the argument sorts for a term constructor, used to disambiguate -multiple term constructors of the same name. -""" -const AlgSorts = Vector{AlgSort} - -""" -`JudgmentBinding` - -A binding of a judgment to a name and possibly a signature. -""" -const JudgmentBinding = Binding{Judgment, Union{AlgSorts, Nothing}} - -""" -`GATSegment` - -A piece of a GAT, consisting of a scope that binds judgments to names, possibly -disambiguated by argument sorts. -""" -const GATSegment = Scope{Judgment, Union{AlgSorts, Nothing}} - -function allnames(seg::GATSegment; aliases=false) - names = Symbol[] - for binding in seg - judgment = getvalue(binding) - if judgment isa AlgTermConstructor - push!(names, nameof(binding)) - elseif judgment isa AlgTypeConstructor - push!(names, nameof(binding)) - for argbinding in argsof(judgment) - push!(names, nameof(argbinding)) - end - end - end - if aliases - append!(names, keys(seg.primary)) - end - names -end - -""" -`GAT` - -A generalized algebraic theory. Essentially, just consists of a name and a list -of `GATSegment`s, but there is also some caching to make access faster. -Specifically, there is a dictionary to map ScopeTag to position in the list of -segments, and there are lists of all of the identifiers for term constructors, -type constructors, and axioms so that they can be iterated through faster. - -GATs allow overloading but not shadowing. -""" -struct GAT <: HasScopeList{Judgment, Union{AlgSorts, Nothing}} - name::Symbol - segments::ScopeList{Judgment, Union{AlgSorts, Nothing}} - termcons::Vector{Ident} - typecons::Vector{Ident} - accessors::Dict{Symbol, Set{Ident}} - axioms::Vector{Ident} - function GAT(name::Symbol, segments::Vector{GATSegment}) - termcons = Ident[] - typecons = Ident[] - axioms = Ident[] - # Maps a name of an accessor to the type constructors it appears in - accessors = Dict{Symbol, Set{Ident}}() - names = Set{Symbol}() - for segment in segments - if !isempty(intersect(keys(segment.names), names)) - error("We do not permit shadowing of names between segments of a GAT") - end - union!(names, keys(segment.names)) - for (x, judgment) in identvalues(segment) - if judgment isa AlgTermConstructor - push!(termcons, x) - elseif judgment isa AlgTypeConstructor - push!(typecons, x) - for arg in argsof(judgment) - if nameof(arg) ∉ keys(accessors) - accessors[nameof(arg)] = Set{Ident}() - end - push!(accessors[nameof(arg)], x) - end - else - push!(axioms, x) - end - end - end - if !isempty(intersect(keys(accessors), names)) - error("We do not permit the arguments to type constructors to have the same name as term constructors or type constructors") - end - new(name, ScopeList(segments), termcons, typecons, accessors, axioms) - end - - # This could be faster, but it's not really worth worrying about - function GAT(name::Symbol, parent::GAT, newsegment::GATSegment) - GAT(name, [parent.segments.scopes..., newsegment]) - end -end - -Base.nameof(theory::GAT) = theory.name -termcons(theory::GAT) = theory.termcons -typecons(theory::GAT) = theory.typecons -accessors(theory::GAT) = theory.accessors -sorts(theory::GAT) = AlgSort.(theory.typecons) - -Scopes.getscopelist(c::GAT) = c.segments - -function allnames(theory::GAT; aliases=false) - vcat(allnames.(theory.segments.scopes; aliases)...) -end - -function sortname(theory::GAT, type::AlgType) - canonicalize(theory, type.head) -end - -Base.issubset(t1::GAT, t2::GAT) = - all(s->hastag(t2, s), gettag.(Scopes.getscopelist(t1).scopes)) - -## Equations - -struct AccessorApplication - accessor::Ident - to::Union{Ident, AccessorApplication} -end - -const InferExpr = Union{Ident, AccessorApplication} - -function build_infer_expr(e::InferExpr; theorymodule=nothing) - if e isa Ident - nameof(e) - else - name = nameof(e.accessor) - accessor = if !isnothing(theorymodule) - :($theorymodule.$name) - else - esc(name) - end - Expr(:call, accessor, build_infer_expr(e.to; theorymodule)) - end -end - -""" Implicit equations defined by a context. - -This function allows a generalized algebraic theory (GAT) to be expressed as -an essentially algebraic theory, i.e., as partial functions whose domains are -defined by equations. - -References: - - (Cartmell, 1986, Sec 6: "Essentially algebraic theories and categories with - finite limits") - - (Freyd, 1972, "Aspects of topoi") - -This function gives expressions for computing each of the elements of `context` - from the `args`, as well as checking that the args are well-typed. - -Example: -> equations({f::Hom(a,b), g::Hom(b,c)}, {a::Ob, b::Ob, c::Ob}, ThCategory) -ways_of_computing = Dict(a => [dom(f)], b => [codom(f), dom(g)], c => [codom(g)], - f => [f], g => [g]) - -Algorithm: - -Start from the arguments. We know how to compute each of the arguments; they are -given. Each argument tells us how to compute other arguments, and also elements -of the context -""" -function equations(context::TypeCtx, args::AbstractVector{Ident}, theory::Context; init=nothing) - ways_of_computing = Dict{Ident, Set{InferExpr}}() - to_expand = Pair{Ident, InferExpr}[x => x for x in args] - if !isnothing(init) - append!(to_expand, pairs(init)) - end - - while !isempty(to_expand) - x, expr = pop!(to_expand) - if !haskey(ways_of_computing, x) - ways_of_computing[x] = Set{InferExpr}() - end - push!(ways_of_computing[x], expr) - - xtype = getvalue(context[x]) - xtypecon = getvalue(theory[xtype.head]) - - for (i, arg) in enumerate(xtype.args) - if arg.head isa Constant - continue - elseif arg.head ∈ theory - continue - else - @assert arg.head ∈ context - a = ident(xtypecon; lid=LID(i)) - y = arg.head - expr′ = AccessorApplication(a, expr) - push!(to_expand, y => expr′) - end - end - end - ways_of_computing -end - -function equations(theory::GAT, t::TypeInCtx) - tc = getvalue(theory[headof(t.trm)]) - extended = ScopeList([t.ctx, Scope([Binding{AlgType, Nothing}(nothing, t.trm)])]) - lastx = last(getidents(extended)) - accessor_args = zip(idents(tc.localcontext; lid=tc.args), t.trm.args) - init = Dict{Ident, InferExpr}(map(accessor_args) do (accessor, arg) - hasident(t.ctx, headof(arg)) || error("Case not yet handled") - headof(arg) => AccessorApplication(accessor, lastx) - end) - equations(extended, Ident[], theory; init=init) -end - -"""Get equations for a term or type constructor""" -equations(theory::Context, x::Ident) = let x = getvalue(theory[x]); - equations(x, idents(x; lid=x.args),theory) -end - - - -function compile(expr_lookup::Dict{Ident}, term::AlgTerm; theorymodule=nothing) - if term.head isa Constant - term.head.value - else - if haskey(expr_lookup, term.head) - expr_lookup[term.head] - else - name = nameof(term.head) - fun = if !isnothing(theorymodule) - :($theorymodule.$name) - else - esc(name) - end - Expr(:call, fun, [compile(expr_lookup, arg; theorymodule) for arg in term.args]...) - end - end -end - -InCtx(g::GAT, k::Ident) = - (getvalue(g[k]) isa AlgTermConstructor ? TermInCtx : TypeInCtx)(g, k) - -""" -Get the canonical term + ctx associated with a term constructor. -""" -function InCtx{AlgTerm}(g::GAT, k::Ident) - tcon = getvalue(g[k]) - TermInCtx(tcon.localcontext, AlgTerm(k, AlgTerm.(idents(tcon; lid=tcon.args)))) -end - -""" -Get the canonical type + ctx associated with a type constructor. -""" -function InCtx{AlgType}(g::GAT, k::Ident) - tcon = getvalue(g[k]) - TypeInCtx(tcon.localcontext, AlgType(k, AlgTerm.(idents(tcon; lid=tcon.args)))) -end - - -""" -Infer the type of the term of a term. If it is not in context, recurse on its -arguments. The term constructor's output type yields the resulting type once -its localcontext variables are substituted with the relevant AlgTerms. - - (x,y,z)::Ob, p::Hom(x,y), q::Hom(y,z) -E.g. given -------------------------------------- - id(x)⋅(p⋅q) - - (a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c) -and output type: ------------------------------------ - Hom(a,c) - -We first recursively find `{id(x) => Hom(x,x), p⋅q => Hom(x,z)}`. We ultimately -want an AlgTerm for everything in the output type's context such that we can -substitute into `Hom(a,c)` to get the final answer. It will help to also compute -the AlgType for everything in the context. We work backwards, since we start by -knowing `{f => id(x)::Hom(x,x), g=> p⋅q :: Hom(x,z)}`. For `a` `b` and `c`, -we use `equations` which tell us, e.g., that `a = dom(f)`. So we can grab the -first argument of the *type* of `f` (i.e. grab `x` from `Hom(x,x)`). -""" -function infer_type(ctx::Context, t::AlgTerm) - head = headof(t) - tc = getvalue(ctx[head]) - if tc isa AlgType - tc # base case - else - typed_terms = bind_localctx(ctx, t) - AlgType(headof(tc.type), substitute_term.(argsof(tc.type), Ref(typed_terms))) - end -end - -infer_type(ctx::Context, t::TermInCtx) = infer_type(AppendScope(ctx, t.ctx), t.trm) - -""" -Take a term constructor and determine terms of its local context. - -This function is mutually recursive with `infer_type`. -""" -function bind_localctx(ctx::Context, t::TrmTyp) - head = headof(t) - - tc = getvalue(ctx[head]) - eqs = equations(ctx, head) - - typed_terms = Dict{Ident, Pair{AlgTerm,AlgType}}() - for (i,a) in zip(tc.args, t.args) - tt = (a => infer_type(ctx, a)) - typed_terms[ident(tc, lid=i)] = tt - end - - for lc_arg in reverse(getidents(tc)) - if getlid(lc_arg) ∈ tc.args - continue - end - # one way of determining lc_arg's value - filt(e) = e isa AccessorApplication && e.to isa Ident - app = first(filter(filt, eqs[lc_arg])) - inferred_term = typed_terms[app.to][2].args[app.accessor.lid.val] - inferred_type = infer_type(ctx, inferred_term) - typed_terms[lc_arg] = inferred_term => inferred_type - end - - Dict([k=>v[1] for (k,v) in pairs(typed_terms)]) -end - -bind_localctx(ctx::Context, t::InCtx) = bind_localctx(AppendScope(ctx, t.ctx), t.trm) - -""" Replace idents with AlgTerms. """ -function substitute_term(t::T, dic::Dict{Ident,AlgTerm}) where T<:TrmTyp - x = headof(t) - if haskey(dic, x) - isempty(t.args) || error("Cannot substitute a term with arguments") - dic[x] - else - T(headof(t), substitute_term.(argsof(t), Ref(dic))) - end -end - -# ExprInterop -############# - -function toexpr(c::Context, term::T; kw...) where {T<:TrmTyp} - if term.head isa Constant - toexpr(c, term.head; kw...) - else - if isempty(term.args) - toexpr(c, term.head; kw...) - else - Expr(:call, toexpr(c, term.head; kw...), toexpr.(Ref(c), term.args; kw...)...) - end - end -end - -toexpr(c::Context, constant::Constant; kw...) = - Expr(:(::), constant.value, toexpr(c, constant.type; kw...)) - - -function bindingexprs(c::Context, s::Scope) - c′ = AppendScope(c, s) - [Expr(:(::), nameof(b), toexpr(c′, getvalue(b))) for b in s] -end - -function toexpr(c::Context, binding::JudgmentBinding) - judgment = getvalue(binding) - name = nameof(binding) - c′ = AppendScope(c, judgment.localcontext) - head = if judgment isa Union{AlgTypeConstructor, AlgTermConstructor} - if !isempty(judgment.args) - Expr(:call, name, nameof.(argsof(judgment))...) - else - name - end - else - Expr(:call, :(==), toexpr(c′, judgment.equands[1]), toexpr(c′, judgment.equands[2])) - end - headtyped = Expr(:(::), head, judgment isa AlgTypeConstructor ? :TYPE : toexpr(c′, judgment.type)) - if !isempty(judgment.localcontext) - Expr(:call, :(⊣), headtyped, Expr(:vect, bindingexprs(c, judgment.localcontext)...)) - else - headtyped - end -end - -""" -Return `nothing` if the binding we parse has already been bound. -""" -function fromexpr(c::Context, e, ::Type{Binding{AlgType, Nothing}}) - @match e begin - Expr(:(::), name::Symbol, type_expr) => - Binding{AlgType, Nothing}(name, fromexpr(c, type_expr, AlgType)) - _ => error("could not parse binding of name to type from $e") - end -end - -""" -Keep track of variables already bound (e.g. in local context) so that they need -not be redefined, e.g. `compose(f,g::Hom(b,c)) ⊣ [(a,b,c)::Ob, f::Hom(a,b)]` -(If `f` were not defined in the local context, it would be parsed as `default`.) -""" -function parsetypescope(c::Context, exprs::AbstractVector) - scope = TypeScope() - c′ = AppendScope(c, scope) - line = nothing - for expr in exprs - binding_exprs = @match expr begin - a::Symbol => [Expr(:(::), a, :default)] - Expr(:tuple, names...) => [:($name :: default) for name in names] - Expr(:(::), Expr(:tuple, names...), T) => [:($name :: $T) for name in names] - :($a :: $T) => [expr] - l::LineNumberNode => begin - line = l - [] - end - _ => error("invalid binding expression $expr") - end - for binding_expr in binding_exprs - binding = fromexpr(c′, binding_expr, Binding{AlgType, Nothing}) - Scopes.unsafe_pushbinding!(scope, setline(binding, line)) - end - end - scope -end - -function parseargs!(c::Context, exprs::AbstractVector, scope::TypeScope) - c′ = AppendScope(c, scope) - map(exprs) do expr - binding_expr = @match expr begin - a::Symbol => - if hasident(scope; name=a) - return getlid(ident(scope; name=a)) - else - Expr(:(::), a, :default) - end - :($a :: $T) => expr - _ => error("invalid argument expression $expr") - end - binding = fromexpr(c′, binding_expr, Binding{AlgType, Nothing}) - Scopes.unsafe_pushbinding!(scope, binding) - return LID(length(scope)) - end -end - - - -""" -`axiom=true` adds a `::default` to exprs like `f(a,b) ⊣ [a::A, b::B]` -""" -function normalize_decl(e) - @match e begin - :($name := $lhs == $rhs :: $typ ⊣ $ctx) => :((($name := ($lhs == $rhs)) :: $typ) ⊣ $ctx) - :($lhs == $rhs :: $typ ⊣ $ctx) => :((($lhs == $rhs) :: $typ) ⊣ $ctx) - :(($lhs == $rhs :: $typ) ⊣ $ctx) => :((($lhs == $rhs) :: $typ) ⊣ $ctx) - :($trmcon :: $typ ⊣ $ctx) => :(($trmcon :: $typ) ⊣ $ctx) - :($name := $lhs == $rhs ⊣ $ctx) => :((($name := ($lhs == $rhs))) ⊣ $ctx) - :($lhs == $rhs ⊣ $ctx) => :(($lhs == $rhs) ⊣ $ctx) - :($(trmcon::Symbol) ⊣ $ctx) => :(($trmcon :: default) ⊣ $ctx) - :($f($(args...)) ⊣ $ctx) && if f ∉ [:(==), :(⊣)] end => :(($f($(args...)) :: default) ⊣ $ctx) - trmcon::Symbol => :($trmcon :: default) - :($f($(args...))) && if f ∉ [:(==), :(⊣)] end => :($e :: default) - _ => e - end -end - -function parseaxiom(c::Context, localcontext, type_expr, e; name=nothing) - @match e begin - Expr(:call, :(==), lhs_expr, rhs_expr) => begin - c′ = AppendScope(c, localcontext) - equands = fromexpr.(Ref(c′), [lhs_expr, rhs_expr], Ref(AlgTerm)) - type = if isnothing(type_expr) - infer_type(c′, first(equands)) - else - fromexpr(c′, type_expr, AlgType) - end - axiom = AlgAxiom(localcontext, type, equands) - JudgmentBinding(name, axiom) - end - _ => error("failed to parse equation from $e") - end -end - -""" -parse a term of the following forms: - compose(f::Hom(a,b), g::Hom(b,c)) ⊣ [(a,b,c)::Ob] - Hom(dom::Ob, codom::Ob) - -Some AlgType information may be in an explicit context, otherwise it comes from -explicitly annotated symbols. For explicit annotations to be registered as such -rather than parsed as Constants, set kwarg `constants=false`. -""" -function fromexpr(c::Context, e, ::Type{InCtx{T}}; kw...) where T - (binding, localcontext) = @match e begin - Expr(:call, :(⊣), binding, Expr(:vect, args...)) => (binding, parsetypescope(c, args)) - e => (e, TypeScope()) - end - t = fromexpr!(c, binding, localcontext, T; kw...) - InCtx{T}(localcontext, t) -end - -function toexpr(c::Context, tic::InCtx; kw...) - c′ = AppendScope(c, tic.ctx) - etrm = toexpr(c′, tic.trm; kw...) - flat = Scopes.flatten(tic.ctx) - ectx = toexpr(AppendScope(c,flat), flat; kw...) - Expr(:call, :(⊣), etrm, ectx) -end - -toexpr(c::Context, ts::TypeScope; kw...) = - Expr(:vect,[Expr(:(::), nameof(b), toexpr(c, getvalue(b); kw...)) for b in ts]...) - -function fromexpr(c::Context, e, ::Type{JudgmentBinding}) - e = normalize_decl(e) - - (binding, localcontext) = @match e begin - Expr(:call, :(⊣), binding, Expr(:vect, args...)) => (binding, parsetypescope(c, args)) - e => (e, TypeScope()) - end - - c′ = AppendScope(c, localcontext) - - (head, type_expr) = @match binding begin - Expr(:(::), head, type_expr) => (head, type_expr) - _ => (binding, nothing) - end - - @match head begin - Expr(:(:=), name, equation) => parseaxiom(c, localcontext, type_expr, equation; name) - Expr(:call, :(==), _, _) => parseaxiom(c, localcontext, type_expr, head) - _ => begin - (name, arglist) = @match head begin - Expr(:call, name, args...) => (name, args) - name::Symbol => (name, []) - _ => error("failed to parse head of term constructor $head") - end - args = parseargs!(c, arglist, localcontext) - @match type_expr begin - :TYPE => begin - typecon = AlgTypeConstructor(localcontext, args) - JudgmentBinding(name, typecon) - end - _ => begin - type = fromexpr(c′, type_expr, AlgType) - termcon = AlgTermConstructor(localcontext, args, type) - sig = sortsignature(termcon) - JudgmentBinding(name, termcon, sig) - end - end - end - end -end - -function toexpr(c::Context, seg::GATSegment) - c′ = AppendScope(c, seg) - e = Expr(:block) - for binding in seg - if !isnothing(getline(binding)) - push!(e.args, getline(binding)) - end - push!(e.args, toexpr(c′, binding)) - end - e -end - -function fromexpr(c::Context, e, ::Type{GATSegment}) - seg = GATSegment() - e.head == :block || error("expected a block to pars into a GATSegment, got: $e") - c′ = AppendScope(c, seg) - linenumber = nothing - for line in e.args - @switch line begin - @case l::LineNumberNode - (linenumber = l) - @case Expr(:macrocall, var"@op", _, aliasexpr) - lines = @match aliasexpr begin - Expr(:block, lines...) => lines - _ => [aliasexpr] - end - for line in lines - @switch line begin - @case (_::LineNumberNode) - nothing - @case :($alias := $name) - Scopes.unsafe_addalias!(seg, name, alias) - @case _ - error("could not match @op line $line") - end - end - @case Expr(:import, Expr(:(:), Expr(:(.), mod), imports)) - nothing - @case _ - binding = setline(fromexpr(c′, line, JudgmentBinding), linenumber) - Scopes.unsafe_pushbinding!(seg, binding) - end - end - seg -end +include("gats/ast.jl") +include("gats/judgments.jl") +include("gats/gat.jl") +include("gats/exprinterop.jl") +include("gats/algorithms.jl") end diff --git a/src/syntax/Scopes.jl b/src/syntax/Scopes.jl index 42becd58..f25767e3 100644 --- a/src/syntax/Scopes.jl +++ b/src/syntax/Scopes.jl @@ -10,7 +10,7 @@ export hasident, ident, getidents, idents, canonicalize, HasScope, haslid, getscope, getbindings, getbinding, identvalues, namevalues, - Scope, ScopeList, HasScopeList, AppendScope, + Scope, ScopeList, HasScopeList, AppendContext, EmptyContext using UUIDs @@ -213,11 +213,15 @@ function Base.show(io::IO, b::Binding; crayon=nothing) print(io, crayon, bname) print(io, inv(crayon)) end - print(io, " => $(repr(getvalue(b)))") + value = getvalue(b) + if value isa Alias + print(io, " = ", value.ref) + else + print(io, " => $(repr(getvalue(b)))") + end end Base.nameof(b::Binding) = b.name -aliases(b::Binding) = b.aliases function MetaUtils.setname(b::Binding{T}, name::Symbol) where {T} Binding{T}(name, b.value, b.line) @@ -235,6 +239,7 @@ getline(b::Binding) = b.line setline(b::Binding{T}, line::Union{LineNumberNode, Nothing}) where {T} = Binding{T}(b.name, b.value, line) + setvalue(b::Binding{T}, t::T) where {T} = Binding{T}(b.name, t, b.line) @@ -255,6 +260,7 @@ this list. - `hasname(c::Context, name::Symbol) -> Bool` - `getlevel(c::Context, tag::ScopeTag) -> Int` - `getlevel(c::Context, name::Symbol) -> Int` +- `alltags(c::Context) -> Set{ScopeTag}` """ abstract type Context{T} end @@ -274,6 +280,8 @@ getlevel(hc::HasContext, tag::ScopeTag) = getlevel(getcontext(hc), tag) getlevel(hc::HasContext, name::Symbol) = getlevel(getcontext(hc), name) +alltags(hc::HasContext) = alltags(getcontext(hc)) + # HasScope ########## @@ -309,6 +317,8 @@ getlevel(hs::HasScope, name::Symbol) = nscopes(hs::HasScope) = 1 +alltags(hs::HasScope) = Set([getscope(hs).tag]) + # Scopes ######## @@ -338,27 +348,10 @@ struct Scope{T} <: HasScope{T} # cached mapping names::Dict{Symbol, LID} function Scope{T}(tag, bindings, names) where {T} - check_names(bindings, names) new{T}(tag, bindings, names) end end -check_names(s::Scope) = check_names(s.bindings, s.names) - -function check_names(bindings, names) - # Check bindings don't shadow each other - namevals = Set([(nameof(b), getvalue(b)) for b in bindings]) - shadow = "Scopes do not permit shadowing: $namevals" - length(namevals) == length(bindings) || error(shadow) - - # Check names are valid - allnames = Set(filter(!isnothing, nameof.(bindings))) - extra_names = setdiff(keys(names), allnames) - missing_names = setdiff(allnames, keys(names)) - isempty(extra_names) || error("Extra names: $extra_names") - isempty(missing_names) || error("Missing names: $missing_names") -end - Base.:(==)(s1::Scope, s2::Scope) = s1.tag == s2.tag Base.hash(s::Scope, h::UInt64) = hash(s.tag, h) @@ -366,6 +359,8 @@ Base.hash(s::Scope, h::UInt64) = hash(s.tag, h) Scope{T}() where {T} = Scope{T}(newscopetag(), Binding{T}[], Dict{Symbol, LID}()) +check_names(s::Scope) = make_name_dict(s.bindings) == s.names + function make_name_dict(bindings::AbstractVector{Binding{T}}) where {T} d = Dict{Symbol, LID}() for (i, binding) in enumerate(bindings) @@ -373,35 +368,23 @@ function make_name_dict(bindings::AbstractVector{Binding{T}}) where {T} if isnothing(name) continue end + if haskey(d, name) + error("Scopes do not permit shadowing: binding $binding shadows existing binding $(bindings[d[name].val])") + end d[name] = LID(i) end d end -function make_primary_map(aliases::Dict{Symbol, Set{Symbol}}) - primary = Dict{Symbol, Symbol}() - for (k, vs) in pairs(aliases) - for v in vs - primary[v] = k - end - end - primary -end - -function Scope(bindings::Vector{Binding{T}}; - tag=newscopetag()) where {T} - Scope{T}(tag, bindings, make_name_dict(bindings), - make_primary_map(aliases)) +function Scope(bindings::Vector{Binding{T}}; tag=newscopetag()) where {T} + Scope{T}(tag, bindings, make_name_dict(bindings)) end -function Scope(pairs::Pair{Symbol, T}...; - tag=newscopetag()) where {T} - Scope( - Binding{T}[Binding{T}(x, v) for (x, v) in pairs]; tag) +function Scope(pairs::Pair{Symbol, T}...; tag=newscopetag()) where {T} + Scope(Binding{T}[Binding{T}(x, v) for (x, v) in pairs]; tag) end -function Scope{T}(pairs::Pair{Symbol, <:T}...; - tag=newscopetag()) where {T} +function Scope{T}(pairs::Pair{Symbol, <:T}...; tag=newscopetag()) where {T} Scope(Binding{T}[Binding{T}(x, v) for (x, v) in pairs]; tag) end @@ -410,9 +393,11 @@ function Scope(symbols::Symbol...; tag=newscopetag()) end retag(replacements::Dict{ScopeTag,ScopeTag}, s::Scope{T}) where {T} = - Scope{T}(get(replacements, gettag(s), gettag(s)), - retag.(Ref(replacements), s.bindings), - s.names) + Scope{T}( + get(replacements, gettag(s), gettag(s)), + retag.(Ref(replacements), s.bindings), + s.names + ) function Base.show(io::IO, s::Scope) n = length(s.bindings) @@ -438,18 +423,15 @@ getscope(s::Scope) = s Add a new binding to the end of Scope `s`. """ function unsafe_pushbinding!(s::Scope{T}, b::Binding{T}) where {T} + name = nameof(b) if haskey(s.names, nameof(b)) error("name $name already defined in scope $s") end - name = nameof(b) if !isnothing(name) - if !haskey(s.aliases, name) - s.aliases[name] = Set{Symbol}() - end s.names[name] = LID(length(s.bindings) + 1) end push!(s.bindings, b) - b + Ident(gettag(s), LID(length(s.bindings)), nameof(b)) end # HasScope utilities @@ -464,8 +446,6 @@ function hasname(hs::HasScope, name::Symbol, lid::LID) lid == s.names[name] end -hasname(hs::HasScope, name::Symbol) = haskey(getscope(hs).names, name) - hasident(hs::HasScope, x::Ident) = gettag(hs) == gettag(x) && haslid(hs, getlid(x)) && @@ -526,6 +506,14 @@ function ident( level::Union{Int, Nothing}=nothing, ) lid = getlid(hs; tag, name, lid, level) + binding = getbinding(hs, lid) + value = getvalue(binding) + if value isa Alias + return value.ref + end + if isnothing(name) + name = nameof(getbinding(hs, lid)) + end Ident(gettag(hs), lid, name) end @@ -536,10 +524,14 @@ function identvalues(hs::HasScope) end """This collects all the idents in a scope""" -function getidents(hs::HasScope) - map(enumerate(getbindings(hs))) do (i, binding) - Ident(gettag(hs), LID(i), nameof(binding)) +function getidents(hs::HasScope; aliases=true) + xs = Ident[] + for (i, binding) in enumerate(getbindings(hs)) + if aliases || !(getvalue(binding) isa Alias) + push!(xs, Ident(gettag(hs), LID(i), nameof(binding))) + end end + xs end function namevalues(hs::HasScope) @@ -595,10 +587,6 @@ function Base.in(x::Ident, s::Context) hasident(s, x) end -function canonicalize(c::Context, x::Ident) - ident(c; level=getlevel(c, gettag(x)), lid=getlid(x)) -end - """ `ident` creates an `Ident` from a context and some partial data supplied as keywords. @@ -654,7 +642,9 @@ function hasident(c::Context; kwargs...) end end -"""This is a broadcasted version of `ident`""" +""" +This is a broadcasted version of `ident` +""" function idents( c::Context; tag=Ref(nothing), @@ -691,19 +681,49 @@ struct ScopeList{T} <: HasScopeList{T} scopes::Vector{HasScope{T}} taglookup::Dict{ScopeTag, Int} namelookup::Dict{Symbol, Int} - function ScopeList(scopes::Vector{<:HasScope{T}}) where {T} - allunique(gettag.(scopes)) || error("tags of scopes in ScopeList must all be unique") - taglookup = Dict{ScopeTag, Int}() - namelookup = Dict{Symbol, Int}() - for (i, hs) in enumerate(scopes) - s = getscope(hs) - taglookup[gettag(s)] = i - for name in keys(s.names) - namelookup[name] = i # overwrite most recent - end - end - new{T}(scopes, taglookup, namelookup) +end + +function ScopeList(scopes::Vector{<:HasScope{T}}) where {T} + ScopeList{T}(scopes) +end +function ScopeList{T}(scopes::Vector{<:HasScope{T}}) where {T} + allunique(gettag.(scopes)) || error("tags of scopes in ScopeList must all be unique") + taglookup = Dict{ScopeTag, Int}() + namelookup = Dict{Symbol, Int}() + c = ScopeList{T}(scopes, taglookup, namelookup) + for (i, hs) in enumerate(scopes) + unsafe_updatecache!(c, i, hs) end + c +end + +function Base.copy(c::ScopeList{T}) where {T} + ScopeList{T}(copy(c.scopes), copy(c.taglookup), copy(c.namelookup)) +end + +function ScopeList{T}() where {T} + ScopeList{T}(HasScope{T}[]) +end + +function unsafe_pushbinding!(c::ScopeList{T}, binding::Binding{T}) where {T} + name = nameof(binding) + if !isnothing(name) + c.namelookup[name] = length(c.scopes) + end + unsafe_pushbinding!(getscope(c.scopes[end]), binding) +end + +function unsafe_updatecache!(c::ScopeList{T}, i::Int, hs::HasScope{T}) where {T} + s = getscope(hs) + c.taglookup[gettag(s)] = i + for name in keys(s.names) + c.namelookup[name] = i # overwrite most recent + end +end + +function unsafe_pushscope!(c::ScopeList{T}, scope::HasScope{T}) where {T} + push!(c.scopes, scope) + unsafe_updatecache!(c, length(c.scopes), scope) end getscopelist(c::ScopeList) = c @@ -725,7 +745,7 @@ end getscope(hsl::HasScopeList, level::Int) = - getscopelist(hsl).scopes[level] + getscope(getscopelist(hsl).scopes[level]) nscopes(hsl::HasScopeList) = length(getscopelist(hsl).scopes) @@ -742,81 +762,56 @@ hastag(hsl::HasScopeList, t::ScopeTag) = hasname(hsl::HasScopeList, name::Symbol) = haskey(getscopelist(hsl).namelookup, name) -getidents(hsl::HasScopeList; kw...) = vcat(getidents.(getscopelist(hsl))...) +getidents(hsl::HasScopeList; kw...) = vcat(getidents.(getscopelist(hsl); kw...)...) -""" -Flatten a scopelist if possible. This will fail if any of the bindings shadow -bindings in earlier scopes. -""" -function flatten(hsl::HasScopeList{T}) where {T} - if nscopes(hsl) == 0 - Scope{T}() - else - res = Scope(getbindings(deepcopy(getscope(hsl, 1)))) - newtag = gettag(res) - retagdict = Dict{ScopeTag, ScopeTag}(gettag(getscope(hsl, 1))=>newtag) - for i in 2:nscopes(hsl) - nextscope = getscope(hsl, i) - retagdict[gettag(nextscope)] = newtag - nextscope = retag(retagdict, nextscope) - for b in getbindings(nextscope) - unsafe_pushbinding!(res, b) - end - end - res - end -end +alltags(hsl::HasScopeList) = Set(gettag.(getscopelist(hsl).scopes)) -flatten(s::Scope) = s +# AppendContext +############### -# AppendScope -############# - -struct AppendScope{T₁, T₂} <: Context{Union{T₁,T₂}} - context::Context{T₁} - last::Scope{T₂} - function AppendScope(context::Context{T₁}, last::Scope{T₂}) where {T₁, T₂} - !hastag(context, gettag(last)) || error("All scopes in context must have unique tags: collision with level $(getlevel(context, gettag(last)))") - new{T₁, T₂}(context, last) - end -end - -function AppendScope(context::Context, last::ScopeList) - res = context - for scope in getscope.(Ref(last), 1:nscopes(last)) - res = AppendScope(res, scope) +struct AppendContext{T₁, T₂} <: Context{Union{T₁,T₂}} + ctx1::Context{T₁} + ctx2::Context{T₂} + function AppendContext(ctx1::Context{T₁}, ctx2::Context{T₂}) where {T₁, T₂} + isempty(intersect(alltags(ctx1), alltags(ctx2))) || + error("All scopes in context must have unique tags") + new{T₁, T₂}(ctx1, ctx2) end - res end -getscope(c::AppendScope, level::Int) = - if level == nscopes(c) - c.last +getscope(c::AppendContext, level::Int) = + if level > nscopes(c.ctx1) + getscope(c.ctx2, level - nscopes(c.ctx1)) else - getscope(c.context, level) + getscope(c.ctx1, level) end -nscopes(c::AppendScope) = nscopes(c.context) + 1 +nscopes(c::AppendContext) = nscopes(c.ctx1) + nscopes(c.ctx2) -getlevel(c::AppendScope, name::Symbol) = - if name ∈ keys(c.last.names) - nscopes(c) +getlevel(c::AppendContext, name::Symbol) = + if hasname(c.ctx2, name) + getlevel(c.ctx2, name) + nscopes(c.ctx1) else - getlevel(c.context, name) + getlevel(c.ctx1, name) end -getlevel(c::AppendScope, tag::ScopeTag) = - gettag(c.last) == tag ? nscopes(c) : getlevel(c.context, tag) +getlevel(c::AppendContext, tag::ScopeTag) = + if hastag(c.ctx2, tag) + getlevel(c.ctx2, tag) + nscopes(c.ctx1) + else + getlevel(c.ctx1, tag) + end -hasname(c::AppendScope, name::Symbol) = - hasname(c.last, name) || hasname(c.context, name) +hasname(c::AppendContext, name::Symbol) = + hasname(c.ctx2, name) || hasname(c.ctx1, name) -hastag(c::AppendScope, tag::ScopeTag) = - hastag(c.last, tag) || hastag(c.context, tag) +hastag(c::AppendContext, tag::ScopeTag) = + hastag(c.ctx2, tag) || hastag(c.ctx1, tag) -getidents(scope::AppendScope; kw...) = - vcat(getidents(scope.context; kw...), getidents(scope.last; kw...)) +getidents(c::AppendContext; kw...) = + vcat(getidents(c.ctx1; kw...), getidents(c.ctx2; kw...)) +alltags(c::AppendContext) = union(alltags(c.ctx1), alltags(c.ctx2)) # EmptyContext ############## @@ -836,4 +831,6 @@ hasname(::EmptyContext, name::Symbol) = false hastag(::EmptyContext, tag::ScopeTag) = false +alltags(::EmptyContext) = Set{ScopeTag}() + end # module diff --git a/src/syntax/TheoryInterface.jl b/src/syntax/TheoryInterface.jl index 0f730a1d..7b0308c6 100644 --- a/src/syntax/TheoryInterface.jl +++ b/src/syntax/TheoryInterface.jl @@ -49,27 +49,16 @@ macro theory(head, body) parent = if !isnothing(parentname) macroexpand(__module__, :($parentname.@theory)) else - GAT(:_EMPTY, GATSegment[]) - end - - newsegment = fromexpr(parent, body, GATSegment) - importlines = Expr[] - for line in body.args - @switch line begin - @case Expr(:import, Expr(:(:), Expr(:., mod), imports)) - push!(importlines, line) - @case _ - nothing - end + GAT(:_EMPTY) end - - theory = GAT(name, parent, newsegment) + theory = fromexpr(parent, body, GAT; name) + newsegment = theory.segments.scopes[end] modulelines = Any[] push!(modulelines, :(export $(allnames(theory; aliases=true)...))) - append!(modulelines, importlines) + if !isnothing(parentname) push!(modulelines, Expr(:using, Expr(:(.), :(.), :(.), parentname))) end @@ -79,22 +68,14 @@ macro theory(head, body) push!(modulelines, :(macro theory() $theory end)) push!(modulelines, :(macro theory_module() @__MODULE__ end)) - for name in Set(allnames(newsegment)) - # TODO: also push an automatically generated docstring - push!( - modulelines, - quote - function $name end - - function Base.getindex(::typeof($name), m::$(GlobalRef(TheoryInterface, :Model))) - (args...; context=nothing) -> $name($(GlobalRef(TheoryInterface, :WithModel))(m), args...; context) - end - end - ) - end - - for (alias, name) in pairs(newsegment.primary) - push!(modulelines, :(const $alias = $name)) + for binding in newsegment + judgment = getvalue(binding) + bname = nameof(binding) + if judgment isa AlgDeclaration + push!(modulelines, juliadeclaration(bname, judgment)) + elseif judgment isa Alias + push!(modulelines, :(const $bname = $(nameof(judgment.ref)))) + end end push!(modulelines, :($(GlobalRef(TheoryInterface, :GAT_MODULE_LOOKUP))[$(gettag(newsegment))] = $name)) @@ -112,6 +93,27 @@ macro theory(head, body) ) end +function juliadeclaration(name::Symbol, judgment::AlgDeclaration) + decl = if isnothing(judgment.overloads) + :(function $name end) + else + Expr(:import, + Expr( + :(:), + Expr(:(.), judgment.overloads[1:end-1]...), + Expr(:as, Expr(:(.), judgment.overloads[end]), name) + ) + ) + end + quote + $decl + + function Base.getindex(::typeof($name), m::$(GlobalRef(TheoryInterface, :Model))) + (args...; context=nothing) -> $name($(GlobalRef(TheoryInterface, :WithModel))(m), args...; context) + end + end +end + function invoke_term(theory_module, types, name, args; model=nothing) theory = theory_module.THEORY method = getproperty(theory_module, name) diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl new file mode 100644 index 00000000..7e477151 --- /dev/null +++ b/src/syntax/gats/algorithms.jl @@ -0,0 +1,235 @@ +""" +`sortcheck(ctx::Context, t::AlgTerm)` + +Throw an error if a the head of an AlgTerm (which refers to a term constructor) +has arguments of the wrong sort. Returns the sort of the term. +""" +function sortcheck(ctx::Context, t::AlgTerm)::AlgSort + if isapp(t) + judgment = ctx[t.body.method] |> getvalue + if judgment isa AlgTermConstructor + argsorts = sortcheck.(Ref(ctx), t.body.args) + argsorts == sortsignature(judgment) || error("Sorts don't match") + AlgSort(judgment.type) + end + elseif isvariable(t) + type = ctx[t.body] |> getvalue + AlgSort(type) + else + AlgSort(t.body.type) + end +end + +# sortcheck(ctx::Context, t::TermInCtx)::AlgSort = +# sortcheck(AppendScope(ctx, t.ctx), t.trm) + +""" +`sortcheck(ctx::Context, t::AlgType)` + +Throw an error if a the head of an AlgType (which refers to a type constructor) +has arguments of the wrong sort. +""" +function sortcheck(ctx::Context, t::AlgType) + judgment = ctx[t.body.method] |> getvalue + judgment isa AlgTypeConstructor || error("AlgType method must refer to AlgTypeConstructor: $judgment") + argsorts = sortcheck.(Ref(ctx), t.body.args) + expected = AlgSort.(getvalue.(argsof(judgment))) + argsorts == expected || error("Sorts don't match: $argsorts != $expected") +end + +# Equations + +""" Implicit equations defined by a context. + +This function allows a generalized algebraic theory (GAT) to be expressed as +an essentially algebraic theory, i.e., as partial functions whose domains are +defined by equations. + +References: + - (Cartmell, 1986, Sec 6: "Essentially algebraic theories and categories with + finite limits") + - (Freyd, 1972, "Aspects of topoi") + +This function gives expressions for computing each of the elements of `context` + from the `args`, as well as checking that the args are well-typed. + +Example: +> equations({f::Hom(a,b), g::Hom(b,c)}, {a::Ob, b::Ob, c::Ob}, ThCategory) +ways_of_computing = Dict(a => [dom(f)], b => [codom(f), dom(g)], c => [codom(g)], + f => [f], g => [g]) + +Algorithm: + +Start from the arguments. We know how to compute each of the arguments; they are +given. Each argument tells us how to compute other arguments, and also elements +of the context +""" +function equations(context::TypeCtx, args::AbstractVector{Ident}, theory::Context; init=nothing) + ways_of_computing = Dict{Ident, Set{AlgTerm}}() + to_expand = Pair{Ident, AlgTerm}[x => x for x in args] + if !isnothing(init) + append!(to_expand, pairs(init)) + end + + while !isempty(to_expand) + x, expr = pop!(to_expand) + if !haskey(ways_of_computing, x) + ways_of_computing[x] = Set{AlgTerm}() + end + push!(ways_of_computing[x], expr) + + xtype = getvalue(context[x]) + xtypecon = getvalue(theory[xtype.head]) + + for (i, arg) in enumerate(xtype.args) + if arg.head isa Constant + continue + elseif arg.head ∈ theory + continue + else + @assert arg.head ∈ context + a = ident(xtypecon; lid=LID(i)) + y = arg.head + expr′ = AccessorApplication(a, expr) + push!(to_expand, y => expr′) + end + end + end + ways_of_computing +end + +function equations(theory::GAT, t::TypeInCtx) + tc = getvalue(theory[headof(t.trm)]) + extended = ScopeList([t.ctx, Scope([Binding{AlgType, Nothing}(nothing, t.trm)])]) + lastx = last(getidents(extended)) + accessor_args = zip(idents(tc.localcontext; lid=tc.args), t.trm.args) + init = Dict{Ident, AlgTerm}(map(accessor_args) do (accessor, arg) + hasident(t.ctx, headof(arg)) || error("Case not yet handled") + headof(arg) => AccessorApplication(accessor, lastx) + end) + equations(extended, Ident[], theory; init=init) +end + +"""Get equations for a term or type constructor""" +equations(theory::Context, x::Ident) = let x = getvalue(theory[x]); + equations(x, idents(x; lid=x.args),theory) +end + +function compile(expr_lookup::Dict{Ident}, term::AlgTerm; theorymodule=nothing) + if isapp(term) + name = nameof(term.body.head) + fun = if !isnothing(theorymodule) + :($theorymodule.$name) + else + esc(name) + end + Expr(:call, fun, [compile(expr_lookup, arg; theorymodule) for arg in term.args]...) + elseif isvariable(term) + expr_lookup[term.body] + elseif isconstant(term) + term.body.value + end +end + +InCtx(g::GAT, k::Ident) = + (getvalue(g[k]) isa AlgTermConstructor ? TermInCtx : TypeInCtx)(g, k) + +""" +Get the canonical term + ctx associated with a term constructor. +""" +function InCtx{AlgTerm}(g::GAT, k::Ident) + tcon = getvalue(g[k]) + TermInCtx(tcon.localcontext, AlgTerm(k, AlgTerm.(idents(tcon; lid=tcon.args)))) +end + +""" +Get the canonical type + ctx associated with a type constructor. +""" +function InCtx{AlgType}(g::GAT, k::Ident) + tcon = getvalue(g[k]) + TypeInCtx(tcon.localcontext, AlgType(k, AlgTerm.(idents(tcon; lid=tcon.args)))) +end + + +""" +Infer the type of the term of a term. If it is not in context, recurse on its +arguments. The term constructor's output type yields the resulting type once +its localcontext variables are substituted with the relevant AlgTerms. + + (x,y,z)::Ob, p::Hom(x,y), q::Hom(y,z) +E.g. given -------------------------------------- + id(x)⋅(p⋅q) + + (a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c) +and output type: ------------------------------------ + Hom(a,c) + +We first recursively find `{id(x) => Hom(x,x), p⋅q => Hom(x,z)}`. We ultimately +want an AlgTerm for everything in the output type's context such that we can +substitute into `Hom(a,c)` to get the final answer. It will help to also compute +the AlgType for everything in the context. We work backwards, since we start by +knowing `{f => id(x)::Hom(x,x), g=> p⋅q :: Hom(x,z)}`. For `a` `b` and `c`, +we use `equations` which tell us, e.g., that `a = dom(f)`. So we can grab the +first argument of the *type* of `f` (i.e. grab `x` from `Hom(x,x)`). +""" +function infer_type(ctx::Context, t::AlgTerm) + if isvariable(t) + getvalue(ctx[head]) + elseif isconstant(t) + t.body.type + else + typed_terms = bind_localctx(ctx, t.body) + tc = ctx[t.body.method] + substitute_type(tc.type, typed_terms) + end +end + +infer_type(ctx::Context, t::TermInCtx) = infer_type(AppendContext(ctx, t.ctx), t.trm) + +""" +Take a term constructor and determine terms of its local context. + +This function is mutually recursive with `infer_type`. +""" +function bind_localctx(ctx::Context, t::MethodApp{AlgTerm}) + tc = getvalue(ctx[t.method]) + + eqs = equations(ctx, head) + + typed_terms = Dict{Ident, Pair{AlgTerm,AlgType}}() + for (i,a) in zip(tc.args, t.args) + tt = (a => infer_type(ctx, a)) + typed_terms[ident(tc; lid=i)] = tt + end + + for lc_arg in reverse(getidents(tc)) + if getlid(lc_arg) ∈ tc.args + continue + end + # one way of determining lc_arg's value + filt(e) = e isa AccessorApplication && e.to isa Ident + app = first(filter(filt, eqs[lc_arg])) + inferred_term = typed_terms[app.to][2].args[app.accessor.lid.val] + inferred_type = infer_type(ctx, inferred_term) + typed_terms[lc_arg] = inferred_term => inferred_type + end + + Dict([k=>v[1] for (k,v) in pairs(typed_terms)]) +end + +bind_localctx(ctx::Context, t::InCtx) = bind_localctx(AppendContext(ctx, t.ctx), t.trm) + +""" Replace idents with AlgTerms. """ +function substitute_term(t::T, dic::Dict{Ident,AlgTerm}) where T<:Union{AlgType, AlgTerm} + if isvar(t) + dic[t.body] + elseif isconst(t) + t + else + T(substitute_term(t.body, dic)) + end +end + +function substitute_term(ma::MethodApp{AlgTerm}, dic::Dict{Ident, AlgTerm}) + MethodApp(ma.head, ma.method, substitute_term.(ma.args, Ref(dic))) +end diff --git a/src/syntax/gats/ast.jl b/src/syntax/gats/ast.jl new file mode 100644 index 00000000..586b03e2 --- /dev/null +++ b/src/syntax/gats/ast.jl @@ -0,0 +1,167 @@ +# GAT ASTs +########## + +""" +We need this to resolve a mutual reference loop; the only subtype is Constant +""" +abstract type AbstractConstant end + +""" +`MethodApp` + +An application of a method of a constructor to arguments. We need a type parameter +`T` because `AlgTerm` hasn't been defined yet, but the only type used for `T` will +in fact be `AlgTerm`. + +`method` either points to an `AlgTermConstructor`, an `AlgTypeConstructor` or an +`AlgAccessor`, +""" +@struct_hash_equal struct MethodApp{T} + head::Ident + method::Ident + args::Vector{T} +end + +headof(t::MethodApp) = t.head +methodof(t::MethodApp) = t.method +argsof(t::MethodApp) = t.args + +rename(tag::ScopeTag, reps::Dict{Symbol, Symbol}, t::MethodApp{T}) where {T} = + MethodApp{T}( + rename(tag, reps, t.head), + rename(tag, reps, t.method), + rename.(Ref(tag), Ref(reps), t.args) + ) + +retag(reps::Dict{ScopeTag, ScopeTag}, t::MethodApp{T}) where {T} = + MethodApp{T}( + retag(reps, t.head), + retag(reps, t.method), + retag.(Ref(reps), t.args) + ) + +""" +`AlgTerm` + +One syntax tree to rule all the terms. +""" +@struct_hash_equal struct AlgTerm + body::Union{Ident, MethodApp{AlgTerm}, AbstractConstant} + function AlgTerm(body::Union{Ident, MethodApp{AlgTerm}, AbstractConstant}) + new(body) + end +end + +const EMPTY_ARGS = AlgTerm[] + +function AlgTerm(fun::Ident, method::Ident, args::Vector{AlgTerm}) + AlgTerm(MethodApp{AlgTerm}(fun, method, args)) +end + +function AlgTerm(fun::Ident, method::Ident) + AlgTerm(MethodApp{AlgTerm}(fun, method, EMPTY_ARGS)) +end + +isvariable(t::AlgTerm) = t.body isa Ident + +isapp(t::AlgTerm) = t.body isa MethodApp + +isconstant(t::AlgTerm) = t.body isa AbstractConstant + +rename(tag::ScopeTag, reps::Dict{Symbol,Symbol}, t::AlgTerm) = AlgTerm(rename(tag, reps, t.body)) + +retag(reps::Dict{ScopeTag, ScopeTag}, t::AlgTerm) = AlgTerm(retag(reps, t.body)) + +""" +`AlgType` + +One syntax tree to rule all the types. +`head` must be reference to a `AlgTypeConstructor` +""" +@struct_hash_equal struct AlgType + body::MethodApp{AlgTerm} +end + +isvariable(t::AlgType) = false + +isapp(t::AlgType) = true + +isconstant(t::AlgType) = false + +AlgType(head::Ident, method::Ident, args::Vector{AlgTerm}) = + AlgType(MethodApp{AlgTerm}(head, method, args)) + +retag(reps::Dict{ScopeTag,ScopeTag}, t::AlgType) = + AlgType(retag(reps, t.body)) + +rename(tag::ScopeTag, reps::Dict{Symbol, Symbol}, t::AlgType) = + AlgType(rename(tag, reps, t.body)) + +""" +Common methods for AlgType and AlgTerm +""" +function Base.show(io::IO, trm::T) where T<:Union{AlgTerm, AlgType} + print(io, "$(nameof(T))(") + print(io, toexpr(TypeScope(), trm)) + print(io, ")") +end + +""" +`Constant` + +A Julia value in an algebraic context. Type checked elsewhere. +""" +@struct_hash_equal struct Constant <: AbstractConstant + value::Any + type::AlgType +end + +# AlgSorts +########## + +""" +`AlgSort` + +A *sort*, which is essentially a type constructor without arguments +`ref` must be reference to a `AlgTypeConstructor` +""" +@struct_hash_equal struct AlgSort + head::Ident + method::Ident +end + +AlgSort(t::AlgType) = AlgSort(t.body.head, t.body.method) + +function AlgSort(c::Context, t::AlgTerm) + if isconstant(t) + AlgSort(t.body.type) + elseif isapp(t) + binding = c[t.body.method] + value = getvalue(binding) + AlgSort(value.type) + else # variable + binding = c[t.body] + AlgSort(getvalue(binding)) + end +end + +# Type Contexts +############### + +const TypeCtx = Context{AlgType} + +""" +A type or term with an accompanying type context, e.g. + + (a,b)::R (a,b)::Ob +----------- or ---------- + a*(a+b) Hom(a,b) +""" + +@struct_hash_equal struct InCtx{T} + ctx::TypeCtx + val::T +end + +const TermInCtx = InCtx{AlgTerm} +const TypeInCtx = InCtx{AlgType} diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl new file mode 100644 index 00000000..ad34b9a2 --- /dev/null +++ b/src/syntax/gats/exprinterop.jl @@ -0,0 +1,349 @@ +# AST +##### + +function parse_methodapp(c::GATContext, head::Symbol, argexprs) + args = Vector{AlgTerm}(fromexpr.(Ref(c), argexprs, Ref(AlgTerm))) + fun = fromexpr(c, head, Ident) + signature = AlgSort.(Ref(c), args) + method = methodlookup(c, fun, signature) + MethodApp{AlgTerm}(fun, method, args) +end + +function fromexpr(c::GATContext, e, ::Type{MethodApp{AlgTerm}}) + @match e begin + Expr(:call, head::Symbol, argexprs...) => parse_methodapp(c, head, argexprs) + _ => error("expected a call expression") + end +end + +function toexpr(c::Context, m::MethodApp) + Expr(:call, toexpr(c, m.head), toexpr.(Ref(c), m.args)...) +end + +function fromexpr(c::GATContext, e, ::Type{AlgTerm}) + @match e begin + s::Symbol => AlgTerm(fromexpr(c, s, Ident)) + Expr(:call, head::Symbol, argexprs...) => AlgTerm(parse_methodapp(c, head, argexprs)) + Expr(:(::), val, type) => Constant(val, fromexpr(c, type, AlgType)) + e::Expr => error("could not parse AlgTerm from $e") + constant::Constant => AlgTerm(constant) + end +end + +function toexpr(c::Context, term::AlgTerm) + toexpr(c, term.body) +end + +function fromexpr(c::GATContext, e, ::Type{AlgType})::AlgType + (head, argexprs) = @match e begin + s::Symbol => (s, []) + Expr(:call, head, args...) => (head, args) + _ => error("could not parse AlgType from $e") + end + AlgType(parse_methodapp(c, head, argexprs)) +end + +function toexpr(c::Context, type::AlgType) + if length(type.body.args) == 0 + toexpr(c, type.body.head) + else + Expr(:call, toexpr(c, type.body.head), toexpr.(Ref(c), type.body.args)...) + end +end + +toexpr(c::Context, constant::Constant; kw...) = + Expr(:(::), constant.value, toexpr(c, constant.type; kw...)) + +function fromexpr(c::GATContext, e, ::Type{InCtx{T}}; kw...) where T + (termexpr, localcontext) = @match e begin + Expr(:call, :(⊣), binding, Expr(:vect, args...)) => (binding, parsetypescope(c, args)) + e => (e, TypeScope()) + end + term = fromexpr(AppendContext(c, localcontext), termexpr, T) + InCtx{T}(localcontext, t) +end + +function toexpr(c::Context, tic::InCtx; kw...) + c′ = AppendScope(c, tic.ctx) + etrm = toexpr(c′, tic.trm; kw...) + flat = Scopes.flatten(tic.ctx) + ectx = toexpr(AppendScope(c,flat), flat; kw...) + Expr(:call, :(⊣), etrm, ectx) +end + +# Judgments +########### + +# toexpr + +function bindingexprs(c::Context, s::HasScope) + c′ = AppendContext(c, s) + [Expr(:(::), nameof(b), toexpr(c′, getvalue(b))) for b in s] +end + +function toexpr(c::Context, ts::TypeScope) + Expr(:vect, bindingexprs(c, ts)...) +end + +function judgmenthead(_::GAT, name, _::Union{AlgDeclaration, AlgAccessor}) + nothing +end + +function judgmenthead(_::GAT, _, judgment::AlgTypeConstructor) + name = nameof(getdecl(judgment)) + untyped = if isempty(judgment.args) + name + else + Expr(:call, name, nameof.(argsof(judgment))...) + end + Expr(:(::), untyped, :TYPE) +end + +function judgmenthead(theory::GAT, _, judgment::AlgTermConstructor) + name = nameof(getdecl(judgment)) + untyped = Expr(:call, name, nameof.(argsof(judgment))...) + c = GATContext(theory, judgment.localcontext) + Expr(:(::), untyped, toexpr(c, judgment.type)) +end + +function judgmenthead(theory::GAT, name, judgment::AlgAxiom) + c = GATContext(theory, judgment.localcontext) + untyped = Expr(:call, :(==), toexpr(c, judgment.equands[1]), toexpr(c, judgment.equands[2])) + Expr(:(::), untyped, toexpr(c, judgment.type)) +end + +function toexpr(c::GAT, binding::Binding{Judgment}) + judgment = getvalue(binding) + name = nameof(binding) + # FIXME + if judgment isa Alias + return nothing + end + head = judgmenthead(c, name, judgment) + if isnothing(head) + return nothing + end + Expr(:call, :(⊣), head, Expr(:vect, bindingexprs(c, judgment.localcontext)...)) +end + +# fromexpr + +function fromexpr(c::GATContext, e, ::Type{Binding{AlgType}}) + @match e begin + Expr(:(::), name::Symbol, type_expr) => + Binding{AlgType}(name, fromexpr(c, type_expr, AlgType)) + _ => error("could not parse binding of name to type from $e") + end +end + +function fromexpr(c::GATContext, e, ::Type{TypeScope}) + exprs = e.args + scope = TypeScope() + c′ = AppendContext(c, scope) + line = nothing + for expr in exprs + binding_exprs = @match expr begin + a::Symbol => [Expr(:(::), a, :default)] + Expr(:tuple, names...) => [:($name :: default) for name in names] + Expr(:(::), Expr(:tuple, names...), T) => [:($name :: $T) for name in names] + :($a :: $T) => [expr] + l::LineNumberNode => begin + line = l + [] + end + _ => error("invalid binding expression $expr") + end + for binding_expr in binding_exprs + binding = fromexpr(c′, binding_expr, Binding{AlgType}) + Scopes.unsafe_pushbinding!(getscope(scope), setline(binding, line)) + end + end + scope +end + +function parseargs!(theory::GAT, exprs::AbstractVector, scope::TypeScope) + c = GATContext(theory, scope) + map(exprs) do expr + binding_expr = @match expr begin + a::Symbol => getlid(ident(scope; name=a)) + :($a :: $T) => begin + binding = fromexpr(c, expr, Binding{AlgType}) + Scopes.unsafe_pushbinding!(scope.scope, binding) + LID(length(scope)) + end + _ => error("invalid argument expression $expr") + end + end +end + +function parseaxiom!(theory::GAT, localcontext, type_expr, e; name=nothing) + @match e begin + Expr(:call, :(==), lhs_expr, rhs_expr) => begin + c = GATContext(theory, localcontext) + equands = fromexpr.(Ref(c), [lhs_expr, rhs_expr], Ref(AlgTerm)) + type = if isnothing(type_expr) + infer_type(c, first(equands)) + else + fromexpr(c, type_expr, AlgType) + end + axiom = AlgAxiom(localcontext, type, equands) + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, axiom)) + end + _ => error("failed to parse equation from $e") + end +end + +function parseconstructor!(theory::GAT, localcontext, type_expr, e) + (name, arglist) = @match e begin + Expr(:call, name, args...) => (name, args) + name::Symbol => (name, []) + _ => error("failed to parse head of term constructor $head") + end + args = parseargs!(theory, arglist, localcontext) + @match type_expr begin + :TYPE => begin + decl = if hasname(theory, name) + ident(theory; name) + else + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration(nothing))) + end + typecon = AlgTypeConstructor(decl, localcontext, args) + X = Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(nothing, typecon)) + for (i, arg) in enumerate(argsof(typecon)) + argname = nameof(arg) + argdecl = if hasname(theory, argname) + ident(theory; name=argname) + else + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(argname, AlgDeclaration(nothing))) + end + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(nothing, AlgAccessor(argdecl, decl, X, i))) + end + end + _ => begin + c = GATContext(theory, localcontext) + type = fromexpr(c, type_expr, AlgType) + decl = if hasname(theory, name) + ident(theory; name) + else + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration(nothing))) + end + termcon = AlgTermConstructor(decl, localcontext, args, type) + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(nothing, termcon)) + end + end +end + +""" +This is necessary because the intuitive precedence rules for the symbols +that we use do not match the Julia precedence rules. In theory, this could +be written with some algorithm that recalculates precedence, but I am too lazy +to write that, so instead I just special case everything. +""" +function normalize_judgment(e) + @match e begin + :($name := $lhs == $rhs :: $typ ⊣ $ctx) => :((($name := ($lhs == $rhs)) :: $typ) ⊣ $ctx) + :($lhs == $rhs :: $typ ⊣ $ctx) => :((($lhs == $rhs) :: $typ) ⊣ $ctx) + :(($lhs == $rhs :: $typ) ⊣ $ctx) => :((($lhs == $rhs) :: $typ) ⊣ $ctx) + :($trmcon :: $typ ⊣ $ctx) => :(($trmcon :: $typ) ⊣ $ctx) + :($name := $lhs == $rhs ⊣ $ctx) => :((($name := ($lhs == $rhs))) ⊣ $ctx) + :($lhs == $rhs ⊣ $ctx) => :(($lhs == $rhs) ⊣ $ctx) + :($(trmcon::Symbol) ⊣ $ctx) => :(($trmcon :: default) ⊣ $ctx) + :($f($(args...)) ⊣ $ctx) && if f ∉ [:(==), :(⊣)] end => :(($f($(args...)) :: default) ⊣ $ctx) + trmcon::Symbol => :($trmcon :: default) + :($f($(args...))) && if f ∉ [:(==), :(⊣)] end => :($e :: default) + _ => e + end +end + +function parse_binding_line!(theory::GAT, e, linenumber) + e = normalize_judgment(e) + + (binding, localcontext) = @match e begin + Expr(:call, :(⊣), binding, ctxexpr) && if ctxexpr.head == :vect end => + (binding, fromexpr(GATContext(theory), ctxexpr, TypeScope)) + e => (e, TypeScope()) + end + + c = GATContext(theory, localcontext) + + (head, type_expr) = @match binding begin + Expr(:(::), head, type_expr) => (head, type_expr) + _ => (binding, nothing) + end + + @match head begin + Expr(:(:=), name, equation) => parseaxiom!(theory, localcontext, type_expr, equation; name) + Expr(:call, :(==), _, _) => parseaxiom!(theory, localcontext, type_expr, head) + _ => parseconstructor!(theory, localcontext, type_expr, head) + end +end + +# GATs +###### + +""" +This only works when `seg` is a segment of `theory` +""" +function toexpr(theory::GAT, seg::GATSegment) + e = Expr(:block) + for binding in seg + if !isnothing(getline(binding)) + push!(e.args, getline(binding)) + end + line = toexpr(theory, binding) + if !isnothing(line) + push!(e.args, line) + end + end + e +end + +function parse_gat_line!(theory::GAT, e::Expr, linenumber) + @match e begin + Expr(:macrocall, var"@op", _, aliasexpr) => begin + lines = @match aliasexpr begin + Expr(:block, lines...) => lines + _ => [aliasexpr] + end + for line in lines + @switch line begin + @case (_::LineNumberNode) + nothing + @case :($alias := $name) + # check if there is already a declaration for name, if not, create declaration + decl = if hasname(theory, name) + ident(theory; name) + else + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration(nothing))) + end + binding = Binding{Judgment}(alias, Alias(decl), linenumber) + Scopes.unsafe_pushbinding!(theory, binding) + @case _ + error("could not match @op expression $line") + end + end + end + Expr(:import, Expr(:(:), Expr(:(.), mod), imports)) => begin + # create appropriate declarations + end + _ => begin + parse_binding_line!(theory, e, linenumber) + end + end +end + +function fromexpr(parent::GAT, e, ::Type{GAT}; name=parent.name) + theory = copy(parent; name) + unsafe_newsegment!(theory) + e.head == :block || error("expected a block to parse into a GAT, got: $e") + linenumber = nothing + for line in e.args + bindings = @match line begin + l::LineNumberNode => begin + linenumber = l + end + _ => parse_gat_line!(theory, line, linenumber) + end + end + theory +end diff --git a/src/syntax/gats/gat.jl b/src/syntax/gats/gat.jl new file mode 100644 index 00000000..65e99528 --- /dev/null +++ b/src/syntax/gats/gat.jl @@ -0,0 +1,168 @@ +""" +`GATSegment` + +A piece of a GAT, consisting of a scope that binds judgments to names, possibly +disambiguated by argument sorts. + +This is a struct rather than just a type alias so that we can customize the show method. +""" +struct GATSegment <: HasScope{Judgment} + scope::Scope{Judgment} +end + +GATSegment() = GATSegment(Scope{Judgment}()) + +Scopes.getscope(seg::GATSegment) = seg.scope + +""" +`MethodResolver` + +Right now, this just maps a sort signature to the resolved method. + +When we eventually support varargs, this will have to do something slightly +fancier. +""" +@struct_hash_equal struct MethodResolver + bysignature::Dict{AlgSorts, Ident} +end + +function MethodResolver() + MethodResolver(Dict{AlgSorts, Ident}()) +end + +addmethod!(m::MethodResolver, sig::AlgSorts, method::Ident) = + m.bysignature[sig] = method + +resolvemethod(m::MethodResolver, sig::AlgSorts) = m.bysignature[sig] + +allmethods(m::MethodResolver) = pairs(m.bysignature) + +""" +`GAT` + +A generalized algebraic theory. Essentially, just consists of a name and a list +of `GATSegment`s, but there is also some caching to make access faster. +Specifically, there is a dictionary to map ScopeTag to position in the list of +segments, and there are lists of all of the identifiers for term constructors, +type constructors, and axioms so that they can be iterated through faster. + +GATs allow overloading but not shadowing. +""" +struct GAT <: HasScopeList{Judgment} + name::Symbol + segments::ScopeList{Judgment} + resolvers::Dict{Ident, MethodResolver} + sorts::Vector{AlgSort} + axioms::Vector{Ident} +end + +function Base.copy(theory::GAT; name=theory.name) + GAT( + name, + copy(theory.segments), + deepcopy(theory.resolvers), + copy(theory.sorts), + copy(theory.axioms) + ) +end + +function GAT(name::Symbol) + GAT( + name, + ScopeList{Judgment}(), + Dict{Ident, MethodResolver}(), + AlgSort[], + Ident[] + ) +end + +# Mutators which should only be called during construction of a theory + +function unsafe_newsegment!(theory::GAT) + Scopes.unsafe_pushscope!(theory.segments, GATSegment()) +end + +function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgDeclaration) + theory.resolvers[x] = MethodResolver() +end + +function unsafe_updatecache!( + theory::GAT, + x::Ident, + judgment::Union{AlgTermConstructor, AlgAccessor} +) + addmethod!(theory.resolvers[getdecl(judgment)], sortsignature(judgment), x) +end + +function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgTypeConstructor) + addmethod!(theory.resolvers[getdecl(judgment)], sortsignature(judgment), x) + push!(theory.sorts, AlgSort(getdecl(judgment), x)) +end + +function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgAxiom) + push!(theory.axioms, x) +end + +function unsafe_updatecache!(theory::GAT, x::Ident, judgment::Alias) + nothing +end + +function Scopes.unsafe_pushbinding!(theory::GAT, binding::Binding{Judgment}) + x = Scopes.unsafe_pushbinding!(theory.segments, binding) + unsafe_updatecache!(theory, x, getvalue(binding)) + x +end + +# Pretty-printing + +function Base.show(io::IO, theory::GAT) + println(io, "GAT(", theory.name, "):") + for seg in theory.segments.scopes + block = toexpr(theory, seg) + for line in block.args[1:end-1] + println(io, " ", line) + end + print(io, " ", block.args[end]) + end +end + +# Accessors + +Base.nameof(theory::GAT) = theory.name + +resolvers(theory::GAT) = theory.resolvers + +Scopes.getscopelist(c::GAT) = c.segments + +function allnames(theory::GAT; aliases=false) + filter(!=(nothing), nameof.(getidents(theory; aliases))) +end + +sorts(theory::GAT) = theory.sorts + +Base.issubset(t1::GAT, t2::GAT) = + all(s->hastag(t2, s), gettag.(Scopes.getscopelist(t1).scopes)) + +""" +`GATContext` + +A context consisting of two parts: a GAT and a TypeCtx + +Certain types (like AlgTerm) can only be parsed in a GATContext, because +they require access to the method resolving in the GAT. +""" +struct GATContext <: HasContext{Union{Judgment, AlgType}} + theory::GAT + context::Context{AlgType} +end + +GATContext(theory::GAT) = GATContext(theory, EmptyContext{AlgType}()) + +Scopes.getcontext(c::GATContext) = AppendContext(c.theory, c.context) + +Scopes.AppendContext(c::GATContext, context::Context{AlgType}) = + GATContext(c.theory, AppendContext(c.context, context)) + +function methodlookup(c::GATContext, decl::Ident, sig::AlgSorts) + resolvemethod(c.theory.resolvers[decl], sig) +end diff --git a/src/syntax/gats/judgments.jl b/src/syntax/gats/judgments.jl new file mode 100644 index 00000000..b9da3611 --- /dev/null +++ b/src/syntax/gats/judgments.jl @@ -0,0 +1,142 @@ +# GAT Judgments +############### + +""" +`TypeScope` + +A scope where variables are assigned to `AlgType`s. We use a wrapper +here so that it pretty prints as `[a::B]` instead of `{a => AlgType(B)}` +""" +struct TypeScope <: HasScope{AlgType} + scope::Scope{AlgType} +end + +TypeScope() = TypeScope(Scope{AlgType}()) + +TypeScope(bindings::Vector{Binding{AlgType}}; tag=newscopetag()) = TypeScope(Scope(bindings; tag)) +TypeScope(bindings::Pair{Symbol, AlgType}...) = TypeScope(Scope{AlgType}(bindings...)) + +Scopes.getscope(ts::TypeScope) = ts.scope + +function Base.show(io::IO, ts::TypeScope) + print(io, toexpr(EmptyContext{AlgType}(), ts)) +end + +""" +A GAT is conceptually a bunch of `Judgment`s strung together. +""" +abstract type Judgment <: HasContext{AlgType} end + +abstract type TrmTypConstructor <: Judgment end + +argsof(t::TrmTypConstructor) = t[t.args] + +Scopes.getscope(t::TrmTypConstructor) = t.localcontext + +Base.getindex(tc::TrmTypConstructor, lid::LID) = getindex(tc.localcontext, lid) + +Base.getindex(tc::TrmTypConstructor, lids::AbstractVector{LID}) = getindex(tc.localcontext, lids) + +""" +`AlgDeclaration` + +A declaration of a constructor; constructor methods in the form of +`AlgTermConstructors` or the accessors for `AlgTypeConstructors` follow later in +the theory. + +If `overloads` is nothing, this is declared in the theory module as a new function, i.e. + +```julia +function foo end +``` + +If `overloads` is a vector of symbols, then this is imported into the theory module +from the fully-qualified module name given by the vector of symbols, i.e. + +```julia +import Base: foo +``` +""" +@struct_hash_equal struct AlgDeclaration <: Judgment + overloads::Union{Nothing, Vector{Symbol}} +end + +Scopes.getcontext(::AlgDeclaration) = EmptyContext{AlgType}() + +""" +`AlgTypeConstructor` + +A declaration of a type constructor. +""" +@struct_hash_equal struct AlgTypeConstructor <: TrmTypConstructor + declaration::Ident + localcontext::TypeScope + args::Vector{LID} +end + +Scopes.getcontext(tc::AlgTypeConstructor) = tc.localcontext + +getdecl(tc::AlgTypeConstructor) = tc.declaration + +""" +`AlgAccessor` + +The arguments to a term constructor serve a dual function as both arguments and +also methods to extract the value of those arguments. + +I.e., declaring `Hom(dom::Ob, codom::Ob)::TYPE` implicitly overloads a previous +declaration for `dom` and `codom`, or creates declarations if none previously +exist. +""" +@struct_hash_equal struct AlgAccessor <: Judgment + declaration::Ident + typecondecl::Ident + typecon::Ident + arg::Int +end + +Scopes.getcontext(acc::AlgAccessor) = EmptyContext{AlgType}() + +getdecl(acc::AlgAccessor) = acc.declaration + +sortsignature(acc::AlgAccessor) = [AlgSort(acc.typecondecl, acc.typecon)] + +""" +`AlgTermConstructor` + +A declaration of a term constructor as a method of an `AlgFunction`. +""" +@struct_hash_equal struct AlgTermConstructor <: TrmTypConstructor + declaration::Ident + localcontext::TypeScope + args::Vector{LID} + type::AlgType +end + +Scopes.getcontext(tc::AlgTermConstructor) = tc.localcontext + +getdecl(tc::AlgTermConstructor) = tc.declaration + +sortsignature(tc::Union{AlgTypeConstructor, AlgTermConstructor}) = + AlgSort.(getvalue.(argsof(tc))) + +""" +`AlgAxiom` + +A declaration of an axiom +""" +@struct_hash_equal struct AlgAxiom <: Judgment + localcontext::TypeScope + type::AlgType + equands::Vector{AlgTerm} +end + +Scopes.getcontext(t::AlgAxiom) = t.localcontext + +""" +`AlgSorts` + +A description of the argument sorts for a term constructor, used to disambiguate +multiple term constructors of the same name. +""" +const AlgSorts = Vector{AlgSort} diff --git a/src/syntax/module.jl b/src/syntax/module.jl index 5c95dc98..3e1ccc0b 100644 --- a/src/syntax/module.jl +++ b/src/syntax/module.jl @@ -3,17 +3,17 @@ module Syntax using Reexport include("Scopes.jl") -# include("ExprInterop.jl") -# include("GATs.jl") +include("ExprInterop.jl") +include("GATs.jl") # include("Presentations.jl") -# include("TheoryInterface.jl") +include("TheoryInterface.jl") # include("TheoryMaps.jl") @reexport using .Scopes -# @reexport using .ExprInterop -# @reexport using .GATs +@reexport using .ExprInterop +@reexport using .GATs # @reexport using .Presentations -# @reexport using .TheoryInterface +@reexport using .TheoryInterface # @reexport using .TheoryMaps end diff --git a/test/models/ModelInterface.jl b/test/models/ModelInterface.jl index a4e4f16c..c7a1b4ed 100644 --- a/test/models/ModelInterface.jl +++ b/test/models/ModelInterface.jl @@ -100,9 +100,9 @@ end f end - function Hom(values::Vector{Int}, dom::FinSet, codom::FinSet) - FinFunction(values, dom, codom) - end + # function Hom(values::Vector{Int}, dom::FinSet, codom::FinSet) + # FinFunction(values, dom, codom) + # end dom(f::FinFunction) = f.dom codom(f::FinFunction) = f.codom diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index 9045f2bf..b0df8a40 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -9,20 +9,19 @@ function basicprinted(x) sprint(show, x; context=(:color=>false)) end -scope = Scope(:number, :(+), :(*)) +scope = Scope(:number, :(+), :(_), :(*)) -number, plus, times = idents(scope; name=[:number, :(+), :(*)]) +number, plus, plusmethod, times = idents(scope; name=[:number, :(+), :(_), :(*)]) one = AlgTerm(Constant(1, AlgType(number))) two = AlgTerm(Constant(2, AlgType(number))) -three = AlgTerm(plus, [one, two]) +three = AlgTerm(plus, plusmethod, [one, two]) @test toexpr(scope, three) == :((1::number) + (2::number)) -@test fromexpr(TypeScope(), two.head, AlgTerm) == two -@test_throws Exception fromexpr(TypeScope(), :(x = 3), AlgTerm) +@test fromexpr(GATContext(GAT(:Empty), TypeScope()), two.body, AlgTerm) == two @test basicprinted(two) == "AlgTerm(2::number)" @@ -34,56 +33,61 @@ three = AlgTerm(plus, [one, two]) @test_throws Exception AlgSort(scope, three) -# This throws a type error because it tries to look up `+` with a signature, -# of AlgSorts, but `scope` only has nothing-typed signatures. -@test_throws TypeError fromexpr(scope, toexpr(scope, three), AlgTerm) +@test_throws MethodError fromexpr(scope, toexpr(scope, three), AlgTerm) seg_expr = quote Ob :: TYPE Hom(dom, codom) :: TYPE ⊣ [dom::Ob, codom::Ob] - id(a) :: Hom(a,a) ⊣ [a::Ob] + id(a) :: Hom(a, a) ⊣ [a::Ob] compose(f, g) :: Hom(a, c) ⊣ [a::Ob, b::Ob, c::Ob, f::Hom(a, b), g::Hom(b, c)] - ((compose(f, compose(g, h)) == compose(compose(f, g), h)) :: Hom(a,d)) ⊣ [ + compose(f, compose(g, h)) == compose(compose(f, g), h) :: Hom(a,d) ⊣ [ a::Ob, b::Ob, c::Ob, d::Ob, f::Hom(a, b), g::Hom(b, c), h::Hom(c, d) ] end -seg = fromexpr(TypeScope(), seg_expr, GATSegment) +thcat = fromexpr(GAT(:ThCat), seg_expr, GAT) -O, H, i, cmp = idents(seg; lid=LID.(1:4)) +O, H, i, cmp = idents(thcat; name=[:Ob, :Hom, :id, :compose]) -@test toexpr(TypeScope(), seg) == seg_expr - -O, H, i, cmp = getidents(seg) +ObT = fromexpr(GATContext(thcat), :Ob, AlgType) +ObS = AlgSort(ObT) # Extend seg with a context of (A: Ob) -sortscope = Scope([Binding{AlgType}(:A, AlgType(O))]) +sortscope = TypeScope(:A => ObT) + A = ident(sortscope; name=:A) + ATerm = AlgTerm(A) +c = GATContext(thcat, sortscope) -ss = AppendScope(ScopeList([seg]), sortscope) -@test sortcheck(ss, AlgTerm(A)) == AlgSort(O) +HomT = fromexpr(c, :(Hom(A, A)), AlgType) +HomS = AlgSort(HomT) + +@test sortcheck(c, AlgTerm(A)) == ObS # # Good term and bad term -ida = fromexpr(ss, :(id(A)), AlgTerm) -iida = AlgTerm(i,[AlgTerm(i, [AlgTerm(A)])]) +ida = fromexpr(c, :(id(A)), AlgTerm) + +im = ida.body.method + +iida = AlgTerm(i, im, [AlgTerm(i, im, [AlgTerm(A)])]) -@test AlgSort(ss, ida) == AlgSort(H) -@test sortcheck(ss, ida) == AlgSort(H) -@test AlgSort(ss, iida) == AlgSort(H) -@test_throws Exception sortcheck(ss, iida) +@test AlgSort(c, ida) == HomS +@test sortcheck(c, ida) == HomS +@test AlgSort(c, iida) == HomS +@test_throws Exception sortcheck(c, iida) # Good type and bad type -haa = AlgType(H,[AlgTerm(A),AlgTerm(A)]) -haia = AlgType(H,[AlgTerm(A),ida]) -@test sortcheck(ss, haa) -@test_throws Exception sortcheck(ss, haia) +haa = HomT +haia = AlgType(HomS.head, HomS.method, [ATerm, ida]) +@test sortcheck(c, haa) +@test_throws Exception sortcheck(c, haia) # Renaming BTerm = rename(gettag(sortscope), Dict(:A=>:B), ATerm) -Bsortscope = Scope([Binding{AlgType}(:B, AlgType(O))]; tag=gettag(sortscope)) +Bsortscope = TypeScope([Binding{AlgType}(:B, ObT)]; tag=gettag(sortscope)) BTerm_expected = AlgTerm(ident(Bsortscope;name=:B)) @test BTerm == BTerm_expected diff --git a/test/syntax/Scopes.jl b/test/syntax/Scopes.jl index af536c6d..12972a24 100644 --- a/test/syntax/Scopes.jl +++ b/test/syntax/Scopes.jl @@ -86,7 +86,8 @@ bind_z = Binding{String}(:x, "ex") # Scopes ######## -xy_scope = Scope([bind_x, bind_y]; tag=tag1, aliases=Dict(:x => Set([:X]), :y => Set([:Y]))) +bind_X, bind_Y = Binding{String}(:X, Alias(x)), Binding{String}(:Y, Alias(y)) +xy_scope = Scope([bind_x, bind_y, bind_X, bind_Y]; tag=tag1) xy_scope′ = Scope([bind_x]; tag=tag1) @test xy_scope == xy_scope′ @@ -98,7 +99,7 @@ xy_scope′ = Scope([bind_x]; tag=tag1) @test haslid(xy_scope, LID(2)) @test hasident(xy_scope, x) @test !hasident(xy_scope, Ident(tag1, LID(1), :y)) -@test getbindings(xy_scope) == [bind_x, bind_y] +@test getbindings(xy_scope) == [bind_x, bind_y, bind_X, bind_Y] @test getbinding(xy_scope, LID(1)) == bind_x @test getbinding(xy_scope, x) == bind_x @test getbinding(xy_scope, :x) == bind_x @@ -130,13 +131,7 @@ s_tag = gettag(s) s_x = Ident(s_tag, LID(1), :x) -@test ident(s; name=:x, sig=2) == s_x -@test_throws KeyError ident(s; name=:x) -@test ident(s; name=:x, isunique=true) == s_x - -Scopes.unsafe_addalias!(s, :x, :X) - -@test ident(s; name=:X, isunique=true) == s_x +@test ident(s; name=:x) == s_x @test length(s) == 1 @test s[:x] == getbinding(s, :x) @@ -144,7 +139,6 @@ Scopes.unsafe_addalias!(s, :x, :X) @test s[s_x] == getbinding(s, s_x) @test [s...] == [bind_x_typed] @test haskey(s, :x) -@test haskey(s, :X) @test haskey(s, LID(1)) @test haskey(s, s_x) @@ -156,20 +150,18 @@ Scopes.unsafe_addalias!(s, :x, :X) @test getlevel(s, :x) == 1 @test_throws KeyError getlevel(s, :elephant) @test Scopes.check_names(s) + # Context Utilities ################### @test getscope(s, s_tag) == s @test getscope(s, s_x) == s @test s_x ∈ s -@test nameof(canonicalize(s, Ident(s_tag, LID(1), :X))) == :x # ScopeList ########### -e = ScopeList(Scope{Nothing,Nothing}[]) +e = ScopeList(Scope{Nothing}[]) @test nscopes(e) == 0 -flat = Scopes.flatten(e) -@test flat == Scope(Binding{Nothing,Nothing}[],tag=gettag(flat)) @test sprint(show, e) == "[]" @test_throws Exception ScopeList([xy_scope, xy_scope]) @@ -190,8 +182,6 @@ xz_scope = Scope([bind_x, bind_z]) c = ScopeList([xy_scope, xz_scope]) @test sprint(show, c)[1:2] == "[{" -@test_throws ErrorException Scopes.flatten(c) - @test getscope(c, 1) == xy_scope @test getscope(c, 2) == xz_scope @test nscopes(c) == 2 @@ -207,13 +197,13 @@ c = ScopeList([xy_scope, xz_scope]) @test nameof(ident(c; level=1, lid=LID(2))) == :y @test_throws ErrorException ident(c) -# AppendScope +# AppendContext ############# -@test_throws Exception AppendScope(ScopeList([xy_scope]), xy_scope) +@test_throws Exception AppendContext(ScopeList([xy_scope]), xy_scope) -c = AppendScope(ScopeList([xy_scope]), xz_scope) -@test length(getidents(c)) == 4 +c = AppendContext(ScopeList([xy_scope]), xz_scope) +@test length(getidents(c)) == 6 @test getscope(c, 1) == xy_scope @test getscope(c, 2) == xz_scope @test nscopes(c) == 2 @@ -229,11 +219,12 @@ c = AppendScope(ScopeList([xy_scope]), xz_scope) @test nameof(ident(c; level=1, lid=LID(2))) == :y # EmptyContext -e = EmptyContext{Nothing, Nothing}() +e = EmptyContext{Nothing}() @test nscopes(e) == 0 @test !hastag(e, newscopetag()) @test_throws BoundsError getscope(e, 1) @test_throws KeyError getlevel(e, :x) @test_throws KeyError getlevel(e, gettag(x)) @test !hasname(e, :x) + end diff --git a/test/syntax/TheoryInterface.jl b/test/syntax/TheoryInterface.jl index 575e1143..c69055b3 100644 --- a/test/syntax/TheoryInterface.jl +++ b/test/syntax/TheoryInterface.jl @@ -11,6 +11,7 @@ end @test ThCategoryTypes.Ob isa Function @test ThCategoryTypes.Hom isa Function @test Set(allnames(ThCategoryTypes.THEORY)) == Set([:Ob, :Hom, :dom, :codom]) +@test Set(allnames(ThCategoryTypes.THEORY; aliases=true)) == Set([:Ob, :Hom, :(→), :dom, :codom]) using .ThCategoryTypes @@ -31,7 +32,6 @@ using .ThLawlessCategory @test parentmodule(id) == ThLawlessCategory @test Set(allnames(ThLawlessCategory.THEORY)) == Set([:Ob, :Hom, :dom, :codom, :compose, :id]) @test nameof(ThLawlessCategory.THEORY) == :ThLawlessCategory -@test keys(accessors(ThLawlessCategory.THEORY)) == Set([:dom, :codom]) @test_throws Exception @eval @theory ThDoubleCategory <: ThCategory begin Hom(dom::Ob, codom::Ob) :: TYPE From 163a8907b5a96b005450f306fcce6d93042d6369 Mon Sep 17 00:00:00 2001 From: Owen Lynch Date: Tue, 3 Oct 2023 20:14:33 -0700 Subject: [PATCH 03/12] most things working except theorymaps --- src/models/ModelInterface.jl | 7 +- src/models/SymbolicModels.jl | 90 ++++++++++++--------- src/models/module.jl | 8 +- src/stdlib/models/module.jl | 4 +- src/stdlib/module.jl | 4 +- src/stdlib/theories/Algebra.jl | 18 ++--- src/stdlib/theories/Categories.jl | 8 +- src/stdlib/theories/Monoidal.jl | 4 +- src/stdlib/theories/module.jl | 12 +-- src/syntax/GATs.jl | 4 +- src/syntax/TheoryInterface.jl | 20 ++++- src/syntax/gats/algorithms.jl | 126 +++++++----------------------- src/syntax/gats/ast.jl | 2 + src/syntax/gats/exprinterop.jl | 42 ++++++++-- src/syntax/gats/gat.jl | 33 ++++++-- src/syntax/gats/judgments.jl | 2 +- test/models/ModelInterface.jl | 3 +- test/models/SymbolicModels.jl | 8 +- test/stdlib/models/Arithmetic.jl | 62 +++++++-------- test/stdlib/models/Op.jl | 22 +++--- test/stdlib/models/tests.jl | 2 +- test/syntax/GATs.jl | 23 ++---- test/syntax/Scopes.jl | 2 +- test/syntax/TheoryInterface.jl | 4 - test/syntax/tests.jl | 13 ++- 25 files changed, 260 insertions(+), 263 deletions(-) diff --git a/src/models/ModelInterface.jl b/src/models/ModelInterface.jl index 7496b18b..a570d971 100644 --- a/src/models/ModelInterface.jl +++ b/src/models/ModelInterface.jl @@ -339,6 +339,9 @@ function typecheck_instance( "the types for this model declaration do not permit Julia overloading to distinguish between GAT overloads" for (decl, resolver) in theory.resolvers + if nameof(decl) ∈ ext_functions + continue + end for (_, x) in allmethods(resolver) sig = julia_signature(getvalue(theory[x]), jltype_by_sort; oldinstance, X=x) if haskey(undefined_signatures, sig) @@ -392,10 +395,10 @@ function typecheck_instance( end end - for (_, (decl, method)) in undefined_signatures + for (sig, (decl, method)) in undefined_signatures judgment = getvalue(theory[method]) if judgment isa AlgTermConstructor - error("Failed to implement $(nameof(n))") + error("Failed to implement $(toexpr(sig))") elseif judgment isa AlgTypeConstructor push!(typechecked, default_typecon_impl(method, theory, jltype_by_sort)) elseif judgment isa AlgAccessor diff --git a/src/models/SymbolicModels.jl b/src/models/SymbolicModels.jl index 6dd0aaca..94a56a72 100644 --- a/src/models/SymbolicModels.jl +++ b/src/models/SymbolicModels.jl @@ -244,9 +244,10 @@ macro symbolic_model(decl, theoryname, body) end f = parse_function(line) juliasig = parse_function_sig(f) - gatsig = AlgSort.(idents(theory; name=juliasig.types)) - x = ident(theory; name=f.name, sig=gatsig) - overrides[x] = f + decl = ident(theory; name=juliasig.name) + sig = fromexpr.(Ref(GATContext(theory)), juliasig.types, Ref(AlgSort)) + method = resolvemethod(theory.resolvers[decl], sig) + overrides[method] = f end end @@ -262,7 +263,7 @@ macro symbolic_model(decl, theoryname, body) internal_constructors(theory)...] module_decl = :(module $(esc(name)) - export $(nameof.(typecons(theory))...) + export $(nameof.(sorts(theory))...) using ..($(nameof(__module__))) import ..($(nameof(__module__))): $theoryname const $(esc(:THEORY_MODULE)) = $(esc(theoryname)) @@ -276,8 +277,8 @@ macro symbolic_model(decl, theoryname, body) # Part 4: Generating generators. - # generator_overloads = symbolic_generators(theorymodule, theory) generator_overloads = [] + # generator_overloads = symbolic_generators(theorymodule, theory) Expr( :toplevel, @@ -297,13 +298,13 @@ function symbolic_struct(name, abstract_type, parentmod)::Expr end function symbolic_structs(theory::GAT, abstract_types, parentmod)::Vector{Expr} - map(zip(theory.typecons, abstract_types)) do (X, abstract_type) + map(zip(theory.sorts, abstract_types)) do (X, abstract_type) symbolic_struct(nameof(X), abstract_type, parentmod) end end function typename(theory::GAT, type::AlgType; parentmod=nothing) - name = nameof(sortname(theory, type)) + name = nameof(type.body.head) if !isnothing(parentmod) Expr(:(.), parentmod, QuoteNode(name)) else @@ -312,12 +313,15 @@ function typename(theory::GAT, type::AlgType; parentmod=nothing) end function internal_accessors(theory::GAT) - map(typecons(theory)) do X - map(enumerate(argsof(getvalue(theory[X])))) do (i, binding) + map(theory.sorts) do sort + typecon = getvalue(theory[sort.method]) + map(collect(pairs(theory.accessors[sort.method]))) do (i, acc) + accessor = getvalue(theory[acc]) + return_type = getvalue(typecon[typecon.args[i]]) JuliaFunction( - name=esc(nameof(binding)), - args=[:(x::$(esc(nameof(X))))], - return_type = typename(theory, getvalue(binding)), + name=esc(nameof(getdecl(accessor))), + args=[:(x::$(esc(nameof(sort.head))))], + return_type = typename(theory, return_type), impl=:(x.type_args[$i]) ) end @@ -325,14 +329,14 @@ function internal_accessors(theory::GAT) end function internal_constructors(theory::GAT)::Vector{JuliaFunction} - map(termcons(theory)) do f - name = nameof(f) - termcon = getvalue(theory, f) + map(termcons(theory)) do (decl, method) + name = nameof(decl) + termcon = getvalue(theory, method) args = map(argsof(termcon)) do binding Expr(:(::), nameof(binding), typename(theory, getvalue(binding))) end - eqs = equations(theory, f) + eqs = equations(theory, method) throw_expr = Expr( :call, @@ -345,28 +349,33 @@ function internal_constructors(theory::GAT)::Vector{JuliaFunction} ) ) + arg_expr_lookup = Dict{Ident, Any}(map(termcon.args) do lid + x = ident(termcon.localcontext; lid) + x => nameof(x) + end) + check_expr = Expr( :&&, map(filter(exprs -> length(exprs) > 1, collect(values(eqs)))) do exprs cmp_expr = [] for e in exprs - append!(cmp_expr, [build_infer_expr(e), esc(:(==))]) + append!(cmp_expr, [compile(arg_expr_lookup, e), esc(:(==))]) end Expr(:comparison, cmp_expr[1:end-1]...) end... ) check_or_error = Expr(:(||), :(!strict), check_expr, throw_expr) - exprs = getidents(termcon.localcontext) - expr_lookup = Dict{Ident, Any}(map(exprs) do x - x => build_infer_expr(first(eqs[x])) + context_xs = getidents(termcon.localcontext) + expr_lookup = Dict{Ident, Any}(map(context_xs) do x + x => compile(arg_expr_lookup, first(eqs[x])) end) build = Expr( :call, Expr(:curly, typename(theory, termcon.type), Expr(:quote, name)), Expr(:vect, nameof.(argsof(termcon))...), - Expr(:ref, GATExpr, compile.(Ref(expr_lookup), termcon.type.args)...) + Expr(:ref, GATExpr, compile.(Ref(expr_lookup), termcon.type.body.args)...) ) JuliaFunction( @@ -390,27 +399,29 @@ function symbolic_instance_methods( type_con_funs = [] accessors_funs = [] - for type_con_id in typecons(theory) - type_con = getvalue(theory[type_con_id]) - symgen = symbolic_generator(theorymodule, syntaxname, type_con_id, type_con, theory) + for sort in sorts(theory) + type_con = getvalue(theory[sort.method]) + symgen = symbolic_generator(theorymodule, syntaxname, sort.method, type_con, theory) push!(type_con_funs, symgen) for binding in argsof(type_con) - push!(accessors_funs, symbolic_accessor(theorymodule, theory, syntaxname, type_con_id, binding)) + push!(accessors_funs, symbolic_accessor(theorymodule, theory, syntaxname, sort.method, binding)) end end - type_replacements = [nameof(X) => Expr(:(.), syntaxname, QuoteNode(nameof(X))) for X in typecons(theory)] + type_replacements = [ + nameof(X) => Expr(:(.), syntaxname, QuoteNode(nameof(X))) for X in sorts(theory) + ] - term_con_funs = map(termcons(theory)) do term_con_id - if haskey(overrides, term_con_id) + term_con_funs = map(termcons(theory)) do (decl, method) + if haskey(overrides, method) replace_symbols( Dict([ - :new => Expr(:(.), syntaxname, QuoteNode(nameof(term_con_id))); type_replacements + :new => Expr(:(.), syntaxname, QuoteNode(nameof(decl))); type_replacements ]), - setname(overrides[term_con_id], Expr(:(.), theorymodule, QuoteNode(nameof(term_con_id)))) + setname(overrides[method], Expr(:(.), theorymodule, QuoteNode(nameof(decl)))) ) else - symbolic_termcon(theorymodule, theory, syntaxname, term_con_id) + symbolic_termcon(theorymodule, theory, syntaxname, method) end end [type_con_funs..., accessors_funs..., term_con_funs...] @@ -419,7 +430,7 @@ end function symbolic_generator(theorymodule, syntaxname, X::Ident, typecon::AlgTypeConstructor, theory::GAT) value_param = gensym(:value) - name = nameof(X) + name = nameof(getdecl(typecon)) args = [ Expr(:(::), value_param, Any); [Expr(:(::), nameof(binding), typename(theory, getvalue(binding); parentmod=syntaxname)) @@ -438,8 +449,8 @@ function symbolic_generator(theorymodule, syntaxname, X::Ident, typecon::AlgType JuliaFunction(name=Expr(:(.), theorymodule, QuoteNode(name)), args=args,impl=impl ) end -function symbolic_accessor(theorymodule, theory, name, typecon::Ident, accessor::Binding, ) - typcon_name = QuoteNode(nameof(typecon)) +function symbolic_accessor(theorymodule, theory, name, typecon::Ident, accessor::Binding) + typcon_name = QuoteNode(nameof(getdecl(getvalue(theory[typecon])))) accessor_name = QuoteNode(nameof(accessor)) JuliaFunction( name=Expr(:(.), theorymodule, accessor_name), @@ -449,9 +460,9 @@ function symbolic_accessor(theorymodule, theory, name, typecon::Ident, accessor: ) end -function symbolic_termcon(theorymodule, theory, syntaxname, termcon_id::Ident ) - termcon_name = QuoteNode(nameof(termcon_id)) - termcon = getvalue(theory[termcon_id]) +function symbolic_termcon(theorymodule, theory, syntaxname, method::Ident) + termcon = getvalue(theory[method]) + termcon_name = QuoteNode(nameof(getdecl(termcon))) return_type = typename(theory, termcon.type; parentmod=syntaxname) args = if !isempty(termcon.args) map(argsof(termcon)) do argbinding @@ -476,7 +487,7 @@ end function invoke_term(syntax_module::Module, constructor_name::Symbol, args) theory_module = syntax_module.THEORY_MODULE theory = theory_module.THEORY - syntax_types = Tuple(getfield(syntax_module, nameof(cons)) for cons in typecons(theory)) + syntax_types = Tuple(getfield(syntax_module, nameof(sort)) for sort in sorts(theory)) invoke_term(theory_module, syntax_types, constructor_name, args) end @@ -579,7 +590,8 @@ function parse_json_sexpr(syntax_module::Module, sexpr; theory_module = syntax_module.THEORY_MODULE theory = theory_module.THEORY type_lens = Dict( - nameof(binding) => length(getvalue(binding).args) for binding in [theory[tc] for tc in typecons(theory)] + nameof(getdecl(getvalue(binding))) => length(getvalue(binding).args) + for binding in [theory[sort.method] for sort in sorts(theory)] ) function parse_impl(sexpr::Vector, ::Type{Val{:expr}}) diff --git a/src/models/module.jl b/src/models/module.jl index 0e3ec494..d44fc8d2 100644 --- a/src/models/module.jl +++ b/src/models/module.jl @@ -3,11 +3,11 @@ module Models using Reexport include("ModelInterface.jl") -# include("SymbolicModels.jl") -# include("GATExprUtils.jl") +include("SymbolicModels.jl") +include("GATExprUtils.jl") @reexport using .ModelInterface -# @reexport using .SymbolicModels -# @reexport using .GATExprUtils +@reexport using .SymbolicModels +@reexport using .GATExprUtils end diff --git a/src/stdlib/models/module.jl b/src/stdlib/models/module.jl index 569fb3ed..dd69e9e0 100644 --- a/src/stdlib/models/module.jl +++ b/src/stdlib/models/module.jl @@ -8,7 +8,7 @@ include("FinMatrices.jl") include("SliceCategories.jl") include("Op.jl") include("Nothings.jl") -include("GATs.jl") +# include("GATs.jl") @reexport using .FinSets @reexport using .Arithmetic @@ -16,6 +16,6 @@ include("GATs.jl") @reexport using .SliceCategories @reexport using .Op @reexport using .Nothings -@reexport using .GATs +# @reexport using .GATs end diff --git a/src/stdlib/module.jl b/src/stdlib/module.jl index f8dc9a6e..9fbc602f 100644 --- a/src/stdlib/module.jl +++ b/src/stdlib/module.jl @@ -3,12 +3,12 @@ module Stdlib using Reexport include("theories/module.jl") -# include("models/module.jl") +include("models/module.jl") # include("theorymaps/module.jl") # include("derivedmodels/module.jl") @reexport using .StdTheories -# @reexport using .StdModels +@reexport using .StdModels # @reexport using .StdTheoryMaps # @reexport using .StdDerivedModels diff --git a/src/stdlib/theories/Algebra.jl b/src/stdlib/theories/Algebra.jl index 68b8ef89..fee1ef33 100644 --- a/src/stdlib/theories/Algebra.jl +++ b/src/stdlib/theories/Algebra.jl @@ -12,7 +12,7 @@ end end @theory ThMagma <: ThSet begin - (x ⋅ y) :: default + (x ⋅ y) :: default ⊣ [x, y] end @theory ThSemiGroup <: ThMagma begin @@ -20,15 +20,15 @@ end end @theory ThMonoid <: ThSemiGroup begin - e :: default - e ⋅ x == x ⊣ [x] - x ⋅ e == x ⊣ [x] + e() :: default + e() ⋅ x == x ⊣ [x] + x ⋅ e() == x ⊣ [x] end @theory ThGroup <: ThMonoid begin - i(x) - i(x) ⋅ x == e ⊣ [x] - x ⋅ i(x) == e ⊣ [x] + i(x) :: default ⊣ [x] + i(x) ⋅ x == e() ⊣ [x] + x ⋅ i(x) == e() ⊣ [x] end @theory ThCMonoid <: ThMonoid begin @@ -69,11 +69,11 @@ end # end @theory ThPreorder <: ThSet begin - Leq(dom, codom)::TYPE + Leq(dom, codom)::TYPE ⊣ [dom, codom] @op (≤) := Leq refl(p)::Leq(p,p) ⊣ [p] trans(f::Leq(p,q),g::Leq(q,r))::Leq(p,r) ⊣ [p,q,r] - irrev := f == g :: Leq(p,q) ⊣ [p,q, (f,g)::Leq(p,q)] + irrev := f == g ⊣ [p,q, (f,g)::Leq(p,q)] end end diff --git a/src/stdlib/theories/Categories.jl b/src/stdlib/theories/Categories.jl index 34a1a329..d521005d 100644 --- a/src/stdlib/theories/Categories.jl +++ b/src/stdlib/theories/Categories.jl @@ -40,7 +40,7 @@ end The theory of a category with the associative law for composition. """ @theory ThAscCat <: ThLawlessCat begin - assoc := ((f ⋅ g) ⋅ h) == (f ⋅ (g ⋅ h)) :: Hom(a, d) ⊣ + assoc := ((f ⋅ g) ⋅ h) == (f ⋅ (g ⋅ h)) ⊣ [(a, b, c, d)::Ob, f::(a→b), g::(b→c), h::(c→d)] end @@ -57,8 +57,8 @@ end The theory of a category with composition operations and associativity and identity axioms. """ @theory ThCategory <: ThIdLawlessCat begin - idl := id(a) ⋅ f == f :: (a → b) ⊣ [a::Ob, b::Ob, f::Hom(a,b)] - idr := f ⋅ id(b) == f :: (a → b) ⊣ [a::Ob, b::Ob, f::Hom(a,b)] + idl := id(a) ⋅ f == f ⊣ [a::Ob, b::Ob, f::Hom(a,b)] + idr := f ⋅ id(b) == f ⊣ [a::Ob, b::Ob, f::Hom(a,b)] end """ ThThinCategory @@ -67,7 +67,7 @@ The theory of a thin category meaning that if two morphisms have the same domain These are equivalent to preorders. """ @theory ThThinCategory <: ThCategory begin - thineq := f == g :: Hom(a, b) ⊣ [a::Ob, b::Ob, f::Hom(a,b), g::Hom(a,b)] + thineq := f == g ⊣ [a::Ob, b::Ob, f::Hom(a,b), g::Hom(a,b)] end end diff --git a/src/stdlib/theories/Monoidal.jl b/src/stdlib/theories/Monoidal.jl index 67b8d9ee..532bee4b 100644 --- a/src/stdlib/theories/Monoidal.jl +++ b/src/stdlib/theories/Monoidal.jl @@ -16,8 +16,8 @@ end @theory ThStrictMonCat <: ThLawlessMonCat begin (A ⊗ B) ⊗ C == (A ⊗ (B ⊗ C)) :: Ob ⊣ [(A,B,C)::Ob] - I ⊗ A == A :: Ob ⊣ [A::Ob] - A ⊗ I == A :: Ob ⊣ [A::Ob] + I() ⊗ A == A :: Ob ⊣ [A::Ob] + A ⊗ I() == A :: Ob ⊣ [A::Ob] end end diff --git a/src/stdlib/theories/module.jl b/src/stdlib/theories/module.jl index 76273ef4..919bb813 100644 --- a/src/stdlib/theories/module.jl +++ b/src/stdlib/theories/module.jl @@ -3,13 +3,13 @@ module StdTheories using Reexport include("Categories.jl") -# include("Algebra.jl") -# include("Monoidal.jl") -# include("Naturals.jl") +include("Algebra.jl") +include("Monoidal.jl") +include("Naturals.jl") @reexport using .Categories -# @reexport using .Algebra -# @reexport using .Monoidal -# @reexport using .Naturals +@reexport using .Algebra +@reexport using .Monoidal +@reexport using .Naturals end diff --git a/src/syntax/GATs.jl b/src/syntax/GATs.jl index 4e099af1..603eb19e 100644 --- a/src/syntax/GATs.jl +++ b/src/syntax/GATs.jl @@ -3,8 +3,8 @@ export Constant, AlgTerm, AlgType, TypeScope, TypeCtx, AlgSort, AlgSorts, AlgDeclaration, AlgTermConstructor, AlgTypeConstructor, AlgAccessor, AlgAxiom, sortsignature, getdecl, - GATSegment, GAT, GATContext, allmethods, - termcons, typecons, accessors, + GATSegment, GAT, GATContext, allmethods, resolvemethod, + termcons, accessors, equations, build_infer_expr, compile, sortcheck, allnames, sorts, sortname, InCtx, TermInCtx, TypeInCtx, headof, argsof, argcontext diff --git a/src/syntax/TheoryInterface.jl b/src/syntax/TheoryInterface.jl index 7b0308c6..b44c5bb3 100644 --- a/src/syntax/TheoryInterface.jl +++ b/src/syntax/TheoryInterface.jl @@ -59,6 +59,18 @@ macro theory(head, body) push!(modulelines, :(export $(allnames(theory; aliases=true)...))) + for x in keys(theory.resolvers) + decl = getvalue(theory[x]) + if !isnothing(decl.overloads) + push!( + modulelines, + Expr(:import, + Expr(:(:), Expr(:(.), decl.overloads[1:end-1]...), Expr(:(.), decl.overloads[end])) + ) + ) + end + end + if !isnothing(parentname) push!(modulelines, Expr(:using, Expr(:(.), :(.), :(.), parentname))) end @@ -117,12 +129,14 @@ end function invoke_term(theory_module, types, name, args; model=nothing) theory = theory_module.THEORY method = getproperty(theory_module, name) - type_idx = findfirst(==(name), nameof.(typecons(theory))) + type_idx = findfirst(==(name), nameof.(sorts(theory))) if !isnothing(type_idx) && length(args) <= 1 args = method(types[type_idx], args...) elseif isnothing(model) && isempty(args) - termcon = getvalue(theory, ident(theory; name, sig=AlgSort[])) - idx = findfirst(==(termcon.type.head), typecons(theory)) + x = ident(theory; name) + method_id = resolvemethod(theory.resolvers[x], AlgSort[]) + termcon = getvalue(theory[method_id]) + idx = findfirst(==(AlgSort(termcon.type)), sorts(theory)) method(types[idx]) elseif isnothing(model) method(args...) diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl index 7e477151..31eaa1d3 100644 --- a/src/syntax/gats/algorithms.jl +++ b/src/syntax/gats/algorithms.jl @@ -50,47 +50,45 @@ References: finite limits") - (Freyd, 1972, "Aspects of topoi") -This function gives expressions for computing each of the elements of `context` - from the `args`, as well as checking that the args are well-typed. +This function gives expressions for computing the elements of `c.context` + which can be inferred from applying accessor functions to elements of `args`. Example: > equations({f::Hom(a,b), g::Hom(b,c)}, {a::Ob, b::Ob, c::Ob}, ThCategory) ways_of_computing = Dict(a => [dom(f)], b => [codom(f), dom(g)], c => [codom(g)], f => [f], g => [g]) - -Algorithm: - -Start from the arguments. We know how to compute each of the arguments; they are -given. Each argument tells us how to compute other arguments, and also elements -of the context """ -function equations(context::TypeCtx, args::AbstractVector{Ident}, theory::Context; init=nothing) +function equations(c::GATContext, args::AbstractVector{Ident}; init=nothing) + theory = c.theory + context = c.context ways_of_computing = Dict{Ident, Set{AlgTerm}}() - to_expand = Pair{Ident, AlgTerm}[x => x for x in args] + to_expand = Pair{Ident, AlgTerm}[x => AlgTerm(x) for x in args] + if !isnothing(init) append!(to_expand, pairs(init)) end while !isempty(to_expand) - x, expr = pop!(to_expand) + x, t = pop!(to_expand) + if !haskey(ways_of_computing, x) ways_of_computing[x] = Set{AlgTerm}() end - push!(ways_of_computing[x], expr) - xtype = getvalue(context[x]) - xtypecon = getvalue(theory[xtype.head]) + push!(ways_of_computing[x], t) - for (i, arg) in enumerate(xtype.args) - if arg.head isa Constant - continue - elseif arg.head ∈ theory + type = getvalue(context[x]) + typecon = getvalue(theory[type.body.method]) + + for (i, arg) in enumerate(type.body.args) + if isconstant(arg) || isapp(arg) continue else - @assert arg.head ∈ context - a = ident(xtypecon; lid=LID(i)) - y = arg.head - expr′ = AccessorApplication(a, expr) + y = arg.body + @assert y ∈ context + a = theory.accessors[type.body.method][i] + acc = getvalue(theory[a]) + expr′ = AlgTerm(getdecl(acc), a, [t]) push!(to_expand, y => expr′) end end @@ -111,8 +109,9 @@ function equations(theory::GAT, t::TypeInCtx) end """Get equations for a term or type constructor""" -equations(theory::Context, x::Ident) = let x = getvalue(theory[x]); - equations(x, idents(x; lid=x.args),theory) +function equations(theory::GAT, x::Ident) + judgment = getvalue(theory, x) + equations(GATContext(theory, judgment), idents(judgment.localcontext; lid=judgment.args)) end function compile(expr_lookup::Dict{Ident}, term::AlgTerm; theorymodule=nothing) @@ -123,7 +122,7 @@ function compile(expr_lookup::Dict{Ident}, term::AlgTerm; theorymodule=nothing) else esc(name) end - Expr(:call, fun, [compile(expr_lookup, arg; theorymodule) for arg in term.args]...) + Expr(:call, fun, [compile(expr_lookup, arg; theorymodule) for arg in term.body.args]...) elseif isvariable(term) expr_lookup[term.body] elseif isconstant(term) @@ -150,86 +149,17 @@ function InCtx{AlgType}(g::GAT, k::Ident) TypeInCtx(tcon.localcontext, AlgType(k, AlgTerm.(idents(tcon; lid=tcon.args)))) end - -""" -Infer the type of the term of a term. If it is not in context, recurse on its -arguments. The term constructor's output type yields the resulting type once -its localcontext variables are substituted with the relevant AlgTerms. - - (x,y,z)::Ob, p::Hom(x,y), q::Hom(y,z) -E.g. given -------------------------------------- - id(x)⋅(p⋅q) - - (a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c) -and output type: ------------------------------------ - Hom(a,c) - -We first recursively find `{id(x) => Hom(x,x), p⋅q => Hom(x,z)}`. We ultimately -want an AlgTerm for everything in the output type's context such that we can -substitute into `Hom(a,c)` to get the final answer. It will help to also compute -the AlgType for everything in the context. We work backwards, since we start by -knowing `{f => id(x)::Hom(x,x), g=> p⋅q :: Hom(x,z)}`. For `a` `b` and `c`, -we use `equations` which tell us, e.g., that `a = dom(f)`. So we can grab the -first argument of the *type* of `f` (i.e. grab `x` from `Hom(x,x)`). -""" -function infer_type(ctx::Context, t::AlgTerm) - if isvariable(t) - getvalue(ctx[head]) - elseif isconstant(t) - t.body.type - else - typed_terms = bind_localctx(ctx, t.body) - tc = ctx[t.body.method] - substitute_type(tc.type, typed_terms) - end -end - -infer_type(ctx::Context, t::TermInCtx) = infer_type(AppendContext(ctx, t.ctx), t.trm) - -""" -Take a term constructor and determine terms of its local context. - -This function is mutually recursive with `infer_type`. -""" -function bind_localctx(ctx::Context, t::MethodApp{AlgTerm}) - tc = getvalue(ctx[t.method]) - - eqs = equations(ctx, head) - - typed_terms = Dict{Ident, Pair{AlgTerm,AlgType}}() - for (i,a) in zip(tc.args, t.args) - tt = (a => infer_type(ctx, a)) - typed_terms[ident(tc; lid=i)] = tt - end - - for lc_arg in reverse(getidents(tc)) - if getlid(lc_arg) ∈ tc.args - continue - end - # one way of determining lc_arg's value - filt(e) = e isa AccessorApplication && e.to isa Ident - app = first(filter(filt, eqs[lc_arg])) - inferred_term = typed_terms[app.to][2].args[app.accessor.lid.val] - inferred_type = infer_type(ctx, inferred_term) - typed_terms[lc_arg] = inferred_term => inferred_type - end - - Dict([k=>v[1] for (k,v) in pairs(typed_terms)]) -end - -bind_localctx(ctx::Context, t::InCtx) = bind_localctx(AppendContext(ctx, t.ctx), t.trm) - """ Replace idents with AlgTerms. """ -function substitute_term(t::T, dic::Dict{Ident,AlgTerm}) where T<:Union{AlgType, AlgTerm} +function substitute_term(t::T, subst::Dict{Ident,AlgTerm}) where T<:Union{AlgType, AlgTerm} if isvar(t) dic[t.body] elseif isconst(t) t else - T(substitute_term(t.body, dic)) + T(substitute_term(t.body, subst)) end end -function substitute_term(ma::MethodApp{AlgTerm}, dic::Dict{Ident, AlgTerm}) - MethodApp(ma.head, ma.method, substitute_term.(ma.args, Ref(dic))) +function substitute_term(ma::MethodApp{AlgTerm}, subst::Dict{Ident, AlgTerm}) + MethodApp(ma.head, ma.method, substitute_term.(ma.args, Ref(subst))) end diff --git a/src/syntax/gats/ast.jl b/src/syntax/gats/ast.jl index 586b03e2..95435dc1 100644 --- a/src/syntax/gats/ast.jl +++ b/src/syntax/gats/ast.jl @@ -145,6 +145,8 @@ function AlgSort(c::Context, t::AlgTerm) end end +Base.nameof(sort::AlgSort) = nameof(sort.head) + # Type Contexts ############### diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index ad34b9a2..0758a8c2 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -22,7 +22,15 @@ end function fromexpr(c::GATContext, e, ::Type{AlgTerm}) @match e begin - s::Symbol => AlgTerm(fromexpr(c, s, Ident)) + s::Symbol => begin + x = fromexpr(c, s, Ident) + value = getvalue(c[x]) + if value isa AlgType + AlgTerm(fromexpr(c, s, Ident)) + else + error("nullary constructors must be explicitly called: $e") + end + end Expr(:call, head::Symbol, argexprs...) => AlgTerm(parse_methodapp(c, head, argexprs)) Expr(:(::), val, type) => Constant(val, fromexpr(c, type, AlgType)) e::Expr => error("could not parse AlgTerm from $e") @@ -51,6 +59,17 @@ function toexpr(c::Context, type::AlgType) end end +function fromexpr(c::GATContext, e, ::Type{AlgSort}) + e isa Symbol || error("expected a Symbol to parse a sort, got: $e") + decl = ident(c.theory; name=e) + method = only(allmethods(c.theory.resolvers[decl]))[2] + AlgSort(decl, method) +end + +function toexpr(c::GATContext, s::AlgSort) + toexpr(c, getdecl(s)) +end + toexpr(c::Context, constant::Constant; kw...) = Expr(:(::), constant.value, toexpr(c, constant.type; kw...)) @@ -176,17 +195,19 @@ function parseargs!(theory::GAT, exprs::AbstractVector, scope::TypeScope) end end -function parseaxiom!(theory::GAT, localcontext, type_expr, e; name=nothing) +function parseaxiom!(theory::GAT, localcontext, sort_expr, e; name=nothing) @match e begin Expr(:call, :(==), lhs_expr, rhs_expr) => begin c = GATContext(theory, localcontext) equands = fromexpr.(Ref(c), [lhs_expr, rhs_expr], Ref(AlgTerm)) - type = if isnothing(type_expr) - infer_type(c, first(equands)) + sorts = sortcheck.(Ref(c), equands) + @assert allequal(sorts) + sort = if isnothing(sort_expr) + first(sorts) else - fromexpr(c, type_expr, AlgType) + fromexpr(c, sort_expr, AlgSort) end - axiom = AlgAxiom(localcontext, type, equands) + axiom = AlgAxiom(localcontext, sort, equands) Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, axiom)) end _ => error("failed to parse equation from $e") @@ -323,8 +344,13 @@ function parse_gat_line!(theory::GAT, e::Expr, linenumber) end end end - Expr(:import, Expr(:(:), Expr(:(.), mod), imports)) => begin - # create appropriate declarations + Expr(:import, Expr(:(:), Expr(:(.), mod...), imports...)) => begin + imports = map(imports) do expr + expr.args[1] + end + for name in imports + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration([mod; name]))) + end end _ => begin parse_binding_line!(theory, e, linenumber) diff --git a/src/syntax/gats/gat.jl b/src/syntax/gats/gat.jl index 65e99528..37e889bb 100644 --- a/src/syntax/gats/gat.jl +++ b/src/syntax/gats/gat.jl @@ -31,7 +31,11 @@ function MethodResolver() end addmethod!(m::MethodResolver, sig::AlgSorts, method::Ident) = - m.bysignature[sig] = method + if haskey(m.bysignature, sig) + error("method already overloaded for signature: $sig") + else + m.bysignature[sig] = method + end resolvemethod(m::MethodResolver, sig::AlgSorts) = m.bysignature[sig] @@ -53,6 +57,7 @@ struct GAT <: HasScopeList{Judgment} segments::ScopeList{Judgment} resolvers::Dict{Ident, MethodResolver} sorts::Vector{AlgSort} + accessors::Dict{Ident, Dict{Int, Ident}} axioms::Vector{Ident} end @@ -62,6 +67,7 @@ function Base.copy(theory::GAT; name=theory.name) copy(theory.segments), deepcopy(theory.resolvers), copy(theory.sorts), + deepcopy(theory.accessors), copy(theory.axioms) ) end @@ -72,6 +78,7 @@ function GAT(name::Symbol) ScopeList{Judgment}(), Dict{Ident, MethodResolver}(), AlgSort[], + Dict{Ident, Dict{Int, Ident}}(), Ident[] ) end @@ -86,17 +93,19 @@ function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgDeclaration) theory.resolvers[x] = MethodResolver() end -function unsafe_updatecache!( - theory::GAT, - x::Ident, - judgment::Union{AlgTermConstructor, AlgAccessor} -) +function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgTermConstructor) addmethod!(theory.resolvers[getdecl(judgment)], sortsignature(judgment), x) end function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgTypeConstructor) addmethod!(theory.resolvers[getdecl(judgment)], sortsignature(judgment), x) push!(theory.sorts, AlgSort(getdecl(judgment), x)) + theory.accessors[x] = Dict{Int, Ident}() +end + +function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgAccessor) + addmethod!(theory.resolvers[getdecl(judgment)], sortsignature(judgment), x) + theory.accessors[judgment.typecon][judgment.arg] = x end function unsafe_updatecache!(theory::GAT, x::Ident, judgment::AlgAxiom) @@ -140,6 +149,18 @@ end sorts(theory::GAT) = theory.sorts +function termcons(theory::GAT) + xs = Tuple{Ident, Ident}[] + for (decl, resolver) in theory.resolvers + for (_, method) in allmethods(resolver) + if getvalue(theory, method) isa AlgTermConstructor + push!(xs, (decl, method)) + end + end + end + xs +end + Base.issubset(t1::GAT, t2::GAT) = all(s->hastag(t2, s), gettag.(Scopes.getscopelist(t1).scopes)) diff --git a/src/syntax/gats/judgments.jl b/src/syntax/gats/judgments.jl index b9da3611..4d29b172 100644 --- a/src/syntax/gats/judgments.jl +++ b/src/syntax/gats/judgments.jl @@ -127,7 +127,7 @@ A declaration of an axiom """ @struct_hash_equal struct AlgAxiom <: Judgment localcontext::TypeScope - type::AlgType + sort::AlgSort equands::Vector{AlgTerm} end diff --git a/test/models/ModelInterface.jl b/test/models/ModelInterface.jl index c7a1b4ed..b960e62e 100644 --- a/test/models/ModelInterface.jl +++ b/test/models/ModelInterface.jl @@ -118,7 +118,8 @@ g = FinFunction([1,1,1],B,A) @test id(A) == FinFunction([1,2], A, A) @test compose(f,g) == FinFunction([1,1], A, A) @test Hom(f, A, B) == f -@test Hom([2,3], A, B) == f +# TODO: +# @test Hom([2,3], A, B) == f @test_throws TypeCheckFail Hom(f, A, A) @withmodel FinSetC() (mcompose, id) begin diff --git a/test/models/SymbolicModels.jl b/test/models/SymbolicModels.jl index 00867257..e62cf5a2 100644 --- a/test/models/SymbolicModels.jl +++ b/test/models/SymbolicModels.jl @@ -325,8 +325,8 @@ end @theory ThGroupoid <: ThCategory begin invert(f::(A → B))::(B → A) ⊣ [A::Ob, B::Ob] - (f ⋅ invert(f) == id(A)) :: Hom(A, A) ⊣ [A::Ob, B::Ob, f::(A → B)] - (invert(f) ⋅ f == id(B)) :: Hom(B, B) ⊣ [A::Ob, B::Ob, f::(A → B)] + f ⋅ invert(f) == id(A) ⊣ [A::Ob, B::Ob, f::(A → B)] + invert(f) ⋅ f == id(B) ⊣ [A::Ob, B::Ob, f::(A → B)] end using .ThGroupoid @@ -379,8 +379,8 @@ of a functor that's total on objects and partial on morphisms. """ @theory ThPointedSetCategory <: ThCategory begin zeromap(A::Ob,B::Ob)::Hom(A,B) - (compose(zeromap(A,B),f::(B→C))==zeromap(A,C)) :: Hom(A, C) ⊣ [A::Ob,B::Ob,C::Ob] - (compose(g::(A→B),zeromap(A,B))==zeromap(A,C)) :: Hom(A, C) ⊣ [A::Ob,B::Ob,C::Ob] + (compose(zeromap(A,B),f)==zeromap(A,C)) ⊣ [A::Ob,B::Ob,C::Ob,f::(A→B)] + (compose(g,zeromap(A,B))==zeromap(A,C)) ⊣ [A::Ob,B::Ob,C::Ob,g::(A→B)] end @symbolic_model FreePointedSetCategory{ObExpr,HomExpr} ThPointedSetCategory begin diff --git a/test/stdlib/models/Arithmetic.jl b/test/stdlib/models/Arithmetic.jl index 07bb6a39..d1c7291f 100644 --- a/test/stdlib/models/Arithmetic.jl +++ b/test/stdlib/models/Arithmetic.jl @@ -11,36 +11,36 @@ using .ThNatPlus @test S(S(Z())) + Z() == 2 end -# IntMonoid = NatPlusMonoid(IntNatPlus) -#-------------------------------------- -using .ThMonoid - -IM = IntMonoid(IntNatPlus()) -@withmodel IM (e) begin - @test e() == 0 - @test (ThMonoid.:(⋅)[IM])(3, 4) == 7 -end - -# Integers as preorder -#--------------------- -using .ThPreorder - -@withmodel IntPreorder() (Leq, refl, trans) begin - @test trans((1,3), (3,5)) == (1,5) - @test_throws TypeCheckFail Leq((5,3), 5, 3) - @test refl(2) == (2,2) -end - -# Now using category interface - -using .ThCategory -M = IntPreorderCat(IntPreorder()) - -@withmodel M (Hom, id, compose) begin - @test compose((1,3), (3,5)) == (1,5) - @test_throws TypeCheckFail Hom((5,3), 5, 3) - @test_throws ErrorException compose((1,2), (3,5)) - @test id(2) == (2,2) -end +# # IntMonoid = NatPlusMonoid(IntNatPlus) +# #-------------------------------------- +# using .ThMonoid + +# IM = IntMonoid(IntNatPlus()) +# @withmodel IM (e) begin +# @test e() == 0 +# @test (ThMonoid.:(⋅)[IM])(3, 4) == 7 +# end + +# # Integers as preorder +# #--------------------- +# using .ThPreorder + +# @withmodel IntPreorder() (Leq, refl, trans) begin +# @test trans((1,3), (3,5)) == (1,5) +# @test_throws TypeCheckFail Leq((5,3), 5, 3) +# @test refl(2) == (2,2) +# end + +# # Now using category interface + +# using .ThCategory +# M = IntPreorderCat(IntPreorder()) + +# @withmodel M (Hom, id, compose) begin +# @test compose((1,3), (3,5)) == (1,5) +# @test_throws TypeCheckFail Hom((5,3), 5, 3) +# @test_throws ErrorException compose((1,2), (3,5)) +# @test id(2) == (2,2) +# end end # module diff --git a/test/stdlib/models/Op.jl b/test/stdlib/models/Op.jl index 01882147..a9e3f4f7 100644 --- a/test/stdlib/models/Op.jl +++ b/test/stdlib/models/Op.jl @@ -24,17 +24,17 @@ end # Theory-morphism Op #------------------- -M = OpFinSetC(FinSetC()) -@withmodel M (Ob, Hom, id, compose, dom, codom) begin - @test Ob(0) == 0 - @test_throws TypeCheckFail Ob(-1) - @test_throws TypeCheckFail Hom([1,5,2], 4, 3) - @test Hom(Int[], 4, 0) == Int[] - - @test id(2) == [1,2] - @test compose([1,1,1,3,2], [5]) == [2] - @test codom([5]) == 1 -end +# M = OpFinSetC(FinSetC()) +# @withmodel M (Ob, Hom, id, compose, dom, codom) begin +# @test Ob(0) == 0 +# @test_throws TypeCheckFail Ob(-1) +# @test_throws TypeCheckFail Hom([1,5,2], 4, 3) +# @test Hom(Int[], 4, 0) == Int[] + +# @test id(2) == [1,2] +# @test compose([1,1,1,3,2], [5]) == [2] +# @test codom([5]) == 1 +# end end # module diff --git a/test/stdlib/models/tests.jl b/test/stdlib/models/tests.jl index 7893f362..06d4e3ac 100644 --- a/test/stdlib/models/tests.jl +++ b/test/stdlib/models/tests.jl @@ -6,6 +6,6 @@ include("FinMatrices.jl") include("SliceCategories.jl") include("Op.jl") include("Nothings.jl") -include("GATs.jl") +# include("GATs.jl") end diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index b0df8a40..7f36e8d7 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -13,9 +13,9 @@ scope = Scope(:number, :(+), :(_), :(*)) number, plus, plusmethod, times = idents(scope; name=[:number, :(+), :(_), :(*)]) -one = AlgTerm(Constant(1, AlgType(number))) +one = AlgTerm(Constant(1, AlgType(number, number, AlgTerm[]))) -two = AlgTerm(Constant(2, AlgType(number))) +two = AlgTerm(Constant(2, AlgType(number, number, AlgTerm[]))) three = AlgTerm(plus, plusmethod, [one, two]) @@ -27,7 +27,7 @@ three = AlgTerm(plus, plusmethod, [one, two]) @test_throws Exception AlgSort(scope, three) -@test sortcheck(scope, two) == AlgSort(number) +@test sortcheck(scope, two) == AlgSort(number, number) @test_throws Exception sortcheck(scope, three) @@ -40,7 +40,7 @@ seg_expr = quote Hom(dom, codom) :: TYPE ⊣ [dom::Ob, codom::Ob] id(a) :: Hom(a, a) ⊣ [a::Ob] compose(f, g) :: Hom(a, c) ⊣ [a::Ob, b::Ob, c::Ob, f::Hom(a, b), g::Hom(b, c)] - compose(f, compose(g, h)) == compose(compose(f, g), h) :: Hom(a,d) ⊣ [ + compose(f, compose(g, h)) == compose(compose(f, g), h) ⊣ [ a::Ob, b::Ob, c::Ob, d::Ob, f::Hom(a, b), g::Hom(b, c), h::Hom(c, d) ] @@ -100,18 +100,11 @@ TG = ThGraph.THEORY # InCtx #---------- -tic = fromexpr(T, :(compose(f,compose(id(b),id(b))) ⊣ [a::Ob, b::Ob, f::Hom(a,b)]), TermInCtx); -tic2 = fromexpr(T,toexpr(T, tic), TermInCtx) # same modulo scope tags +# tic = fromexpr(T, :(compose(f,compose(id(b),id(b))) ⊣ [a::Ob, b::Ob, f::Hom(a,b)]), TermInCtx); +# tic2 = fromexpr(T,toexpr(T, tic), TermInCtx) # same modulo scope tags -typic = fromexpr(T, :(Hom(a,b) ⊣ [a::Ob, b::Ob, f::Hom(a,b)]), TypeInCtx) -typic2 = fromexpr(T,toexpr(T, typic), TypeInCtx) # same modulo scope tags - -# Type inference -#--------------- - -t = fromexpr(T,:(id(x)⋅(p⋅q) ⊣ [(x,y,z)::Ob, p::Hom(x,y), q::Hom(y,z)]), TermInCtx) -expected = fromexpr(AppendScope(T, t.ctx), :(Hom(x,z)), AlgType) -@test Syntax.GATs.infer_type(T, t) == expected +# typic = fromexpr(T, :(Hom(a,b) ⊣ [a::Ob, b::Ob, f::Hom(a,b)]), TypeInCtx) +# typic2 = fromexpr(T,toexpr(T, typic), TypeInCtx) # same modulo scope tags end # module diff --git a/test/syntax/Scopes.jl b/test/syntax/Scopes.jl index 12972a24..64725a4c 100644 --- a/test/syntax/Scopes.jl +++ b/test/syntax/Scopes.jl @@ -108,7 +108,7 @@ xy_scope′ = Scope([bind_x]; tag=tag1) @test_throws ScopeTagError getlid(xy_scope; tag=tag2) @test_throws KeyError getlid(xy_scope; name=:z) @test ident(xy_scope; name=:x) == x -@test nameof(ident(xy_scope; name=:X)) == :X +@test nameof(ident(xy_scope; name=:X)) == :x @test ident(xy_scope; lid=LID(1)) == x @test_throws BoundsError ident(xy_scope; name=:x, level=2) @test hasident(xy_scope, x) diff --git a/test/syntax/TheoryInterface.jl b/test/syntax/TheoryInterface.jl index c69055b3..cfa15b3c 100644 --- a/test/syntax/TheoryInterface.jl +++ b/test/syntax/TheoryInterface.jl @@ -37,10 +37,6 @@ using .ThLawlessCategory Hom(dom::Ob, codom::Ob) :: TYPE end -@test_throws Exception @eval @theory ThWeirdCategory <: ThCategory begin - dom(x::Ob, f::Hom(a, b)) :: Hom(a, b) ⊣ [(a,b)::Ob] -end - @test_throws Exception @eval @theory ThBadAliases <: ThCategory begin @op 1 + 1 end diff --git a/test/syntax/tests.jl b/test/syntax/tests.jl index 47033529..6df34491 100644 --- a/test/syntax/tests.jl +++ b/test/syntax/tests.jl @@ -15,17 +15,16 @@ end include("GATs.jl") end -@testset "Presentations" begin - include("Presentations.jl") -end +# @testset "Presentations" begin +# include("Presentations.jl") +# end @testset "TheoryInterface" begin include("TheoryInterface.jl") end -@testset "TheoryMaps" begin - include("TheoryMaps.jl") -end - +# @testset "TheoryMaps" begin +# include("TheoryMaps.jl") +# end end From 8bdb6a4d35d0946bfa29a9e33c44c8781deac84b Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Tue, 3 Oct 2023 21:28:33 -0700 Subject: [PATCH 04/12] presentations working --- src/syntax/Presentations.jl | 25 ++++++++++++++----------- src/syntax/TheoryMaps.jl | 6 +++--- src/syntax/gats/algorithms.jl | 2 +- src/syntax/gats/exprinterop.jl | 14 +++++++++----- src/syntax/module.jl | 4 ++-- test/syntax/Presentations.jl | 30 +++++++++++++++--------------- test/syntax/tests.jl | 6 +++--- 7 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/syntax/Presentations.jl b/src/syntax/Presentations.jl index e9f425cd..f42b48e6 100644 --- a/src/syntax/Presentations.jl +++ b/src/syntax/Presentations.jl @@ -2,21 +2,22 @@ module Presentations export Presentation, @present using ...Util -using ..Scopes, ..GATs, ..ExprInterop +using ..Scopes, ..GATs using StructEquality using MLStyle +import ..ExprInterop: fromexpr, toexpr """ A presentation has a set of generators, given by a `TypeScope`, and a set of equations among terms which can refer to those generators. Each element of `eqs` is a list of terms which are asserted to be equal. """ -@struct_hash_equal struct Presentation <: HasContext{AlgType, Nothing} +@struct_hash_equal struct Presentation <: HasContext{AlgType} theory::GAT scope::TypeScope eqs::Vector{Vector{AlgTerm}} function Presentation(gat, scope, eqs=[]) - gatscope = AppendScope(gat, scope) + gatscope = AppendContext(gat, scope) # scope terms must be defined in GAT sortcheck.(Ref(gatscope), getvalue.(scope)) # eq terms must be defined in GAT ++ scope @@ -28,12 +29,14 @@ equations among terms which can refer to those generators. Each element of end end -Scopes.getcontext(p::Presentation) = AppendScope(p.theory, p.scope) +Scopes.getcontext(p::Presentation) = GATContext(p.theory, p.scope) + +fromexpr(p::Presentation, e, t) = fromexpr(Scopes.getcontext(p), e, t) """Context of presentation is the underlying GAT""" -ExprInterop.toexpr(p::Presentation) = toexpr(p.theory, p) +toexpr(p::Presentation) = toexpr(p.theory, p) -function ExprInterop.toexpr(c::Context, p::Presentation) +function toexpr(c::Context, p::Presentation) c == p.theory || error("Invalid context for presentation") decs = GATs.bindingexprs(c, p.scope) eqs = map(p.eqs) do ts @@ -55,7 +58,7 @@ h′::Hom(a, c) compose(f, g) == h == h′ ``` """ -function ExprInterop.fromexpr(ctx::Context, e, ::Type{Presentation}) +function fromexpr(ctx::GATContext, e, ::Type{Presentation}) e.head == :block || error("expected a block to parse into a GATSegment, got: $e") scopelines, eqlines = [], Vector{Expr0}[] for line in e.args @@ -70,13 +73,13 @@ function ExprInterop.fromexpr(ctx::Context, e, ::Type{Presentation}) push!(scopelines, line) end end - scope = GATs.parsetypescope(ctx, scopelines) - apscope = AppendScope(ctx, scope) - Presentation(ctx, scope, [fromexpr.(Ref(apscope), ts, AlgTerm) for ts in eqlines]) + scope = fromexpr(ctx, Expr(:block, scopelines...), TypeScope) + apscope = AppendContext(ctx, scope) + Presentation(ctx.theory, scope, [fromexpr.(Ref(apscope), ts, AlgTerm) for ts in eqlines]) end function construct_presentation(m::Module, e) - fromexpr(m.THEORY, e, Presentation) + fromexpr(GATContext(m.THEORY), e, Presentation) end macro present(head, body) diff --git a/src/syntax/TheoryMaps.jl b/src/syntax/TheoryMaps.jl index 3679ce66..0f8feb7a 100644 --- a/src/syntax/TheoryMaps.jl +++ b/src/syntax/TheoryMaps.jl @@ -217,7 +217,7 @@ function toexpr(m::AbsTheoryMap) typs, trms = map([typemap(m), termmap(m)]) do tm map(collect(tm)) do (k,v) domterm = toexpr(dom(m), InCtx(dom(m), k)) - Expr(:call, :(=>), domterm, toexpr(AppendScope(codom(m), v.ctx), v.trm)) + Expr(:call, :(=>), domterm, toexpr(AppendContext(codom(m), v.ctx), v.trm)) end end Expr(:block, typs...,trms...) @@ -237,7 +237,7 @@ function fromexpr(dom::GAT, codom::GAT, e, ::Type{TheoryMap}) e1, e2 = @match expr begin Expr(:call, :(=>), e1, e2) => (e1,e2) end flat_term, ctx = @match e1 begin Expr(:call, :⊣, flat_term, Expr(:vect, typescope...)) => begin - flat_term, GATs.parsetypescope(dom, typescope) + flat_term, fromexpr(dom, typescope, TypeScope) end _ => (e1, TypeScope()) end @@ -258,7 +258,7 @@ function fromexpr(dom::GAT, codom::GAT, e, ::Type{TheoryMap}) reorder_init = Dict(zip(getvalue.(getlid.(args)), getvalue.(tc.args))) reordered_ctx = reorder(ctx, tc.localcontext, reorder_init) fctx = pushforward(dom, typs, trms, reordered_ctx) - val = fromexpr(AppendScope(codom, fctx), e2, T) + val = fromexpr(AppendContext(codom, fctx), e2, T) dic = T == AlgType ? typs : trms dic[x] = InCtx{T}(fctx, val) end diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl index 31eaa1d3..ef8ab16b 100644 --- a/src/syntax/gats/algorithms.jl +++ b/src/syntax/gats/algorithms.jl @@ -21,7 +21,7 @@ function sortcheck(ctx::Context, t::AlgTerm)::AlgSort end # sortcheck(ctx::Context, t::TermInCtx)::AlgSort = -# sortcheck(AppendScope(ctx, t.ctx), t.trm) +# sortcheck(AppendContext(ctx, t.ctx), t.trm) """ `sortcheck(ctx::Context, t::AlgType)` diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index 0758a8c2..e4680650 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -32,7 +32,7 @@ function fromexpr(c::GATContext, e, ::Type{AlgTerm}) end end Expr(:call, head::Symbol, argexprs...) => AlgTerm(parse_methodapp(c, head, argexprs)) - Expr(:(::), val, type) => Constant(val, fromexpr(c, type, AlgType)) + Expr(:(::), val, type) => AlgTerm(Constant(val, fromexpr(c, type, AlgType))) e::Expr => error("could not parse AlgTerm from $e") constant::Constant => AlgTerm(constant) end @@ -75,7 +75,7 @@ toexpr(c::Context, constant::Constant; kw...) = function fromexpr(c::GATContext, e, ::Type{InCtx{T}}; kw...) where T (termexpr, localcontext) = @match e begin - Expr(:call, :(⊣), binding, Expr(:vect, args...)) => (binding, parsetypescope(c, args)) + Expr(:call, :(⊣), binding, Expr(:vect, args...)) => (binding, fromexpr(c, args, TypeScope)) e => (e, TypeScope()) end term = fromexpr(AppendContext(c, localcontext), termexpr, T) @@ -83,10 +83,10 @@ function fromexpr(c::GATContext, e, ::Type{InCtx{T}}; kw...) where T end function toexpr(c::Context, tic::InCtx; kw...) - c′ = AppendScope(c, tic.ctx) + c′ = AppendContext(c, tic.ctx) etrm = toexpr(c′, tic.trm; kw...) flat = Scopes.flatten(tic.ctx) - ectx = toexpr(AppendScope(c,flat), flat; kw...) + ectx = toexpr(AppendContext(c,flat), flat; kw...) Expr(:call, :(⊣), etrm, ectx) end @@ -128,7 +128,11 @@ end function judgmenthead(theory::GAT, name, judgment::AlgAxiom) c = GATContext(theory, judgment.localcontext) untyped = Expr(:call, :(==), toexpr(c, judgment.equands[1]), toexpr(c, judgment.equands[2])) - Expr(:(::), untyped, toexpr(c, judgment.type)) + if isnothing(name) + untyped + else + Expr(:(:=), name, untyped) + end end function toexpr(c::GAT, binding::Binding{Judgment}) diff --git a/src/syntax/module.jl b/src/syntax/module.jl index 3e1ccc0b..10aff645 100644 --- a/src/syntax/module.jl +++ b/src/syntax/module.jl @@ -5,14 +5,14 @@ using Reexport include("Scopes.jl") include("ExprInterop.jl") include("GATs.jl") -# include("Presentations.jl") +include("Presentations.jl") include("TheoryInterface.jl") # include("TheoryMaps.jl") @reexport using .Scopes @reexport using .ExprInterop @reexport using .GATs -# @reexport using .Presentations +@reexport using .Presentations @reexport using .TheoryInterface # @reexport using .TheoryMaps diff --git a/test/syntax/Presentations.jl b/test/syntax/Presentations.jl index 6c29ed98..f91f197d 100644 --- a/test/syntax/Presentations.jl +++ b/test/syntax/Presentations.jl @@ -1,25 +1,25 @@ module TestPresentations using GATlab -using GATlab.Syntax.GATs: parsetypescope using Test -T = ThCategory.THEORY - -tscope = parsetypescope( - T, - :([(a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c), (h,h′)::Hom(a,c)]).args +T = ThCategory.THEORY; +ctx = GATContext(T); +tscope = fromexpr( + ctx, + :([(a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c), (h,h′)::Hom(a,c)]), + TypeScope ) _, _, _, f, g, h, h′ = getidents(tscope) h, h′ = AlgTerm.([h,h′]) -fg = fromexpr(AppendScope(T, tscope), :(compose(f,g)), AlgTerm) -p1 = Presentation(T, tscope, [[fg, h]]) +fg = fromexpr(AppendContext(ctx, tscope), :(compose(f,g)), AlgTerm) +p1 = Presentation(T, tscope, [[fg, h]]); x1 = toexpr(p1) -p1′ = fromexpr(T,x1, Presentation); +p1′ = fromexpr(ctx,x1, Presentation); @test length(only(p1′.eqs)) == 2 -p2 = Presentation(T, tscope, [[fg, h, h′]]) +p2 = Presentation(T, tscope, [[fg, h, h′]]); x2 = toexpr(p2) -p2′ = fromexpr(T,x2, Presentation); +p2′ = fromexpr(ctx,x2, Presentation); @test length(only(p2′.eqs)) == 3 # HasContext interface @@ -35,12 +35,12 @@ p2′ = fromexpr(T,x2, Presentation); (E,V)::Ob src::Hom(E,V) tgt::Hom(E,V) -end +end; src, tgt = idents(SchGraph; name=[:src, :tgt]) Hom = ident(SchGraph; name=:Hom) -@test getvalue(SchGraph[src]).head == Hom +@test getvalue(SchGraph[src]).body.head == Hom; @present Z(ThGroup) begin (a,) @@ -54,8 +54,8 @@ a = ident(Z; name=:a) @present D₄(ThGroup) begin (r,f) :: default - (f⋅f) == e - (r⋅r⋅r⋅r) == e + (f⋅f) == e() + (r⋅r⋅r⋅r) == e() end end # module diff --git a/test/syntax/tests.jl b/test/syntax/tests.jl index 6df34491..a3022376 100644 --- a/test/syntax/tests.jl +++ b/test/syntax/tests.jl @@ -15,9 +15,9 @@ end include("GATs.jl") end -# @testset "Presentations" begin -# include("Presentations.jl") -# end +@testset "Presentations" begin + include("Presentations.jl") +end @testset "TheoryInterface" begin include("TheoryInterface.jl") From 4a97f1885bf0c698276015f9a7132c933578b809 Mon Sep 17 00:00:00 2001 From: Owen Lynch Date: Wed, 4 Oct 2023 16:13:03 -0700 Subject: [PATCH 05/12] fixes for catlab --- src/syntax/TheoryInterface.jl | 10 ++++- src/syntax/gats/ast.jl | 2 + src/syntax/gats/exprinterop.jl | 72 +++++++++++++++++++--------------- src/syntax/gats/gat.jl | 14 +++++-- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/syntax/TheoryInterface.jl b/src/syntax/TheoryInterface.jl index b44c5bb3..7aedbf5c 100644 --- a/src/syntax/TheoryInterface.jl +++ b/src/syntax/TheoryInterface.jl @@ -1,5 +1,5 @@ module TheoryInterface -export @theory, Model, invoke_term +export @theory, @signature, Model, invoke_term using ..Scopes, ..GATs, ..ExprInterop @@ -39,7 +39,15 @@ dictionary pointing to the module corresponding to the new theory. """ const GAT_MODULE_LOOKUP = Dict{ScopeTag, Module}() +macro signature(head, body) + theory_impl(head, body, __module__) +end + macro theory(head, body) + theory_impl(head, body, __module__) +end + +function theory_impl(head, body, __module__) (name, parentname) = @match head begin (name::Symbol) => (name, nothing) Expr(:(<:), name, parent) => (name, parent) diff --git a/src/syntax/gats/ast.jl b/src/syntax/gats/ast.jl index 95435dc1..afb9b51a 100644 --- a/src/syntax/gats/ast.jl +++ b/src/syntax/gats/ast.jl @@ -147,6 +147,8 @@ end Base.nameof(sort::AlgSort) = nameof(sort.head) +getdecl(s::AlgSort) = s.head + # Type Contexts ############### diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index e4680650..b09f36e1 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -5,7 +5,11 @@ function parse_methodapp(c::GATContext, head::Symbol, argexprs) args = Vector{AlgTerm}(fromexpr.(Ref(c), argexprs, Ref(AlgTerm))) fun = fromexpr(c, head, Ident) signature = AlgSort.(Ref(c), args) - method = methodlookup(c, fun, signature) + method = try + methodlookup(c, fun, signature) + catch e + error("couldn't find method for $(Expr(:call, head, argexprs...))") + end MethodApp{AlgTerm}(fun, method, args) end @@ -222,7 +226,7 @@ function parseconstructor!(theory::GAT, localcontext, type_expr, e) (name, arglist) = @match e begin Expr(:call, name, args...) => (name, args) name::Symbol => (name, []) - _ => error("failed to parse head of term constructor $head") + _ => error("failed to parse head of term constructor $e") end args = parseargs!(theory, arglist, localcontext) @match type_expr begin @@ -324,41 +328,45 @@ function toexpr(theory::GAT, seg::GATSegment) end function parse_gat_line!(theory::GAT, e::Expr, linenumber) - @match e begin - Expr(:macrocall, var"@op", _, aliasexpr) => begin - lines = @match aliasexpr begin - Expr(:block, lines...) => lines - _ => [aliasexpr] - end - for line in lines - @switch line begin - @case (_::LineNumberNode) - nothing - @case :($alias := $name) - # check if there is already a declaration for name, if not, create declaration - decl = if hasname(theory, name) - ident(theory; name) - else - Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration(nothing))) - end - binding = Binding{Judgment}(alias, Alias(decl), linenumber) - Scopes.unsafe_pushbinding!(theory, binding) - @case _ - error("could not match @op expression $line") + try + @match e begin + Expr(:macrocall, var"@op", _, aliasexpr) => begin + lines = @match aliasexpr begin + Expr(:block, lines...) => lines + _ => [aliasexpr] + end + for line in lines + @switch line begin + @case (_::LineNumberNode) + nothing + @case :($alias := $name) + # check if there is already a declaration for name, if not, create declaration + decl = if hasname(theory, name) + ident(theory; name) + else + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration(nothing))) + end + binding = Binding{Judgment}(alias, Alias(decl), linenumber) + Scopes.unsafe_pushbinding!(theory, binding) + @case _ + error("could not match @op expression $line") + end end end - end - Expr(:import, Expr(:(:), Expr(:(.), mod...), imports...)) => begin - imports = map(imports) do expr - expr.args[1] + Expr(:import, Expr(:(:), Expr(:(.), mod...), imports...)) => begin + imports = map(imports) do expr + expr.args[1] + end + for name in imports + Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration([mod; name]))) + end end - for name in imports - Scopes.unsafe_pushbinding!(theory, Binding{Judgment}(name, AlgDeclaration([mod; name]))) + _ => begin + parse_binding_line!(theory, e, linenumber) end end - _ => begin - parse_binding_line!(theory, e, linenumber) - end + catch _ + error("error parsing expression $e at line $linenumber") end end diff --git a/src/syntax/gats/gat.jl b/src/syntax/gats/gat.jl index 37e889bb..64a6bdcd 100644 --- a/src/syntax/gats/gat.jl +++ b/src/syntax/gats/gat.jl @@ -37,7 +37,9 @@ addmethod!(m::MethodResolver, sig::AlgSorts, method::Ident) = m.bysignature[sig] = method end -resolvemethod(m::MethodResolver, sig::AlgSorts) = m.bysignature[sig] +function resolvemethod(m::MethodResolver, sig::AlgSorts) + m.bysignature[sig] +end allmethods(m::MethodResolver) = pairs(m.bysignature) @@ -83,6 +85,7 @@ function GAT(name::Symbol) ) end + # Mutators which should only be called during construction of a theory function unsafe_newsegment!(theory::GAT) @@ -184,6 +187,11 @@ Scopes.getcontext(c::GATContext) = AppendContext(c.theory, c.context) Scopes.AppendContext(c::GATContext, context::Context{AlgType}) = GATContext(c.theory, AppendContext(c.context, context)) -function methodlookup(c::GATContext, decl::Ident, sig::AlgSorts) - resolvemethod(c.theory.resolvers[decl], sig) +function methodlookup(c::GATContext, x::Ident, sig::AlgSorts) + theory = c.theory + if haskey(theory.resolvers, x) && haskey(theory.resolvers[x].bysignature, sig) + resolvemethod(theory.resolvers[x], sig) + else + error("no method of $x found with signature $(getdecl.(sig))") + end end From 27b3f51b4dc2d2a3a0fc6696f90f247ad3007d48 Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Wed, 4 Oct 2023 17:23:57 -0700 Subject: [PATCH 06/12] theory map tests passing, AlgAST --- src/stdlib/module.jl | 4 +- src/stdlib/theorymaps/Maps.jl | 6 +- src/syntax/GATs.jl | 7 +- src/syntax/Presentations.jl | 4 +- src/syntax/Scopes.jl | 20 +++++ src/syntax/TheoryMaps.jl | 154 +++++++++++++++++++++++++-------- src/syntax/gats/algorithms.jl | 32 +++---- src/syntax/gats/ast.jl | 16 +++- src/syntax/gats/exprinterop.jl | 14 +-- src/syntax/gats/gat.jl | 17 +++- src/syntax/gats/judgments.jl | 3 + src/syntax/module.jl | 4 +- test/syntax/GATs.jl | 4 +- test/syntax/TheoryMaps.jl | 17 ++-- test/syntax/tests.jl | 6 +- 15 files changed, 220 insertions(+), 88 deletions(-) diff --git a/src/stdlib/module.jl b/src/stdlib/module.jl index 9fbc602f..411ada00 100644 --- a/src/stdlib/module.jl +++ b/src/stdlib/module.jl @@ -4,12 +4,12 @@ using Reexport include("theories/module.jl") include("models/module.jl") -# include("theorymaps/module.jl") +include("theorymaps/module.jl") # include("derivedmodels/module.jl") @reexport using .StdTheories @reexport using .StdModels -# @reexport using .StdTheoryMaps +@reexport using .StdTheoryMaps # @reexport using .StdDerivedModels end diff --git a/src/stdlib/theorymaps/Maps.jl b/src/stdlib/theorymaps/Maps.jl index d7261db9..7544d86d 100644 --- a/src/stdlib/theorymaps/Maps.jl +++ b/src/stdlib/theorymaps/Maps.jl @@ -5,15 +5,15 @@ export SwapMonoid, NatPlusMonoid, PreorderCat, OpCat using ...StdTheories using ....Syntax -SwapMonoid = @theorymap ThMonoid => ThMonoid begin +@theorymap SwapMonoid(ThMonoid, ThMonoid) begin default => default x⋅y ⊣ [x, y] => y⋅x - e => e + e() => e() end @theorymap NatPlusMonoid(ThMonoid, ThNatPlus) begin default => ℕ - e => Z + e() => Z() (x ⋅ y) ⊣ [x, y] => x+y end diff --git a/src/syntax/GATs.jl b/src/syntax/GATs.jl index 603eb19e..1b1591ea 100644 --- a/src/syntax/GATs.jl +++ b/src/syntax/GATs.jl @@ -1,12 +1,12 @@ module GATs -export Constant, AlgTerm, AlgType, +export Constant, AlgTerm, AlgType, AlgAST, TypeScope, TypeCtx, AlgSort, AlgSorts, AlgDeclaration, AlgTermConstructor, AlgTypeConstructor, AlgAccessor, AlgAxiom, sortsignature, getdecl, GATSegment, GAT, GATContext, allmethods, resolvemethod, - termcons, accessors, + termcons,typecons, accessors, equations, build_infer_expr, compile, sortcheck, allnames, sorts, sortname, - InCtx, TermInCtx, TypeInCtx, headof, argsof, argcontext + InCtx, TermInCtx, TypeInCtx, headof, argsof, methodof, bodyof, argcontext using ..Scopes import ..ExprInterop: fromexpr, toexpr @@ -15,6 +15,7 @@ import ..Scopes: retag, rename using StructEquality using MLStyle +using DataStructures: OrderedDict include("gats/ast.jl") include("gats/judgments.jl") diff --git a/src/syntax/Presentations.jl b/src/syntax/Presentations.jl index f42b48e6..087a64ab 100644 --- a/src/syntax/Presentations.jl +++ b/src/syntax/Presentations.jl @@ -78,9 +78,7 @@ function fromexpr(ctx::GATContext, e, ::Type{Presentation}) Presentation(ctx.theory, scope, [fromexpr.(Ref(apscope), ts, AlgTerm) for ts in eqlines]) end -function construct_presentation(m::Module, e) - fromexpr(GATContext(m.THEORY), e, Presentation) -end +construct_presentation(m::Module, e) = fromexpr(m.THEORY, e, Presentation) macro present(head, body) (theory, name) = @match head begin diff --git a/src/syntax/Scopes.jl b/src/syntax/Scopes.jl index f25767e3..36190ea1 100644 --- a/src/syntax/Scopes.jl +++ b/src/syntax/Scopes.jl @@ -352,6 +352,8 @@ struct Scope{T} <: HasScope{T} end end +Scope(s::Scope) = s + Base.:(==)(s1::Scope, s2::Scope) = s1.tag == s2.tag Base.hash(s::Scope, h::UInt64) = hash(s.tag, h) @@ -697,6 +699,24 @@ function ScopeList{T}(scopes::Vector{<:HasScope{T}}) where {T} c end +function Scope(hsl::ScopeList{T}) where T + if nscopes(hsl) == 0 + Scope{T}() + else + res = Scope{T}() + newtag = gettag(res) + retagdict = Dict{ScopeTag, ScopeTag}() + for nextscope in getscope.(hsl.scopes) + retagdict[gettag(nextscope)] = newtag + nextscope = retag(retagdict, nextscope) + for b in getbindings(nextscope) + unsafe_pushbinding!(res, b) + end + end + res + end +end + function Base.copy(c::ScopeList{T}) where {T} ScopeList{T}(copy(c.scopes), copy(c.taglookup), copy(c.namelookup)) end diff --git a/src/syntax/TheoryMaps.jl b/src/syntax/TheoryMaps.jl index 0f8feb7a..7b477f01 100644 --- a/src/syntax/TheoryMaps.jl +++ b/src/syntax/TheoryMaps.jl @@ -4,7 +4,7 @@ export IdTheoryMap, TheoryIncl, AbsTheoryMap, TheoryMap, @theorymap, using ..GATs, ..Scopes, ..ExprInterop using ..Scopes: unsafe_pushbinding! -using ..GATs: InCtx, TrmTyp, bindingexprs, bind_localctx, substitute_term +using ..GATs: InCtx, bindingexprs, substitute_term using ..TheoryInterface import ..ExprInterop: toexpr, fromexpr @@ -64,7 +64,7 @@ end function (f::AbsTheoryMap)(t::InCtx{T}) where T fctx = f(t.ctx) - InCtx{T}(fctx, f(t.ctx, t.trm; fctx=fctx)) + InCtx{T}(fctx, f(t.ctx, t.val; fctx=fctx)) end """ @@ -75,15 +75,14 @@ function pushforward( dom::GAT, tymap::OrderedDict{Ident, TypeInCtx}, trmap::OrderedDict{Ident,TermInCtx}, - ctx::TypeCtx + scope::TypeCtx ) fctx = TypeScope() - scope = Scopes.flatten(ctx) for i in 1:length(scope) b = scope[LID(i)] partial_scope = Scope(getbindings(scope)[1:i-1]; tag=gettag(scope)) val = pushforward(dom, tymap, trmap, partial_scope, getvalue(b); fctx=fctx) - new_binding = Binding{AlgType, Nothing}(b.primary, val, b.sig) + new_binding = Binding{AlgType}(nameof(b), val) unsafe_pushbinding!(fctx, new_binding) end fctx @@ -93,27 +92,29 @@ end function pushforward( dom::GAT, tymap::OrderedDict{Ident, TypeInCtx}, - trmap::OrderedDict{Ident,TermInCtx}, + trmap::OrderedDict{Ident, TermInCtx}, ctx::TypeCtx, t::T; fctx=nothing -) where {T<:TrmTyp} +) where T<:AlgAST fctx = isnothing(fctx) ? f(ctx) : fctx - head = headof(t) - if hasident(ctx, head) + b = bodyof(t) + if GATs.isvariable(t) + hasident(ctx, b) || error("Unknown variable $t") retag(Dict(gettag(ctx)=>gettag(fctx)), t) # term is already in the context else + head = methodof(b) tcon = getvalue(dom[head]) # Toplevel Term (or Type) Constructor of t in domain new_term = pushforward(tymap, trmap, head) # Codom TermInCtx associated with the toplevel tcon # idents in bind_localctx refer to term constructors args and l.c. rt_dict = Dict(gettag(tcon.localcontext)=>gettag(new_term.ctx)) # new_term has same context as tcon, so recursively map over components - lc = bind_localctx(dom, InCtx{T}(ctx, t)) + lc = bind_localctx(GATContext(dom), InCtx{T}(ctx, t)) flc = Dict{Ident, AlgTerm}(map(collect(pairs(lc))) do (k, v) retag(rt_dict, k) => pushforward(dom, tymap, trmap, ctx, v; fctx) end) - substitute_term(new_term.trm, flc) + substitute_term(new_term.val, flc) end end @@ -127,6 +128,78 @@ function pushforward(tymap, trmap, s::Ident) end end + +""" +Infer the type of the term of a term. If it is not in context, recurse on its +arguments. The term constructor's output type yields the resulting type once +its localcontext variables are substituted with the relevant AlgTerms. + + (x,y,z)::Ob, p::Hom(x,y), q::Hom(y,z) +E.g. given -------------------------------------- + id(x)⋅(p⋅q) + + (a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c) +and output type: ------------------------------------ + Hom(a,c) + +We first recursively find `{id(x) => Hom(x,x), p⋅q => Hom(x,z)}`. We ultimately +want an AlgTerm for everything in the output type's context such that we can +substitute into `Hom(a,c)` to get the final answer. It will help to also compute +the AlgType for everything in the context. We work backwards, since we start by +knowing `{f => id(x)::Hom(x,x), g=> p⋅q :: Hom(x,z)}`. For `a` `b` and `c`, +we use `equations` which tell us, e.g., that `a = dom(f)`. So we can grab the +first argument of the *type* of `f` (i.e. grab `x` from `Hom(x,x)`). +""" +function infer_type(ctx::Context, t::AlgTerm) + b = bodyof(t) + if GATs.isvariable(t) + getvalue(ctx[b]) + else + head = methodof(b) + tc = getvalue(ctx[head]) + typed_terms = bind_localctx(ctx, t) + typ = bodyof(tc.type) + args = substitute_term.(argsof(typ), Ref(typed_terms)) + AlgType(headof(typ), methodof(typ), args) + end +end + +infer_type(ctx::Context, t::TermInCtx) = infer_type(AppendScope(ctx, t.ctx), t.val) + +""" +Take a term constructor and determine terms of its local context. + +This function is mutually recursive with `infer_type`. +""" +bind_localctx(ctx::GATContext, t::InCtx) = + bind_localctx(GATContext(ctx.theory, AppendContext(ctx.context, t.ctx)), t.val) + +function bind_localctx(ctx::GATContext, t::AlgAST) + m = GATs.methodof(t.body) + tc = getvalue(ctx[m]) + eqs = equations(ctx.theory, m) + typed_terms = Dict{Ident, Pair{AlgTerm,AlgType}}() + for (i,a) in zip(tc.args, t.body.args) + tt = (a => infer_type(ctx, a)) + typed_terms[ident(tc, lid=i, level=1)] = tt + end + + for lc_arg in reverse(getidents(getcontext(tc))) + if getlid(lc_arg) ∈ tc.args + continue + end + # one way of determining lc_arg's value + app = bodyof(first(filter(GATs.isapp, eqs[lc_arg]))) + to = bodyof(only(argsof(app))) + m = methodof(app) + inferred_term = typed_terms[to][2].body.args[getvalue(ctx.theory[m]).arg] + inferred_type = infer_type(ctx, inferred_term) + typed_terms[lc_arg] = inferred_term => inferred_type + end + + Dict([k=>v[1] for (k,v) in pairs(typed_terms)]) +end + # ID #--- @struct_hash_equal struct IdTheoryMap <: AbsTheoryMap @@ -163,10 +236,10 @@ A theory inclusion has a subset of scopes end typemap(ι::Union{IdTheoryMap,TheoryIncl}) = - OrderedDict(k => TypeInCtx(dom(ι), k) for k in typecons(dom(ι))) + OrderedDict(k => TypeInCtx(dom(ι), k) for k in last.(typecons(dom(ι)))) termmap(ι::Union{IdTheoryMap,TheoryIncl}) = - OrderedDict(k=>TermInCtx(dom(ι), k) for k in termcons(dom(ι))) + OrderedDict(k=>TermInCtx(dom(ι), k) for k in last.(termcons(dom(ι)))) compose(f::TheoryIncl, g::TheoryIncl) = TheoryIncl(dom(f), codom(g)) @@ -190,10 +263,10 @@ TODO: check that it is well-formed, axioms are preserved. typemap::OrderedDict{Ident,TypeInCtx} termmap::OrderedDict{Ident,TermInCtx} function TheoryMap(dom, codom, typmap, trmmap) - missing_types = setdiff(Set(keys(typmap)), Set(typecons(dom))) - missing_terms = setdiff(Set(keys(trmmap)), Set(termcons(dom))) + missing_types = setdiff(Set(keys(typmap)), Set(last.(typecons(dom)))) + missing_terms = setdiff(Set(keys(trmmap)), Set(last.(termcons(dom)))) isempty(missing_types) || error("Missing types $missing_types") - isempty(missing_terms) || error("Missing types $missing_terms") + isempty(missing_terms) || error("Missing terms $missing_terms") tymap′, trmap′ = map([typmap, trmmap]) do tmap OrderedDict(k => v isa Ident ? InCtx(codom, v) : v for (k,v) in pairs(tmap)) @@ -217,7 +290,7 @@ function toexpr(m::AbsTheoryMap) typs, trms = map([typemap(m), termmap(m)]) do tm map(collect(tm)) do (k,v) domterm = toexpr(dom(m), InCtx(dom(m), k)) - Expr(:call, :(=>), domterm, toexpr(AppendContext(codom(m), v.ctx), v.trm)) + Expr(:call, :(=>), domterm, toexpr(AppendContext(codom(m), v.ctx), v.val)) end end Expr(:block, typs...,trms...) @@ -236,8 +309,8 @@ function fromexpr(dom::GAT, codom::GAT, e, ::Type{TheoryMap}) for expr in exprs e1, e2 = @match expr begin Expr(:call, :(=>), e1, e2) => (e1,e2) end flat_term, ctx = @match e1 begin - Expr(:call, :⊣, flat_term, Expr(:vect, typescope...)) => begin - flat_term, fromexpr(dom, typescope, TypeScope) + Expr(:call, :⊣, flat_term, tscope) => begin + flat_term, fromexpr(dom, tscope, TypeScope) end _ => (e1, TypeScope()) end @@ -246,21 +319,22 @@ function fromexpr(dom::GAT, codom::GAT, e, ::Type{TheoryMap}) Expr(:call, f::Symbol, args...) => (f, args) end - is_term = xname ∈ nameof.(termcons(dom)) + is_term = xname ∈ nameof.(first.(termcons(dom))) T = is_term ? AlgTerm : AlgType args = idents(ctx; name=argnames) - sig = is_term ? [AlgSort(getvalue(ctx[i])) for i in args] : nothing - x = ident(dom; name=xname, sig) + sig = [AlgSort(getvalue(ctx[i])) for i in args] + x = ident(dom; name=xname) + m = GATs.methodlookup(GATContext(dom), x, sig) # reorder the context to match that of the canonical localctx + args - tc = getvalue(dom[x]) + tc = getvalue(dom[m]) reorder_init = Dict(zip(getvalue.(getlid.(args)), getvalue.(tc.args))) reordered_ctx = reorder(ctx, tc.localcontext, reorder_init) fctx = pushforward(dom, typs, trms, reordered_ctx) - val = fromexpr(AppendContext(codom, fctx), e2, T) + val = fromexpr(GATContext(codom, fctx), e2, T) dic = T == AlgType ? typs : trms - dic[x] = InCtx{T}(fctx, val) + dic[m] = InCtx{T}(fctx, val) end TheoryMap(dom, codom, typs, trms) end @@ -272,13 +346,15 @@ reorder([(B,C,A)::Ob, G::B→C, F::A→B], [(a,b,c)::Ob, f::a→b, g::b→c], {4 Is the reordered first context: [(A,B,C)::Ob, F::A→B, G::B→C] """ -function reorder(domctx::Scope{T,Sig}, codomctx::Scope{T, Sig}, perm::Dict{Int,Int}) where {T,Sig} +function reorder(domctx::TypeScope, codomctx::TypeScope, perm::Dict{Int,Int}) N = length(domctx) N == length(codomctx) || error("Mismatched lengths $N != $(length(codomctx))") for dom_i in reverse(1:N) codom_i = perm[dom_i] dom_lids, codom_lids = map([domctx=>dom_i, codomctx=>codom_i]) do (ctx, i) - getvalue.(getlid.(headof.(argsof(getvalue(ctx[LID(i)]))))) + map(bodyof.(argsof(getvalue(ctx[LID(i)]).body))) do arg + getvalue(getlid(arg)) + end end for (dom_j, codom_j) in zip(dom_lids, codom_lids) if !haskey(perm, dom_j) @@ -289,22 +365,28 @@ function reorder(domctx::Scope{T,Sig}, codomctx::Scope{T, Sig}, perm::Dict{Int,I end end isperm(collect(values(perm))) || error("We need to permute the LIDs") - Scope([reorder(domctx[LID(perm[i])], gettag(domctx), perm) for i in 1:N], - aliases=domctx.aliases, tag=gettag(domctx)) + + TypeScope(Binding{AlgType}[reorder(domctx[LID(perm[i])], gettag(domctx), perm) for i in 1:N], + tag=gettag(domctx)) end """Change LIDs recursively""" -function reorder(t::T, tag::ScopeTag, perm::Dict{Int,Int}) where T <: TrmTyp - args = AlgTerm[reorder.(argsof(t), Ref(tag), Ref(perm))...] - head = headof(t) - if head isa Ident && gettag(head) == tag - T(Ident(gettag(head), LID(perm[getvalue(getlid(head))]), nameof(head)), args) +function reorder(t::T, tag::ScopeTag, perm::Dict{Int,Int}) where T<:AlgAST + b = bodyof(t) + if GATs.isvariable(t) + if gettag(b) == tag + AlgTerm(Ident(gettag(b), LID(perm[getvalue(getlid(b))]), nameof(b))) + else + t + end else - T(head, args) + args = AlgTerm[reorder.(argsof(b), Ref(tag), Ref(perm))...] + T(headof(b), methodof(b), args) end end -reorder(b::Binding{T, Sig}, tag::ScopeTag, perm::Dict{Int,Int}) where {T,Sig} = + +reorder(b::Binding{T}, tag::ScopeTag, perm::Dict{Int,Int}) where {T} = setvalue(b, reorder(getvalue(b), tag, perm)) diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl index ef8ab16b..63b3a2b1 100644 --- a/src/syntax/gats/algorithms.jl +++ b/src/syntax/gats/algorithms.jl @@ -20,9 +20,6 @@ function sortcheck(ctx::Context, t::AlgTerm)::AlgSort end end -# sortcheck(ctx::Context, t::TermInCtx)::AlgSort = -# sortcheck(AppendContext(ctx, t.ctx), t.trm) - """ `sortcheck(ctx::Context, t::AlgType)` @@ -97,13 +94,13 @@ function equations(c::GATContext, args::AbstractVector{Ident}; init=nothing) end function equations(theory::GAT, t::TypeInCtx) - tc = getvalue(theory[headof(t.trm)]) - extended = ScopeList([t.ctx, Scope([Binding{AlgType, Nothing}(nothing, t.trm)])]) + tc = getvalue(theory[headof(t.val)]) + extended = ScopeList([t.ctx, Scope([Binding{AlgType, Nothing}(nothing, t.val)])]) lastx = last(getidents(extended)) - accessor_args = zip(idents(tc.localcontext; lid=tc.args), t.trm.args) + accessor_args = zip(idents(tc.localcontext; lid=tc.args), t.val.args) init = Dict{Ident, AlgTerm}(map(accessor_args) do (accessor, arg) hasident(t.ctx, headof(arg)) || error("Case not yet handled") - headof(arg) => AccessorApplication(accessor, lastx) + headof(arg) => AlgType(headof(t.val), accessor, lastx) end) equations(extended, Ident[], theory; init=init) end @@ -134,11 +131,12 @@ InCtx(g::GAT, k::Ident) = (getvalue(g[k]) isa AlgTermConstructor ? TermInCtx : TypeInCtx)(g, k) """ -Get the canonical term + ctx associated with a term constructor. +Get the canonical term + ctx associated with a method. """ -function InCtx{AlgTerm}(g::GAT, k::Ident) +function InCtx{T}(g::GAT, k::Ident) where T<:AlgAST tcon = getvalue(g[k]) - TermInCtx(tcon.localcontext, AlgTerm(k, AlgTerm.(idents(tcon; lid=tcon.args)))) + args = T.(idents(tcon.localcontext; lid=tcon.args)) + TermInCtx(tcon.localcontext, T(tcon.declaration, k, args)) end """ @@ -146,14 +144,16 @@ Get the canonical type + ctx associated with a type constructor. """ function InCtx{AlgType}(g::GAT, k::Ident) tcon = getvalue(g[k]) - TypeInCtx(tcon.localcontext, AlgType(k, AlgTerm.(idents(tcon; lid=tcon.args)))) + args = AlgTerm[AlgTerm.(idents(tcon.localcontext; lid=tcon.args))...] + dec = getvalue(g[k]).declaration + TypeInCtx(tcon.localcontext, AlgType(MethodApp(dec, k, args))) end """ Replace idents with AlgTerms. """ -function substitute_term(t::T, subst::Dict{Ident,AlgTerm}) where T<:Union{AlgType, AlgTerm} - if isvar(t) - dic[t.body] - elseif isconst(t) +function substitute_term(t::T, subst::Dict{Ident,AlgTerm}) where T <: AlgAST + if isvariable(t) + subst[t.body] + elseif isconstant(t) t else T(substitute_term(t.body, subst)) @@ -161,5 +161,5 @@ function substitute_term(t::T, subst::Dict{Ident,AlgTerm}) where T<:Union{AlgTyp end function substitute_term(ma::MethodApp{AlgTerm}, subst::Dict{Ident, AlgTerm}) - MethodApp(ma.head, ma.method, substitute_term.(ma.args, Ref(subst))) + MethodApp{AlgTerm}(ma.head, ma.method, substitute_term.(ma.args, Ref(subst))) end diff --git a/src/syntax/gats/ast.jl b/src/syntax/gats/ast.jl index afb9b51a..18e1867e 100644 --- a/src/syntax/gats/ast.jl +++ b/src/syntax/gats/ast.jl @@ -40,18 +40,23 @@ retag(reps::Dict{ScopeTag, ScopeTag}, t::MethodApp{T}) where {T} = retag.(Ref(reps), t.args) ) +abstract type AlgAST end + +bodyof(t::AlgAST) = t.body + """ `AlgTerm` One syntax tree to rule all the terms. """ -@struct_hash_equal struct AlgTerm +@struct_hash_equal struct AlgTerm <: AlgAST body::Union{Ident, MethodApp{AlgTerm}, AbstractConstant} function AlgTerm(body::Union{Ident, MethodApp{AlgTerm}, AbstractConstant}) new(body) end end + const EMPTY_ARGS = AlgTerm[] function AlgTerm(fun::Ident, method::Ident, args::Vector{AlgTerm}) @@ -76,12 +81,17 @@ retag(reps::Dict{ScopeTag, ScopeTag}, t::AlgTerm) = AlgTerm(retag(reps, t.body)) `AlgType` One syntax tree to rule all the types. -`head` must be reference to a `AlgTypeConstructor` """ -@struct_hash_equal struct AlgType +@struct_hash_equal struct AlgType <: AlgAST body::MethodApp{AlgTerm} end +function AlgType(fun::Ident, method::Ident) + AlgType(MethodApp{AlgTerm}(fun, method, EMPTY_ARGS)) +end + +bodyof(t::AlgType) = t.body + isvariable(t::AlgType) = false isapp(t::AlgType) = true diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index b09f36e1..e078aa77 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -1,5 +1,7 @@ # AST ##### +"""Coerce GATs to GAT contexts""" +fromexpr(g::GAT, e, t) = fromexpr(GATContext(g), e, t) function parse_methodapp(c::GATContext, head::Symbol, argexprs) args = Vector{AlgTerm}(fromexpr.(Ref(c), argexprs, Ref(AlgTerm))) @@ -79,18 +81,18 @@ toexpr(c::Context, constant::Constant; kw...) = function fromexpr(c::GATContext, e, ::Type{InCtx{T}}; kw...) where T (termexpr, localcontext) = @match e begin - Expr(:call, :(⊣), binding, Expr(:vect, args...)) => (binding, fromexpr(c, args, TypeScope)) + Expr(:call, :(⊣), binding, tscope) => (binding, fromexpr(c, tscope, TypeScope)) e => (e, TypeScope()) end term = fromexpr(AppendContext(c, localcontext), termexpr, T) - InCtx{T}(localcontext, t) + InCtx{T}(localcontext, term) end function toexpr(c::Context, tic::InCtx; kw...) c′ = AppendContext(c, tic.ctx) - etrm = toexpr(c′, tic.trm; kw...) - flat = Scopes.flatten(tic.ctx) - ectx = toexpr(AppendContext(c,flat), flat; kw...) + etrm = toexpr(c′, tic.val; kw...) + flat = TypeScope(tic.ctx) + ectx = toexpr(c, flat; kw...) Expr(:call, :(⊣), etrm, ectx) end @@ -289,7 +291,7 @@ function parse_binding_line!(theory::GAT, e, linenumber) (binding, localcontext) = @match e begin Expr(:call, :(⊣), binding, ctxexpr) && if ctxexpr.head == :vect end => - (binding, fromexpr(GATContext(theory), ctxexpr, TypeScope)) + (binding, fromexpr(theory, ctxexpr, TypeScope)) e => (e, TypeScope()) end diff --git a/src/syntax/gats/gat.jl b/src/syntax/gats/gat.jl index 64a6bdcd..9f2ef364 100644 --- a/src/syntax/gats/gat.jl +++ b/src/syntax/gats/gat.jl @@ -57,9 +57,9 @@ GATs allow overloading but not shadowing. struct GAT <: HasScopeList{Judgment} name::Symbol segments::ScopeList{Judgment} - resolvers::Dict{Ident, MethodResolver} + resolvers::OrderedDict{Ident, MethodResolver} sorts::Vector{AlgSort} - accessors::Dict{Ident, Dict{Int, Ident}} + accessors::OrderedDict{Ident, Dict{Int, Ident}} axioms::Vector{Ident} end @@ -164,6 +164,19 @@ function termcons(theory::GAT) xs end +function typecons(theory::GAT) + xs = Tuple{Ident, Ident}[] + for (decl, resolver) in theory.resolvers + for (_, method) in allmethods(resolver) + if getvalue(theory, method) isa AlgTypeConstructor + push!(xs, (decl, method)) + end + end + end + xs +end + + Base.issubset(t1::GAT, t2::GAT) = all(s->hastag(t2, s), gettag.(Scopes.getscopelist(t1).scopes)) diff --git a/src/syntax/gats/judgments.jl b/src/syntax/gats/judgments.jl index 4d29b172..2f26aae7 100644 --- a/src/syntax/gats/judgments.jl +++ b/src/syntax/gats/judgments.jl @@ -12,11 +12,14 @@ struct TypeScope <: HasScope{AlgType} end TypeScope() = TypeScope(Scope{AlgType}()) +TypeScope(t::TypeScope) = t TypeScope(bindings::Vector{Binding{AlgType}}; tag=newscopetag()) = TypeScope(Scope(bindings; tag)) TypeScope(bindings::Pair{Symbol, AlgType}...) = TypeScope(Scope{AlgType}(bindings...)) Scopes.getscope(ts::TypeScope) = ts.scope +Scopes.unsafe_pushbinding!(ts::TypeScope, b) = + Scopes.unsafe_pushbinding!(ts.scope, b) function Base.show(io::IO, ts::TypeScope) print(io, toexpr(EmptyContext{AlgType}(), ts)) diff --git a/src/syntax/module.jl b/src/syntax/module.jl index 10aff645..b5ee07a3 100644 --- a/src/syntax/module.jl +++ b/src/syntax/module.jl @@ -7,13 +7,13 @@ include("ExprInterop.jl") include("GATs.jl") include("Presentations.jl") include("TheoryInterface.jl") -# include("TheoryMaps.jl") +include("TheoryMaps.jl") @reexport using .Scopes @reexport using .ExprInterop @reexport using .GATs @reexport using .Presentations @reexport using .TheoryInterface -# @reexport using .TheoryMaps +@reexport using .TheoryMaps end diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index 7f36e8d7..e1d6659f 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -21,7 +21,7 @@ three = AlgTerm(plus, plusmethod, [one, two]) @test toexpr(scope, three) == :((1::number) + (2::number)) -@test fromexpr(GATContext(GAT(:Empty), TypeScope()), two.body, AlgTerm) == two +@test fromexpr(GAT(:Empty), two.body, AlgTerm) == two @test basicprinted(two) == "AlgTerm(2::number)" @@ -50,7 +50,7 @@ thcat = fromexpr(GAT(:ThCat), seg_expr, GAT) O, H, i, cmp = idents(thcat; name=[:Ob, :Hom, :id, :compose]) -ObT = fromexpr(GATContext(thcat), :Ob, AlgType) +ObT = fromexpr(thcat, :Ob, AlgType) ObS = AlgSort(ObT) # Extend seg with a context of (A: Ob) diff --git a/test/syntax/TheoryMaps.jl b/test/syntax/TheoryMaps.jl index 3dc2ed99..ce11f01f 100644 --- a/test/syntax/TheoryMaps.jl +++ b/test/syntax/TheoryMaps.jl @@ -14,6 +14,7 @@ TNP = ThNatPlus.THEORY PC = PreorderCat.MAP NP = NatPlusMonoid.MAP + # TheoryMaps ############ x = toexpr(PC) @@ -60,21 +61,23 @@ end # Test PreorderCat -(Ob, Hom), (Cmp, Id) = typecons(T), termcons(T) -@test PC(Ob).trm == AlgType(ident(TP; name=:default)) -@test PC(Cmp) isa TermInCtx +((Ob, om), (Hom, Hm)), ((Cmp, cm), (Id, im)) = typecons(T), termcons(T) +((Def, dm), (Leq, lm)), ((Refl, rm), (Tr, tm)) = typecons(TP), termcons(TP) + +@test PC(om).val == AlgType(Def, dm) +@test PC(cm).val.body.method == tm -@test PC(getvalue(T[Cmp]).localcontext) isa TypeScope +@test PC(getvalue(T[cm]).localcontext) isa TypeScope -@test_throws KeyError PC(first(typecons(TP))) +@test_throws KeyError PC(dm) xterm = fromexpr(TM, :(x ⊣ [x]), TermInCtx) res = NP(xterm) toexpr(ThNat.THEORY, res) -xterm = fromexpr(TM, :(e⋅(e⋅x) ⊣ [x]), TermInCtx) +xterm = fromexpr(TM, :(e()⋅(e()⋅x) ⊣ [x]), TermInCtx) res = NP(xterm) -expected = fromexpr(TNP, :(Z+(Z+x) ⊣ [x::ℕ]), TermInCtx) +expected = fromexpr(TNP, :(Z()+(Z()+x) ⊣ [x::ℕ]), TermInCtx) @test toexpr(TNP, res) == toexpr(TNP, expected) # Test OpCat diff --git a/test/syntax/tests.jl b/test/syntax/tests.jl index a3022376..26a94697 100644 --- a/test/syntax/tests.jl +++ b/test/syntax/tests.jl @@ -23,8 +23,8 @@ end include("TheoryInterface.jl") end -# @testset "TheoryMaps" begin -# include("TheoryMaps.jl") -# end +@testset "TheoryMaps" begin + include("TheoryMaps.jl") +end end From 700050800262a0bb880595cf99ad1759a203f49e Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Thu, 5 Oct 2023 14:46:09 -0700 Subject: [PATCH 07/12] model migration revived --- src/models/ModelInterface.jl | 277 ++++++++++++++++--------------- src/stdlib/models/module.jl | 4 +- src/stdlib/module.jl | 4 +- src/syntax/TheoryMaps.jl | 2 +- src/syntax/gats/algorithms.jl | 17 +- src/syntax/gats/ast.jl | 7 +- test/stdlib/models/Arithmetic.jl | 62 +++---- test/stdlib/models/GATs.jl | 2 +- test/stdlib/models/Op.jl | 22 +-- test/stdlib/models/tests.jl | 2 +- 10 files changed, 205 insertions(+), 194 deletions(-) diff --git a/src/models/ModelInterface.jl b/src/models/ModelInterface.jl index a570d971..5dcd0b9a 100644 --- a/src/models/ModelInterface.jl +++ b/src/models/ModelInterface.jl @@ -258,7 +258,7 @@ function default_accessor_impl(x::Ident, theory::GAT, jltype_by_sort::Dict{AlgSo end julia_signature(theory::GAT, x::Ident, jltype_by_sort::Dict{AlgSort}) = - julia_signature(theory, x, getvalue(theory[x]), jltype_by_sort) + julia_signature(getvalue(theory[x]), jltype_by_sort; X=x) function julia_signature( termcon::AlgTermConstructor, @@ -502,140 +502,145 @@ macro withmodel(model, subsexpr, body) end -# """ -# Given a Theory Morphism T->U and a type Mᵤ (whose values are models of U), -# obtain a type Mₜ which has one parameter (of type Mᵤ) and is a model of T. - -# E.g. given NatIsMonoid: ThMonoid->ThNatPlus and IntPlus <: Model{Tuple{Int}} -# and IntPlus implements ThNatPlus: - -# ``` -# @migrate IntPlusMonoid = NatIsMonoid(IntPlus){Int} -# ``` - -# Yields: - -# ``` -# struct IntPlusMonoid <: Model{Tuple{Int}} -# model::IntPlus -# end - -# @instance ThMonoid{Int} [model::IntPlusMonoid] begin ... end -# ``` - -# Future work: There is some subtlety in how accessor functions should be handled. -# TODO: The new instance methods do not yet handle the `context` keyword argument. -# """ -# macro migrate(head) -# # Parse -# (name, mapname, modelname) = @match head begin -# Expr(:(=), name, Expr(:call, mapname, modelname)) => -# (name, mapname, modelname) -# _ => error("could not parse head of @theory: $head") -# end -# codom_types = :(only(supertype($(esc(modelname))).parameters).types) -# # Unpack -# tmap = macroexpand(__module__, :($mapname.@map)) -# dom_module = macroexpand(__module__, :($mapname.@dom)) -# codom_module = macroexpand(__module__, :($mapname.@codom)) -# dom_theory, codom_theory = TheoryMaps.dom(tmap), TheoryMaps.codom(tmap) - -# codom_jltype_by_sort = Dict{Ident,Expr0}(map(enumerate(sorts(codom_theory))) do (i,v) -# v.ref => Expr(:ref, codom_types, i) -# end) -# _x = gensym("val") - -# dom_types = map(sorts(dom_theory)) do s -# codom_jltype_by_sort[typemap(tmap)[s.ref].trm.head] -# end -# jltype_by_sort = Dict(zip(sorts(dom_theory), dom_types)) - -# # TypeCons for @instance macro -# funs = map(collect(typemap(tmap))) do (x, fx) -# xname = nameof(x) -# fxname = nameof(fx.trm.head) -# tc = getvalue(dom_theory[x]) -# jltype_by_sort[AlgSort(fx.trm.head)] = jltype_by_sort[AlgSort(x)] -# sig = julia_signature(dom_theory, x, jltype_by_sort) - -# argnames = [_x, nameof.(argsof(tc))...] -# args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] - -# impls = to_call_impl.(fx.trm.args, Ref(termcons(codom_theory)), Ref(codom_module)) -# impl = Expr(:call, Expr(:ref, :($codom_module.$fxname), :(model.model)), _x, impls...) -# JuliaFunction(;name=xname, args=args, return_type=sig.types[1], impl=impl) -# end - -# # TermCons for @instance macro -# funs2 = map(collect(termmap(tmap))) do (x, fx) -# tc = getvalue(dom_theory[x]) - -# sig = julia_signature(dom_theory, x, jltype_by_sort) -# argnames = nameof.(argsof(tc)) -# ret_type = jltype_by_sort[AlgSort(typemap(tmap)[tc.type.head].trm.head)] - -# args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] - -# impl = to_call_impl(fx.trm, termcons(codom_theory), codom_module) - -# JuliaFunction(;name=nameof(x), args=args, return_type=ret_type, impl=impl) -# end - -# funs3 = [] # accessors -# for (x, fx) in pairs(typemap(tmap)) -# tc = getvalue(dom_theory[x]) -# eq = equations(codom_theory, fx) -# args = [:($_x::$(jltype_by_sort[AlgSort(fx.trm.head)]))] -# scopedict = Dict{ScopeTag,ScopeTag}(gettag(tc.localcontext)=>gettag(fx.ctx)) -# for accessor in idents(tc.localcontext; lid=tc.args) -# accessor = retag(scopedict, accessor) -# a = nameof(accessor) -# # If we have a default means of computing the accessor... -# if !isempty(eq[accessor]) -# rtype = tc.localcontext[ident(tc.localcontext; name=a)] -# ret_type = jltype_by_sort[AlgSort(getvalue(rtype))] -# impl = to_call_impl(first(eq[accessor]), _x, codom_module) -# jf = JuliaFunction(;name=a, args=args, return_type=ret_type, impl=impl) -# push!(funs3, jf) -# end -# end -# end - -# model_expr = Expr( -# :curly, -# GlobalRef(Syntax.TheoryInterface, :Model), -# Expr(:curly, :Tuple, dom_types...) -# ) - -# quote -# struct $(esc(name)) <: $model_expr -# model :: $(esc(modelname)) -# end - -# @instance $dom_module [model :: $(esc(name))] begin -# $(generate_function.([funs...,funs2..., funs3...])...) -# end -# end -# end - -# """ -# Compile an AlgTerm into a Julia call Expr where termcons (e.g. `f`) are -# interpreted as `mod.f[model.model](...)`. -# """ -# function to_call_impl(t::AlgTerm, termcons, mod::Module) -# args = to_call_impl.(t.args, Ref(termcons), Ref(mod)) -# name = nameof(headof(t)) -# if t.head in termcons -# Expr(:call, Expr(:ref, :($mod.$name), :(model.model)), args...) -# else -# isempty(args) || error("Bad term $t (termcons=$termcons)") -# name -# end -# end - -# function to_call_impl(t::GATs.AccessorApplication, x::Symbol, mod::Module) -# rest = t.to isa Ident ? x : to_call_impl(t.to, x, mod) -# Expr(:call, Expr(:ref, :($mod.$(nameof(t.accessor))), :(model.model)), rest) -# end +""" +Given a Theory Morphism T->U and a type Mᵤ (whose values are models of U), +obtain a type Mₜ which has one parameter (of type Mᵤ) and is a model of T. + +E.g. given NatIsMonoid: ThMonoid->ThNatPlus and IntPlus <: Model{Tuple{Int}} +and IntPlus implements ThNatPlus: + +``` +@migrate IntPlusMonoid = NatIsMonoid(IntPlus){Int} +``` + +Yields: + +``` +struct IntPlusMonoid <: Model{Tuple{Int}} + model::IntPlus +end + +@instance ThMonoid{Int} [model::IntPlusMonoid] begin ... end +``` + +Future work: There is some subtlety in how accessor functions should be handled. +TODO: The new instance methods do not yet handle the `context` keyword argument. +""" +macro migrate(head) + # Parse + (name, mapname, modelname) = @match head begin + Expr(:(=), name, Expr(:call, mapname, modelname)) => + (name, mapname, modelname) + _ => error("could not parse head of @theory: $head") + end + codom_types = :(only(supertype($(esc(modelname))).parameters).types) + # Unpack + tmap = macroexpand(__module__, :($mapname.@map)) + dom_module = macroexpand(__module__, :($mapname.@dom)) + codom_module = macroexpand(__module__, :($mapname.@codom)) + dom_theory, codom_theory = TheoryMaps.dom(tmap), TheoryMaps.codom(tmap) + + codom_jltype_by_sort = Dict{Ident,Expr0}(map(enumerate(sorts(codom_theory))) do (i,v) + v.method => Expr(:ref, codom_types, i) + end) + _x = gensym("val") + dom_types = map(methodof.(sorts(dom_theory))) do s + codom_jltype_by_sort[typemap(tmap)[s].val.body.method] + end + jltype_by_sort = Dict(zip(sorts(dom_theory), dom_types)) + + # TypeCons for @instance macro + funs = map(collect(typemap(tmap))) do (x, fx) + tcon = getvalue(dom_theory[x]) + fxbody = bodyof(fx.val) + fxdecl, fxmethod = headof(fxbody), methodof(fxbody) + fxname = nameof(fxdecl) + xdecl = tcon.declaration + xname = nameof(xdecl) + jltype_by_sort[AlgSort(fxdecl, fxmethod)] = jltype_by_sort[AlgSort(xdecl, x)] + sig = julia_signature(dom_theory, x, jltype_by_sort) + argnames = [_x, nameof.(argsof(tcon))...] + args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] + impls = to_call_impl.(fxbody.args, Ref(termcons(codom_theory)), Ref(codom_module)) + impl = Expr(:call, Expr(:ref, :($codom_module.$fxname), :(model.model)), _x, impls...) + JuliaFunction(;name=xname, args=args, return_type=sig.types[1], impl=impl) + end + + # TermCons for @instance macro + funs2 = map(collect(termmap(tmap))) do (x, fx) + tcon = getvalue(dom_theory[x]) + xname = nameof(tcon.declaration) + sig = julia_signature(dom_theory, x, jltype_by_sort) + argnames = nameof.(argsof(tcon)) + ftype = typemap(tmap)[tcon.type.body.method].val.body + ret_type = jltype_by_sort[AlgSort(headof(ftype), methodof(ftype))] + + args = [:($k::$v) for (k, v) in zip(argnames, sig.types)] + + impl = to_call_impl(fx.val, first.(termcons(codom_theory)), codom_module) + + JuliaFunction(;name=xname, args=args, return_type=ret_type, impl=impl) + end + + funs3 = [] # accessors + for (x, fx) in pairs(typemap(tmap)) + tc = getvalue(dom_theory[x]) + eq = equations(codom_theory, fx) + args = [:($_x::$(jltype_by_sort[AlgSort(fx.val)]))] + scopedict = Dict{ScopeTag,ScopeTag}(gettag(tc.localcontext)=>gettag(fx.ctx)) + for accessor in idents(tc.localcontext; lid=tc.args) + accessor = retag(scopedict, accessor) + a = nameof(accessor) + # If we have a default means of computing the accessor... + if !isempty(eq[accessor]) + rtype = tc.localcontext[ident(tc.localcontext; name=a)] + ret_type = jltype_by_sort[AlgSort(getvalue(rtype))] + impl = to_call_accessor(first(eq[accessor]), _x, codom_module) + jf = JuliaFunction(;name=a, args=args, return_type=ret_type, impl=impl) + push!(funs3, jf) + end + end + end + + model_expr = Expr( + :curly, + GlobalRef(Syntax.TheoryInterface, :Model), + Expr(:curly, :Tuple, dom_types...) + ) + + quote + struct $(esc(name)) <: $model_expr + model :: $(esc(modelname)) + end + + @instance $dom_module [model :: $(esc(name))] begin + $(generate_function.([funs...,funs2..., funs3...])...) + end + end +end + +""" +Compile an AlgTerm into a Julia call Expr where termcons (e.g. `f`) are +interpreted as `mod.f[model.model](...)`. +""" +function to_call_impl(t::AlgTerm, termcons, mod::Module) + b = bodyof(t) + if GATs.isvariable(t) + nameof(b) + else + args = to_call_impl.(argsof(b), Ref(termcons), Ref(mod)) + name = nameof(headof(b)) + b.head in termcons || error("t $t termcons $termcons") + Expr(:call, Expr(:ref, :($mod.$name), :(model.model)), args...) + end +end + +function to_call_accessor(t::AlgTerm, x::Symbol, mod::Module) + b = bodyof(t) + arg = only(b.args) + rest = GATs.isvariable(arg) ? x : to_call_accessor(arg, x, mod) + Expr(:call, Expr(:ref, :($mod.$(nameof(headof(b)))), :(model.model)), rest) +end + end # module diff --git a/src/stdlib/models/module.jl b/src/stdlib/models/module.jl index dd69e9e0..569fb3ed 100644 --- a/src/stdlib/models/module.jl +++ b/src/stdlib/models/module.jl @@ -8,7 +8,7 @@ include("FinMatrices.jl") include("SliceCategories.jl") include("Op.jl") include("Nothings.jl") -# include("GATs.jl") +include("GATs.jl") @reexport using .FinSets @reexport using .Arithmetic @@ -16,6 +16,6 @@ include("Nothings.jl") @reexport using .SliceCategories @reexport using .Op @reexport using .Nothings -# @reexport using .GATs +@reexport using .GATs end diff --git a/src/stdlib/module.jl b/src/stdlib/module.jl index 411ada00..742306b3 100644 --- a/src/stdlib/module.jl +++ b/src/stdlib/module.jl @@ -5,11 +5,11 @@ using Reexport include("theories/module.jl") include("models/module.jl") include("theorymaps/module.jl") -# include("derivedmodels/module.jl") +include("derivedmodels/module.jl") @reexport using .StdTheories @reexport using .StdModels @reexport using .StdTheoryMaps -# @reexport using .StdDerivedModels +@reexport using .StdDerivedModels end diff --git a/src/syntax/TheoryMaps.jl b/src/syntax/TheoryMaps.jl index 7b477f01..9bec2558 100644 --- a/src/syntax/TheoryMaps.jl +++ b/src/syntax/TheoryMaps.jl @@ -175,7 +175,7 @@ bind_localctx(ctx::GATContext, t::InCtx) = bind_localctx(GATContext(ctx.theory, AppendContext(ctx.context, t.ctx)), t.val) function bind_localctx(ctx::GATContext, t::AlgAST) - m = GATs.methodof(t.body) + m = methodof(t.body) tc = getvalue(ctx[m]) eqs = equations(ctx.theory, m) typed_terms = Dict{Ident, Pair{AlgTerm,AlgType}}() diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl index 63b3a2b1..7e69c9c0 100644 --- a/src/syntax/gats/algorithms.jl +++ b/src/syntax/gats/algorithms.jl @@ -94,15 +94,16 @@ function equations(c::GATContext, args::AbstractVector{Ident}; init=nothing) end function equations(theory::GAT, t::TypeInCtx) - tc = getvalue(theory[headof(t.val)]) - extended = ScopeList([t.ctx, Scope([Binding{AlgType, Nothing}(nothing, t.val)])]) - lastx = last(getidents(extended)) - accessor_args = zip(idents(tc.localcontext; lid=tc.args), t.val.args) - init = Dict{Ident, AlgTerm}(map(accessor_args) do (accessor, arg) - hasident(t.ctx, headof(arg)) || error("Case not yet handled") - headof(arg) => AlgType(headof(t.val), accessor, lastx) + b = bodyof(t.val) + m = methodof(b) + newscope = Scope([Binding{AlgType}(nothing, t.val)]) + newterm = AlgTerm(only(getidents(newscope))) + extended = ScopeList([t.ctx, newscope]) + init = Dict{Ident, AlgTerm}(map(collect(theory.accessors[m])) do (i, acc) + algacc = getvalue(theory[acc]) + bodyof(b.args[i]) => AlgTerm(algacc.declaration, acc, [newterm]) end) - equations(extended, Ident[], theory; init=init) + equations(GATContext(theory, extended), Ident[]; init=init) end """Get equations for a term or type constructor""" diff --git a/src/syntax/gats/ast.jl b/src/syntax/gats/ast.jl index 18e1867e..a9512568 100644 --- a/src/syntax/gats/ast.jl +++ b/src/syntax/gats/ast.jl @@ -133,7 +133,6 @@ end `AlgSort` A *sort*, which is essentially a type constructor without arguments -`ref` must be reference to a `AlgTypeConstructor` """ @struct_hash_equal struct AlgSort head::Ident @@ -142,6 +141,9 @@ end AlgSort(t::AlgType) = AlgSort(t.body.head, t.body.method) +headof(a::AlgSort) = a.head +methodof(a::AlgSort) = a.method + function AlgSort(c::Context, t::AlgTerm) if isconstant(t) AlgSort(t.body.type) @@ -179,3 +181,6 @@ end const TermInCtx = InCtx{AlgTerm} const TypeInCtx = InCtx{AlgType} + +Scopes.getvalue(i::InCtx) = i.val +Scopes.getcontext(i::InCtx) = i.ctx diff --git a/test/stdlib/models/Arithmetic.jl b/test/stdlib/models/Arithmetic.jl index d1c7291f..07bb6a39 100644 --- a/test/stdlib/models/Arithmetic.jl +++ b/test/stdlib/models/Arithmetic.jl @@ -11,36 +11,36 @@ using .ThNatPlus @test S(S(Z())) + Z() == 2 end -# # IntMonoid = NatPlusMonoid(IntNatPlus) -# #-------------------------------------- -# using .ThMonoid - -# IM = IntMonoid(IntNatPlus()) -# @withmodel IM (e) begin -# @test e() == 0 -# @test (ThMonoid.:(⋅)[IM])(3, 4) == 7 -# end - -# # Integers as preorder -# #--------------------- -# using .ThPreorder - -# @withmodel IntPreorder() (Leq, refl, trans) begin -# @test trans((1,3), (3,5)) == (1,5) -# @test_throws TypeCheckFail Leq((5,3), 5, 3) -# @test refl(2) == (2,2) -# end - -# # Now using category interface - -# using .ThCategory -# M = IntPreorderCat(IntPreorder()) - -# @withmodel M (Hom, id, compose) begin -# @test compose((1,3), (3,5)) == (1,5) -# @test_throws TypeCheckFail Hom((5,3), 5, 3) -# @test_throws ErrorException compose((1,2), (3,5)) -# @test id(2) == (2,2) -# end +# IntMonoid = NatPlusMonoid(IntNatPlus) +#-------------------------------------- +using .ThMonoid + +IM = IntMonoid(IntNatPlus()) +@withmodel IM (e) begin + @test e() == 0 + @test (ThMonoid.:(⋅)[IM])(3, 4) == 7 +end + +# Integers as preorder +#--------------------- +using .ThPreorder + +@withmodel IntPreorder() (Leq, refl, trans) begin + @test trans((1,3), (3,5)) == (1,5) + @test_throws TypeCheckFail Leq((5,3), 5, 3) + @test refl(2) == (2,2) +end + +# Now using category interface + +using .ThCategory +M = IntPreorderCat(IntPreorder()) + +@withmodel M (Hom, id, compose) begin + @test compose((1,3), (3,5)) == (1,5) + @test_throws TypeCheckFail Hom((5,3), 5, 3) + @test_throws ErrorException compose((1,2), (3,5)) + @test id(2) == (2,2) +end end # module diff --git a/test/stdlib/models/GATs.jl b/test/stdlib/models/GATs.jl index 510298bc..04bb366a 100644 --- a/test/stdlib/models/GATs.jl +++ b/test/stdlib/models/GATs.jl @@ -8,7 +8,7 @@ using .ThCategory expected = @theorymap ThMonoid => ThNatPlus begin default => ℕ x ⋅ y ⊣ [x, y] => y + x - e => Z + e() => Z() end @withmodel GATC() (Ob, Hom, id, compose, dom, codom) begin diff --git a/test/stdlib/models/Op.jl b/test/stdlib/models/Op.jl index a9e3f4f7..01882147 100644 --- a/test/stdlib/models/Op.jl +++ b/test/stdlib/models/Op.jl @@ -24,17 +24,17 @@ end # Theory-morphism Op #------------------- -# M = OpFinSetC(FinSetC()) -# @withmodel M (Ob, Hom, id, compose, dom, codom) begin -# @test Ob(0) == 0 -# @test_throws TypeCheckFail Ob(-1) -# @test_throws TypeCheckFail Hom([1,5,2], 4, 3) -# @test Hom(Int[], 4, 0) == Int[] - -# @test id(2) == [1,2] -# @test compose([1,1,1,3,2], [5]) == [2] -# @test codom([5]) == 1 -# end +M = OpFinSetC(FinSetC()) +@withmodel M (Ob, Hom, id, compose, dom, codom) begin + @test Ob(0) == 0 + @test_throws TypeCheckFail Ob(-1) + @test_throws TypeCheckFail Hom([1,5,2], 4, 3) + @test Hom(Int[], 4, 0) == Int[] + + @test id(2) == [1,2] + @test compose([1,1,1,3,2], [5]) == [2] + @test codom([5]) == 1 +end end # module diff --git a/test/stdlib/models/tests.jl b/test/stdlib/models/tests.jl index 06d4e3ac..7893f362 100644 --- a/test/stdlib/models/tests.jl +++ b/test/stdlib/models/tests.jl @@ -6,6 +6,6 @@ include("FinMatrices.jl") include("SliceCategories.jl") include("Op.jl") include("Nothings.jl") -# include("GATs.jl") +include("GATs.jl") end From 9101271618d43bef944f9796e7e6c425bf1c6ea9 Mon Sep 17 00:00:00 2001 From: Owen Lynch Date: Sun, 8 Oct 2023 12:00:03 -0700 Subject: [PATCH 08/12] AlgType now also can be an == --- src/models/SymbolicModels.jl | 2 +- src/stdlib/theories/Naturals.jl | 24 ---- .../theories/{Algebra.jl => algebra.jl} | 7 +- .../theories/{Categories.jl => categories.jl} | 4 - src/stdlib/theories/module.jl | 15 +- .../theories/{Monoidal.jl => monoidal.jl} | 6 - src/stdlib/theories/naturals.jl | 19 +++ src/syntax/GATs.jl | 2 +- src/syntax/Presentations.jl | 79 +++------- src/syntax/Scopes.jl | 6 +- src/syntax/TheoryMaps.jl | 12 +- src/syntax/gats/algorithms.jl | 6 +- src/syntax/gats/ast.jl | 26 +++- src/syntax/gats/exprinterop.jl | 135 ++++++++++++------ src/syntax/gats/gat.jl | 23 +-- test/syntax/GATs.jl | 2 +- test/syntax/Presentations.jl | 38 ++--- 17 files changed, 202 insertions(+), 204 deletions(-) delete mode 100644 src/stdlib/theories/Naturals.jl rename src/stdlib/theories/{Algebra.jl => algebra.jl} (96%) rename src/stdlib/theories/{Categories.jl => categories.jl} (97%) rename src/stdlib/theories/{Monoidal.jl => monoidal.jl} (89%) create mode 100644 src/stdlib/theories/naturals.jl diff --git a/src/models/SymbolicModels.jl b/src/models/SymbolicModels.jl index 94a56a72..7063b4c4 100644 --- a/src/models/SymbolicModels.jl +++ b/src/models/SymbolicModels.jl @@ -245,7 +245,7 @@ macro symbolic_model(decl, theoryname, body) f = parse_function(line) juliasig = parse_function_sig(f) decl = ident(theory; name=juliasig.name) - sig = fromexpr.(Ref(GATContext(theory)), juliasig.types, Ref(AlgSort)) + sig = fromexpr.(Ref(Presentation(theory)), juliasig.types, Ref(AlgSort)) method = resolvemethod(theory.resolvers[decl], sig) overrides[method] = f end diff --git a/src/stdlib/theories/Naturals.jl b/src/stdlib/theories/Naturals.jl deleted file mode 100644 index 2bd49cee..00000000 --- a/src/stdlib/theories/Naturals.jl +++ /dev/null @@ -1,24 +0,0 @@ -module Naturals -export ThNat, ThNatPlus, ThNatPlusTimes - -using ....Syntax - - # Natural numbers - @theory ThNat begin - ℕ :: TYPE - Z :: ℕ - S(n::ℕ) :: ℕ - end - - @theory ThNatPlus <: ThNat begin - import Base: + - ((x::ℕ) + (y::ℕ))::ℕ - (n + S(m) == S(n+m) :: ℕ) ⊣ [n::ℕ,m::ℕ] - end - - @theory ThNatPlusTimes <: ThNatPlus begin - ((x::ℕ) * (y::ℕ))::ℕ - (n * S(m) == ((n * m) + n)) ⊣ [n::ℕ,m::ℕ] - end - -end diff --git a/src/stdlib/theories/Algebra.jl b/src/stdlib/theories/algebra.jl similarity index 96% rename from src/stdlib/theories/Algebra.jl rename to src/stdlib/theories/algebra.jl index fee1ef33..c19c1add 100644 --- a/src/stdlib/theories/Algebra.jl +++ b/src/stdlib/theories/algebra.jl @@ -1,10 +1,7 @@ -module Algebra export ThEmpty, ThSet, ThMagma, ThSemiGroup, ThMonoid, ThGroup, ThCMonoid, ThAb, ThRing, ThCRing, ThRig, ThCRig, ThElementary, ThPreorder -using ....Syntax - -@theory ThEmpty begin +@theory ThEmpty begin end @theory ThSet begin @@ -75,5 +72,3 @@ end trans(f::Leq(p,q),g::Leq(q,r))::Leq(p,r) ⊣ [p,q,r] irrev := f == g ⊣ [p,q, (f,g)::Leq(p,q)] end - -end diff --git a/src/stdlib/theories/Categories.jl b/src/stdlib/theories/categories.jl similarity index 97% rename from src/stdlib/theories/Categories.jl rename to src/stdlib/theories/categories.jl index d521005d..3c242af3 100644 --- a/src/stdlib/theories/Categories.jl +++ b/src/stdlib/theories/categories.jl @@ -1,7 +1,5 @@ -module Categories export ThClass, ThGraph, ThLawlessCat, ThAscCat, ThCategory, ThThinCategory -using ....Syntax # Category theory ################# @@ -69,5 +67,3 @@ These are equivalent to preorders. @theory ThThinCategory <: ThCategory begin thineq := f == g ⊣ [a::Ob, b::Ob, f::Hom(a,b), g::Hom(a,b)] end - -end diff --git a/src/stdlib/theories/module.jl b/src/stdlib/theories/module.jl index 919bb813..281a1558 100644 --- a/src/stdlib/theories/module.jl +++ b/src/stdlib/theories/module.jl @@ -1,15 +1,10 @@ module StdTheories -using Reexport +using ...Syntax -include("Categories.jl") -include("Algebra.jl") -include("Monoidal.jl") -include("Naturals.jl") - -@reexport using .Categories -@reexport using .Algebra -@reexport using .Monoidal -@reexport using .Naturals +include("categories.jl") +include("algebra.jl") +include("monoidal.jl") +include("naturals.jl") end diff --git a/src/stdlib/theories/Monoidal.jl b/src/stdlib/theories/monoidal.jl similarity index 89% rename from src/stdlib/theories/Monoidal.jl rename to src/stdlib/theories/monoidal.jl index 532bee4b..7f1dbc14 100644 --- a/src/stdlib/theories/Monoidal.jl +++ b/src/stdlib/theories/monoidal.jl @@ -1,9 +1,5 @@ -module Monoidal export ThLawlessMonCat, ThStrictMonCat -using ..Categories -using ....Syntax - @theory ThLawlessMonCat <: ThCategory begin mcompose(A::Ob, B::Ob) :: Ob munit() :: Ob @@ -19,5 +15,3 @@ end I() ⊗ A == A :: Ob ⊣ [A::Ob] A ⊗ I() == A :: Ob ⊣ [A::Ob] end - -end diff --git a/src/stdlib/theories/naturals.jl b/src/stdlib/theories/naturals.jl new file mode 100644 index 00000000..1a8ec1df --- /dev/null +++ b/src/stdlib/theories/naturals.jl @@ -0,0 +1,19 @@ +export ThNat, ThNatPlus, ThNatPlusTimes + +# Natural numbers +@theory ThNat begin + ℕ :: TYPE + Z :: ℕ + S(n::ℕ) :: ℕ +end + +@theory ThNatPlus <: ThNat begin + import Base: + + ((x::ℕ) + (y::ℕ))::ℕ + (n + S(m) == S(n+m) :: ℕ) ⊣ [n::ℕ,m::ℕ] +end + +@theory ThNatPlusTimes <: ThNatPlus begin + ((x::ℕ) * (y::ℕ))::ℕ + (n * S(m) == ((n * m) + n)) ⊣ [n::ℕ,m::ℕ] +end diff --git a/src/syntax/GATs.jl b/src/syntax/GATs.jl index 1b1591ea..afb866a3 100644 --- a/src/syntax/GATs.jl +++ b/src/syntax/GATs.jl @@ -3,7 +3,7 @@ export Constant, AlgTerm, AlgType, AlgAST, TypeScope, TypeCtx, AlgSort, AlgSorts, AlgDeclaration, AlgTermConstructor, AlgTypeConstructor, AlgAccessor, AlgAxiom, sortsignature, getdecl, - GATSegment, GAT, GATContext, allmethods, resolvemethod, + GATSegment, GAT, Presentation, gettheory, gettypecontext, allmethods, resolvemethod, termcons,typecons, accessors, equations, build_infer_expr, compile, sortcheck, allnames, sorts, sortname, InCtx, TermInCtx, TypeInCtx, headof, argsof, methodof, bodyof, argcontext diff --git a/src/syntax/Presentations.jl b/src/syntax/Presentations.jl index 087a64ab..b5201440 100644 --- a/src/syntax/Presentations.jl +++ b/src/syntax/Presentations.jl @@ -7,45 +7,6 @@ using StructEquality using MLStyle import ..ExprInterop: fromexpr, toexpr -""" -A presentation has a set of generators, given by a `TypeScope`, and a set of -equations among terms which can refer to those generators. Each element of -`eqs` is a list of terms which are asserted to be equal. -""" -@struct_hash_equal struct Presentation <: HasContext{AlgType} - theory::GAT - scope::TypeScope - eqs::Vector{Vector{AlgTerm}} - function Presentation(gat, scope, eqs=[]) - gatscope = AppendContext(gat, scope) - # scope terms must be defined in GAT - sortcheck.(Ref(gatscope), getvalue.(scope)) - # eq terms must be defined in GAT ++ scope - for eq in eqs - length(eq) > 1 || error("At least two things must be equated") - sortcheck.(Ref(gatscope), eq) - end - new(gat, scope, collect.(eqs)) - end -end - -Scopes.getcontext(p::Presentation) = GATContext(p.theory, p.scope) - -fromexpr(p::Presentation, e, t) = fromexpr(Scopes.getcontext(p), e, t) - -"""Context of presentation is the underlying GAT""" -toexpr(p::Presentation) = toexpr(p.theory, p) - -function toexpr(c::Context, p::Presentation) - c == p.theory || error("Invalid context for presentation") - decs = GATs.bindingexprs(c, p.scope) - eqs = map(p.eqs) do ts - exprs = zip(toexpr.(Ref(p), ts),Iterators.repeated(:(==))) - Expr(:comparison, collect(Iterators.flatten(exprs))[1:(end-1)]...) - end - Expr(:block, [decs..., eqs...]...) -end - """ Parse, e.g.: @@ -58,37 +19,31 @@ h′::Hom(a, c) compose(f, g) == h == h′ ``` """ -function fromexpr(ctx::GATContext, e, ::Type{Presentation}) - e.head == :block || error("expected a block to parse into a GATSegment, got: $e") - scopelines, eqlines = [], Vector{Expr0}[] - for line in e.args - @switch line begin - @case Expr(:call, :(==), a, b) - push!(eqlines, [a, b]) - @case Expr(:comparison, xs...) - er = "Bad comparison $line" - all(((i,v),)-> iseven(i) == (v == :(==)), enumerate(line.args)) || error(er) - push!(eqlines, xs[1:2:end]) - @case _ - push!(scopelines, line) - end - end - scope = fromexpr(ctx, Expr(:block, scopelines...), TypeScope) - apscope = AppendContext(ctx, scope) - Presentation(ctx.theory, scope, [fromexpr.(Ref(apscope), ts, AlgTerm) for ts in eqlines]) +function fromexpr(p::Presentation, e, ::Type{Presentation}) + e.head == :block || error("expected a block to parse into a Presentation, got: $e") + newscope = fromexpr(p, e, TypeScope) + Presentation(gettheory(p), ScopeList([allscopes(gettypecontext(p)); newscope])) end -construct_presentation(m::Module, e) = fromexpr(m.THEORY, e, Presentation) - macro present(head, body) - (theory, name) = @match head begin - Expr(:call, name, theory) => (theory, name) + (parent, name) = @match head begin + Expr(:call, name, mod) => (:($(Presentation)($(mod).THEORY)), name) + Expr(:(<:), name, parent) => (parent, name) _ => error("invalid head for @present macro: $head") end esc(quote - const $name = $(construct_presentation)($theory, $(QuoteNode(body))) + const $name = $(fromexpr)($parent, $(QuoteNode(body)), $(Presentation)) end) end +function Base.show(io::IO, p::Presentation) + println(io, "Presentation(", nameof(p.theory), "):") + for scope in allscopes(gettypecontext(p)) + for binding in scope + println(io, " ", toexpr(p, binding)) + end + end +end + end # module diff --git a/src/syntax/Scopes.jl b/src/syntax/Scopes.jl index 36190ea1..ab6725d5 100644 --- a/src/syntax/Scopes.jl +++ b/src/syntax/Scopes.jl @@ -5,7 +5,7 @@ export LID, Ident, Alias, gettag, getlid, isnamed, Binding, getvalue, setvalue, getline, setline, - Context, getscope, nscopes, getlevel, hasname, hastag, + Context, getscope, nscopes, getlevel, hasname, hastag, alltags, allscopes, HasContext, getcontext, hasident, ident, getidents, idents, canonicalize, HasScope, haslid, getscope, getbindings, getbinding, @@ -585,6 +585,8 @@ function hasident(c::Context, x::Ident) end end +allscopes(c::Context) = [getscope(c, i) for i in 1:nscopes(c)] + function Base.in(x::Ident, s::Context) hasident(s, x) end @@ -699,7 +701,7 @@ function ScopeList{T}(scopes::Vector{<:HasScope{T}}) where {T} c end -function Scope(hsl::ScopeList{T}) where T +function Scope(hsl::ScopeList{T}) where T if nscopes(hsl) == 0 Scope{T}() else diff --git a/src/syntax/TheoryMaps.jl b/src/syntax/TheoryMaps.jl index 9bec2558..6636aef3 100644 --- a/src/syntax/TheoryMaps.jl +++ b/src/syntax/TheoryMaps.jl @@ -110,7 +110,7 @@ function pushforward( rt_dict = Dict(gettag(tcon.localcontext)=>gettag(new_term.ctx)) # new_term has same context as tcon, so recursively map over components - lc = bind_localctx(GATContext(dom), InCtx{T}(ctx, t)) + lc = bind_localctx(Presentation(dom), InCtx{T}(ctx, t)) flc = Dict{Ident, AlgTerm}(map(collect(pairs(lc))) do (k, v) retag(rt_dict, k) => pushforward(dom, tymap, trmap, ctx, v; fctx) end) @@ -171,10 +171,10 @@ Take a term constructor and determine terms of its local context. This function is mutually recursive with `infer_type`. """ -bind_localctx(ctx::GATContext, t::InCtx) = - bind_localctx(GATContext(ctx.theory, AppendContext(ctx.context, t.ctx)), t.val) +bind_localctx(ctx::Presentation, t::InCtx) = + bind_localctx(Presentation(ctx.theory, AppendContext(ctx.context, t.ctx)), t.val) -function bind_localctx(ctx::GATContext, t::AlgAST) +function bind_localctx(ctx::Presentation, t::AlgAST) m = methodof(t.body) tc = getvalue(ctx[m]) eqs = equations(ctx.theory, m) @@ -325,14 +325,14 @@ function fromexpr(dom::GAT, codom::GAT, e, ::Type{TheoryMap}) args = idents(ctx; name=argnames) sig = [AlgSort(getvalue(ctx[i])) for i in args] x = ident(dom; name=xname) - m = GATs.methodlookup(GATContext(dom), x, sig) + m = GATs.methodlookup(Presentation(dom), x, sig) # reorder the context to match that of the canonical localctx + args tc = getvalue(dom[m]) reorder_init = Dict(zip(getvalue.(getlid.(args)), getvalue.(tc.args))) reordered_ctx = reorder(ctx, tc.localcontext, reorder_init) fctx = pushforward(dom, typs, trms, reordered_ctx) - val = fromexpr(GATContext(codom, fctx), e2, T) + val = fromexpr(Presentation(codom, fctx), e2, T) dic = T == AlgType ? typs : trms dic[m] = InCtx{T}(fctx, val) end diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl index 7e69c9c0..366cb912 100644 --- a/src/syntax/gats/algorithms.jl +++ b/src/syntax/gats/algorithms.jl @@ -55,7 +55,7 @@ Example: ways_of_computing = Dict(a => [dom(f)], b => [codom(f), dom(g)], c => [codom(g)], f => [f], g => [g]) """ -function equations(c::GATContext, args::AbstractVector{Ident}; init=nothing) +function equations(c::Presentation, args::AbstractVector{Ident}; init=nothing) theory = c.theory context = c.context ways_of_computing = Dict{Ident, Set{AlgTerm}}() @@ -103,13 +103,13 @@ function equations(theory::GAT, t::TypeInCtx) algacc = getvalue(theory[acc]) bodyof(b.args[i]) => AlgTerm(algacc.declaration, acc, [newterm]) end) - equations(GATContext(theory, extended), Ident[]; init=init) + equations(Presentation(theory, extended), Ident[]; init=init) end """Get equations for a term or type constructor""" function equations(theory::GAT, x::Ident) judgment = getvalue(theory, x) - equations(GATContext(theory, judgment), idents(judgment.localcontext; lid=judgment.args)) + equations(Presentation(theory, judgment), idents(judgment.localcontext; lid=judgment.args)) end function compile(expr_lookup::Dict{Ident}, term::AlgTerm; theorymodule=nothing) diff --git a/src/syntax/gats/ast.jl b/src/syntax/gats/ast.jl index a9512568..94796d15 100644 --- a/src/syntax/gats/ast.jl +++ b/src/syntax/gats/ast.jl @@ -73,17 +73,32 @@ isapp(t::AlgTerm) = t.body isa MethodApp isconstant(t::AlgTerm) = t.body isa AbstractConstant -rename(tag::ScopeTag, reps::Dict{Symbol,Symbol}, t::AlgTerm) = AlgTerm(rename(tag, reps, t.body)) +rename(tag::ScopeTag, reps::Dict{Symbol,Symbol}, t::AlgTerm) = + AlgTerm(rename(tag, reps, t.body)) retag(reps::Dict{ScopeTag, ScopeTag}, t::AlgTerm) = AlgTerm(retag(reps, t.body)) +""" +`Eq` + +The type of equality judgments. +""" +@struct_hash_equal struct Eq + equands::Tuple{AlgTerm, AlgTerm} +end + +retag(reps::Dict{ScopeTag, ScopeTag}, eq::Eq) = Eq(retag.(Ref(reps), eq.equands)) + +rename(tag::ScopeTag, reps::Dict{Symbol, Symbol}, eq::Eq) = + Eq(retag.(Ref(tag), Ref(reps), eq.equands)) + """ `AlgType` One syntax tree to rule all the types. """ @struct_hash_equal struct AlgType <: AlgAST - body::MethodApp{AlgTerm} + body::Union{MethodApp{AlgTerm}, Eq} end function AlgType(fun::Ident, method::Ident) @@ -94,13 +109,18 @@ bodyof(t::AlgType) = t.body isvariable(t::AlgType) = false -isapp(t::AlgType) = true +isapp(t::AlgType) = t.body isa MethodApp + +iseq(t::AlgType) = t.body isa Eq isconstant(t::AlgType) = false AlgType(head::Ident, method::Ident, args::Vector{AlgTerm}) = AlgType(MethodApp{AlgTerm}(head, method, args)) +AlgType(lhs::AlgTerm, rhs::AlgTerm) = + AlgType(Eq((lhs, rhs))) + retag(reps::Dict{ScopeTag,ScopeTag}, t::AlgType) = AlgType(retag(reps, t.body)) diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index e078aa77..37d49edf 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -1,9 +1,9 @@ # AST ##### """Coerce GATs to GAT contexts""" -fromexpr(g::GAT, e, t) = fromexpr(GATContext(g), e, t) +fromexpr(g::GAT, e, t) = fromexpr(Presentation(g), e, t) -function parse_methodapp(c::GATContext, head::Symbol, argexprs) +function parse_methodapp(c::Presentation, head::Symbol, argexprs) args = Vector{AlgTerm}(fromexpr.(Ref(c), argexprs, Ref(AlgTerm))) fun = fromexpr(c, head, Ident) signature = AlgSort.(Ref(c), args) @@ -15,7 +15,7 @@ function parse_methodapp(c::GATContext, head::Symbol, argexprs) MethodApp{AlgTerm}(fun, method, args) end -function fromexpr(c::GATContext, e, ::Type{MethodApp{AlgTerm}}) +function fromexpr(c::Presentation, e, ::Type{MethodApp{AlgTerm}}) @match e begin Expr(:call, head::Symbol, argexprs...) => parse_methodapp(c, head, argexprs) _ => error("expected a call expression") @@ -26,7 +26,7 @@ function toexpr(c::Context, m::MethodApp) Expr(:call, toexpr(c, m.head), toexpr.(Ref(c), m.args)...) end -function fromexpr(c::GATContext, e, ::Type{AlgTerm}) +function fromexpr(c::Presentation, e, ::Type{AlgTerm}) @match e begin s::Symbol => begin x = fromexpr(c, s, Ident) @@ -48,38 +48,44 @@ function toexpr(c::Context, term::AlgTerm) toexpr(c, term.body) end -function fromexpr(c::GATContext, e, ::Type{AlgType})::AlgType - (head, argexprs) = @match e begin - s::Symbol => (s, []) - Expr(:call, head, args...) => (head, args) +function fromexpr(p::Presentation, e, ::Type{AlgType})::AlgType + @match e begin + s::Symbol => AlgType(parse_methodapp(p, s, [])) + Expr(:call, head, args...) && if head != :(==) end => + AlgType(parse_methodapp(p, head, args)) + Expr(:call, :(==), lhs, rhs) => + AlgType(fromexpr(p, lhs, AlgTerm), fromexpr(p, rhs, AlgTerm)) _ => error("could not parse AlgType from $e") end - AlgType(parse_methodapp(c, head, argexprs)) end function toexpr(c::Context, type::AlgType) - if length(type.body.args) == 0 - toexpr(c, type.body.head) + if isapp(type) + if length(type.body.args) == 0 + toexpr(c, type.body.head) + else + Expr(:call, toexpr(c, type.body.head), toexpr.(Ref(c), type.body.args)...) + end else - Expr(:call, toexpr(c, type.body.head), toexpr.(Ref(c), type.body.args)...) + Expr(:call, :(==), type.body.equands...) end end -function fromexpr(c::GATContext, e, ::Type{AlgSort}) +function fromexpr(c::Presentation, e, ::Type{AlgSort}) e isa Symbol || error("expected a Symbol to parse a sort, got: $e") decl = ident(c.theory; name=e) method = only(allmethods(c.theory.resolvers[decl]))[2] AlgSort(decl, method) end -function toexpr(c::GATContext, s::AlgSort) +function toexpr(c::Presentation, s::AlgSort) toexpr(c, getdecl(s)) end toexpr(c::Context, constant::Constant; kw...) = Expr(:(::), constant.value, toexpr(c, constant.type; kw...)) -function fromexpr(c::GATContext, e, ::Type{InCtx{T}}; kw...) where T +function fromexpr(c::Presentation, e, ::Type{InCtx{T}}; kw...) where T (termexpr, localcontext) = @match e begin Expr(:call, :(⊣), binding, tscope) => (binding, fromexpr(c, tscope, TypeScope)) e => (e, TypeScope()) @@ -101,6 +107,17 @@ end # toexpr +function toexpr(p::Context, b::Binding{AlgType}) + val = getvalue(b) + if isapp(val) + Expr(:(::), nameof(b), toexpr(p, val)) + elseif iseq(val) + Expr(:call, :(==), toexpr.(Ref(p), val.body.equands)...) + elseif val isa Alias + Expr(:(=), nameof(b), toexpr(Ref(p), name.ref)) + end +end + function bindingexprs(c::Context, s::HasScope) c′ = AppendContext(c, s) [Expr(:(::), nameof(b), toexpr(c′, getvalue(b))) for b in s] @@ -127,12 +144,12 @@ end function judgmenthead(theory::GAT, _, judgment::AlgTermConstructor) name = nameof(getdecl(judgment)) untyped = Expr(:call, name, nameof.(argsof(judgment))...) - c = GATContext(theory, judgment.localcontext) + c = Presentation(theory, judgment.localcontext) Expr(:(::), untyped, toexpr(c, judgment.type)) end function judgmenthead(theory::GAT, name, judgment::AlgAxiom) - c = GATContext(theory, judgment.localcontext) + c = Presentation(theory, judgment.localcontext) untyped = Expr(:call, :(==), toexpr(c, judgment.equands[1]), toexpr(c, judgment.equands[2])) if isnothing(name) untyped @@ -155,43 +172,69 @@ function toexpr(c::GAT, binding::Binding{Judgment}) Expr(:call, :(⊣), head, Expr(:vect, bindingexprs(c, judgment.localcontext)...)) end +function toexpr(c::Context, p::Presentation) + c == p.theory || error("Invalid context for presentation") + decs = GATs.bindingexprs(c, p.scope) + eqs = map(p.eqs) do ts + exprs = zip(toexpr.(Ref(p), ts),Iterators.repeated(:(==))) + Expr(:comparison, collect(Iterators.flatten(exprs))[1:(end-1)]...) + end + Expr(:block, [decs..., eqs...]...) +end + # fromexpr -function fromexpr(c::GATContext, e, ::Type{Binding{AlgType}}) +""" +`f(pushbinding!, expr)` should inspect `expr` and call `pushbinding!` +0 or more times with two arguments: the name and value of a new binding. +""" +function parse_scope!(f::Function, scope::Scope, lines::AbstractVector) + currentln = nothing + for e in lines + @match e begin + l::LineNumberNode => (currentln = l) + _ => f(e) do binding + Scopes.unsafe_pushbinding!(scope, setline(binding, currentln)) + end + end + end + scope +end + +function fromexpr(c::Presentation, e, ::Type{Binding{AlgType}}; line=nothing) @match e begin Expr(:(::), name::Symbol, type_expr) => - Binding{AlgType}(name, fromexpr(c, type_expr, AlgType)) + Binding{AlgType}(name, fromexpr(c, type_expr, AlgType), line) _ => error("could not parse binding of name to type from $e") end end -function fromexpr(c::GATContext, e, ::Type{TypeScope}) - exprs = e.args - scope = TypeScope() - c′ = AppendContext(c, scope) - line = nothing - for expr in exprs - binding_exprs = @match expr begin - a::Symbol => [Expr(:(::), a, :default)] - Expr(:tuple, names...) => [:($name :: default) for name in names] - Expr(:(::), Expr(:tuple, names...), T) => [:($name :: $T) for name in names] - :($a :: $T) => [expr] - l::LineNumberNode => begin - line = l - [] - end - _ => error("invalid binding expression $expr") - end - for binding_expr in binding_exprs - binding = fromexpr(c′, binding_expr, Binding{AlgType}) - Scopes.unsafe_pushbinding!(getscope(scope), setline(binding, line)) - end +function parse_binding_expr!(c::Presentation, pushbinding!, e) + p!(name, type_expr) = pushbinding!(Binding{AlgType}(name, fromexpr(c, type_expr, AlgType))) + @match e begin + a::Symbol => p!(a, :default) + Expr(:tuple, names...) => p!.(names, Ref(:default)) + Expr(:(::), Expr(:tuple, names...), T) => p!.(names, Ref(T)) + Expr(:(::), name, T) => p!(name, T) + Expr(:call, :(==), lhs, rhs) => + pushbinding!(Binding{AlgType}( + nothing, AlgType(fromexpr(c, lhs, AlgTerm), fromexpr(c, rhs, AlgTerm)) + )) + _ => error("invalid binding expression $e") end - scope +end + +function fromexpr(p::Presentation, e, ::Type{TypeScope}) + ts = TypeScope() + c = AppendContext(p, ts) + parse_scope!(ts.scope, e.args) do pushbinding!, arg + parse_binding_expr!(c, pushbinding!, arg) + end + ts end function parseargs!(theory::GAT, exprs::AbstractVector, scope::TypeScope) - c = GATContext(theory, scope) + c = Presentation(theory, scope) map(exprs) do expr binding_expr = @match expr begin a::Symbol => getlid(ident(scope; name=a)) @@ -208,7 +251,7 @@ end function parseaxiom!(theory::GAT, localcontext, sort_expr, e; name=nothing) @match e begin Expr(:call, :(==), lhs_expr, rhs_expr) => begin - c = GATContext(theory, localcontext) + c = Presentation(theory, localcontext) equands = fromexpr.(Ref(c), [lhs_expr, rhs_expr], Ref(AlgTerm)) sorts = sortcheck.(Ref(c), equands) @assert allequal(sorts) @@ -251,7 +294,7 @@ function parseconstructor!(theory::GAT, localcontext, type_expr, e) end end _ => begin - c = GATContext(theory, localcontext) + c = Presentation(theory, localcontext) type = fromexpr(c, type_expr, AlgType) decl = if hasname(theory, name) ident(theory; name) @@ -295,7 +338,7 @@ function parse_binding_line!(theory::GAT, e, linenumber) e => (e, TypeScope()) end - c = GATContext(theory, localcontext) + c = Presentation(theory, localcontext) (head, type_expr) = @match binding begin Expr(:(::), head, type_expr) => (head, type_expr) @@ -378,7 +421,7 @@ function fromexpr(parent::GAT, e, ::Type{GAT}; name=parent.name) e.head == :block || error("expected a block to parse into a GAT, got: $e") linenumber = nothing for line in e.args - bindings = @match line begin + @match line begin l::LineNumberNode => begin linenumber = l end diff --git a/src/syntax/gats/gat.jl b/src/syntax/gats/gat.jl index 9f2ef364..de923a95 100644 --- a/src/syntax/gats/gat.jl +++ b/src/syntax/gats/gat.jl @@ -131,10 +131,9 @@ function Base.show(io::IO, theory::GAT) println(io, "GAT(", theory.name, "):") for seg in theory.segments.scopes block = toexpr(theory, seg) - for line in block.args[1:end-1] + for line in block.args println(io, " ", line) end - print(io, " ", block.args[end]) end end @@ -181,26 +180,30 @@ Base.issubset(t1::GAT, t2::GAT) = all(s->hastag(t2, s), gettag.(Scopes.getscopelist(t1).scopes)) """ -`GATContext` +`Presentation` A context consisting of two parts: a GAT and a TypeCtx -Certain types (like AlgTerm) can only be parsed in a GATContext, because +Certain types (like AlgTerm) can only be parsed in a Presentation, because they require access to the method resolving in the GAT. """ -struct GATContext <: HasContext{Union{Judgment, AlgType}} +struct Presentation <: HasContext{Union{Judgment, AlgType}} theory::GAT context::Context{AlgType} end -GATContext(theory::GAT) = GATContext(theory, EmptyContext{AlgType}()) +Presentation(theory::GAT) = Presentation(theory, EmptyContext{AlgType}()) -Scopes.getcontext(c::GATContext) = AppendContext(c.theory, c.context) +gettheory(p::Presentation) = p.theory -Scopes.AppendContext(c::GATContext, context::Context{AlgType}) = - GATContext(c.theory, AppendContext(c.context, context)) +gettypecontext(p::Presentation) = p.context -function methodlookup(c::GATContext, x::Ident, sig::AlgSorts) +Scopes.getcontext(c::Presentation) = AppendContext(c.theory, c.context) + +Scopes.AppendContext(c::Presentation, context::Context{AlgType}) = + Presentation(c.theory, AppendContext(c.context, context)) + +function methodlookup(c::Presentation, x::Ident, sig::AlgSorts) theory = c.theory if haskey(theory.resolvers, x) && haskey(theory.resolvers[x].bysignature, sig) resolvemethod(theory.resolvers[x], sig) diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index e1d6659f..248c73c2 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -60,7 +60,7 @@ A = ident(sortscope; name=:A) ATerm = AlgTerm(A) -c = GATContext(thcat, sortscope) +c = Presentation(thcat, sortscope) HomT = fromexpr(c, :(Hom(A, A)), AlgType) HomS = AlgSort(HomT) diff --git a/test/syntax/Presentations.jl b/test/syntax/Presentations.jl index f91f197d..977b8de7 100644 --- a/test/syntax/Presentations.jl +++ b/test/syntax/Presentations.jl @@ -2,45 +2,45 @@ module TestPresentations using GATlab using Test -T = ThCategory.THEORY; -ctx = GATContext(T); +T = ThCategory.THEORY +ctx = Presentation(T) tscope = fromexpr( ctx, - :([(a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c), (h,h′)::Hom(a,c)]), + :([(a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c), (h,h′)::Hom(a,c), f⋅g == h, h == h′]), TypeScope ) -_, _, _, f, g, h, h′ = getidents(tscope) -h, h′ = AlgTerm.([h,h′]) -fg = fromexpr(AppendContext(ctx, tscope), :(compose(f,g)), AlgTerm) -p1 = Presentation(T, tscope, [[fg, h]]); -x1 = toexpr(p1) -p1′ = fromexpr(ctx,x1, Presentation); -@test length(only(p1′.eqs)) == 2 - -p2 = Presentation(T, tscope, [[fg, h, h′]]); -x2 = toexpr(p2) -p2′ = fromexpr(ctx,x2, Presentation); -@test length(only(p2′.eqs)) == 3 +p1 = Presentation(T, tscope) # HasContext interface @test nscopes(p1) == nscopes(T) + 1 -@test length(getscope(p1, nscopes(p1))) == 7 +@test length(getscope(p1, nscopes(p1))) == 9 @test !hastag(p1, newscopetag()) @test hasname(p1, :f) @test !hasname(p1, :q) @test getlevel(p1, :id) < getlevel(p1, :f) -@test getlevel(p1, gettag(f)) == nscopes(p1) @present SchGraph(ThCategory) begin (E,V)::Ob src::Hom(E,V) tgt::Hom(E,V) -end; +end src, tgt = idents(SchGraph; name=[:src, :tgt]) Hom = ident(SchGraph; name=:Hom) -@test getvalue(SchGraph[src]).body.head == Hom; +@test getvalue(SchGraph[src]).body.head == Hom + +@present SchFiberedGraph <: SchGraph begin + (FE, FV)::Ob + fsrc::(FE → FV) + ftgt::(FE → FV) + v::(FV → V) + e::(FE → E) + fsrc ⋅ v == e ⋅ src + ftgt ⋅ v == e ⋅ tgt +end + +@test nscopes(gettypecontext(SchFiberedGraph)) == 2 @present Z(ThGroup) begin (a,) From 28925197feaca87216ba001a8e05d160c6d89c8c Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Mon, 9 Oct 2023 13:06:05 -0700 Subject: [PATCH 09/12] rename presentation -> GATContext --- docs/src/api.md | 2 +- src/models/SymbolicModels.jl | 2 +- .../{Presentations.jl => GATContexts.jl} | 18 +++++----- src/syntax/GATs.jl | 2 +- src/syntax/TheoryMaps.jl | 12 +++---- src/syntax/gats/algorithms.jl | 6 ++-- src/syntax/gats/exprinterop.jl | 36 +++++++++---------- src/syntax/gats/gat.jl | 20 +++++------ src/syntax/module.jl | 4 +-- .../{Presentations.jl => GATContexts.jl} | 6 ++-- test/syntax/GATs.jl | 2 +- test/syntax/tests.jl | 4 +-- 12 files changed, 57 insertions(+), 57 deletions(-) rename src/syntax/{Presentations.jl => GATContexts.jl} (54%) rename test/syntax/{Presentations.jl => GATContexts.jl} (93%) diff --git a/docs/src/api.md b/docs/src/api.md index bf7dada6..38f6bc66 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -11,7 +11,7 @@ Modules = [ GATlab.Syntax, GATlab.Syntax.Scopes, GATlab.Syntax.GATs, - GATlab.Syntax.Presentations, + GATlab.Syntax.GATContexts, GATlab.Syntax.ExprInterop, GATlab.Syntax.TheoryInterface, ] diff --git a/src/models/SymbolicModels.jl b/src/models/SymbolicModels.jl index 7063b4c4..94a56a72 100644 --- a/src/models/SymbolicModels.jl +++ b/src/models/SymbolicModels.jl @@ -245,7 +245,7 @@ macro symbolic_model(decl, theoryname, body) f = parse_function(line) juliasig = parse_function_sig(f) decl = ident(theory; name=juliasig.name) - sig = fromexpr.(Ref(Presentation(theory)), juliasig.types, Ref(AlgSort)) + sig = fromexpr.(Ref(GATContext(theory)), juliasig.types, Ref(AlgSort)) method = resolvemethod(theory.resolvers[decl], sig) overrides[method] = f end diff --git a/src/syntax/Presentations.jl b/src/syntax/GATContexts.jl similarity index 54% rename from src/syntax/Presentations.jl rename to src/syntax/GATContexts.jl index b5201440..77d3a6b0 100644 --- a/src/syntax/Presentations.jl +++ b/src/syntax/GATContexts.jl @@ -1,5 +1,5 @@ -module Presentations -export Presentation, @present +module GATContexts +export GATContext, @present using ...Util using ..Scopes, ..GATs @@ -19,26 +19,26 @@ h′::Hom(a, c) compose(f, g) == h == h′ ``` """ -function fromexpr(p::Presentation, e, ::Type{Presentation}) - e.head == :block || error("expected a block to parse into a Presentation, got: $e") +function fromexpr(p::GATContext, e, ::Type{GATContext}) + e.head == :block || error("expected a block to parse into a GATContext, got: $e") newscope = fromexpr(p, e, TypeScope) - Presentation(gettheory(p), ScopeList([allscopes(gettypecontext(p)); newscope])) + GATContext(gettheory(p), ScopeList([allscopes(gettypecontext(p)); newscope])) end macro present(head, body) (parent, name) = @match head begin - Expr(:call, name, mod) => (:($(Presentation)($(mod).THEORY)), name) + Expr(:call, name, mod) => (:($(GATContext)($(mod).THEORY)), name) Expr(:(<:), name, parent) => (parent, name) _ => error("invalid head for @present macro: $head") end esc(quote - const $name = $(fromexpr)($parent, $(QuoteNode(body)), $(Presentation)) + const $name = $(fromexpr)($parent, $(QuoteNode(body)), $(GATContext)) end) end -function Base.show(io::IO, p::Presentation) - println(io, "Presentation(", nameof(p.theory), "):") +function Base.show(io::IO, p::GATContext) + println(io, "GATContext(", nameof(p.theory), "):") for scope in allscopes(gettypecontext(p)) for binding in scope println(io, " ", toexpr(p, binding)) diff --git a/src/syntax/GATs.jl b/src/syntax/GATs.jl index afb866a3..964b6c8f 100644 --- a/src/syntax/GATs.jl +++ b/src/syntax/GATs.jl @@ -3,7 +3,7 @@ export Constant, AlgTerm, AlgType, AlgAST, TypeScope, TypeCtx, AlgSort, AlgSorts, AlgDeclaration, AlgTermConstructor, AlgTypeConstructor, AlgAccessor, AlgAxiom, sortsignature, getdecl, - GATSegment, GAT, Presentation, gettheory, gettypecontext, allmethods, resolvemethod, + GATSegment, GAT, GATContext, gettheory, gettypecontext, allmethods, resolvemethod, termcons,typecons, accessors, equations, build_infer_expr, compile, sortcheck, allnames, sorts, sortname, InCtx, TermInCtx, TypeInCtx, headof, argsof, methodof, bodyof, argcontext diff --git a/src/syntax/TheoryMaps.jl b/src/syntax/TheoryMaps.jl index 6636aef3..9bec2558 100644 --- a/src/syntax/TheoryMaps.jl +++ b/src/syntax/TheoryMaps.jl @@ -110,7 +110,7 @@ function pushforward( rt_dict = Dict(gettag(tcon.localcontext)=>gettag(new_term.ctx)) # new_term has same context as tcon, so recursively map over components - lc = bind_localctx(Presentation(dom), InCtx{T}(ctx, t)) + lc = bind_localctx(GATContext(dom), InCtx{T}(ctx, t)) flc = Dict{Ident, AlgTerm}(map(collect(pairs(lc))) do (k, v) retag(rt_dict, k) => pushforward(dom, tymap, trmap, ctx, v; fctx) end) @@ -171,10 +171,10 @@ Take a term constructor and determine terms of its local context. This function is mutually recursive with `infer_type`. """ -bind_localctx(ctx::Presentation, t::InCtx) = - bind_localctx(Presentation(ctx.theory, AppendContext(ctx.context, t.ctx)), t.val) +bind_localctx(ctx::GATContext, t::InCtx) = + bind_localctx(GATContext(ctx.theory, AppendContext(ctx.context, t.ctx)), t.val) -function bind_localctx(ctx::Presentation, t::AlgAST) +function bind_localctx(ctx::GATContext, t::AlgAST) m = methodof(t.body) tc = getvalue(ctx[m]) eqs = equations(ctx.theory, m) @@ -325,14 +325,14 @@ function fromexpr(dom::GAT, codom::GAT, e, ::Type{TheoryMap}) args = idents(ctx; name=argnames) sig = [AlgSort(getvalue(ctx[i])) for i in args] x = ident(dom; name=xname) - m = GATs.methodlookup(Presentation(dom), x, sig) + m = GATs.methodlookup(GATContext(dom), x, sig) # reorder the context to match that of the canonical localctx + args tc = getvalue(dom[m]) reorder_init = Dict(zip(getvalue.(getlid.(args)), getvalue.(tc.args))) reordered_ctx = reorder(ctx, tc.localcontext, reorder_init) fctx = pushforward(dom, typs, trms, reordered_ctx) - val = fromexpr(Presentation(codom, fctx), e2, T) + val = fromexpr(GATContext(codom, fctx), e2, T) dic = T == AlgType ? typs : trms dic[m] = InCtx{T}(fctx, val) end diff --git a/src/syntax/gats/algorithms.jl b/src/syntax/gats/algorithms.jl index 366cb912..7e69c9c0 100644 --- a/src/syntax/gats/algorithms.jl +++ b/src/syntax/gats/algorithms.jl @@ -55,7 +55,7 @@ Example: ways_of_computing = Dict(a => [dom(f)], b => [codom(f), dom(g)], c => [codom(g)], f => [f], g => [g]) """ -function equations(c::Presentation, args::AbstractVector{Ident}; init=nothing) +function equations(c::GATContext, args::AbstractVector{Ident}; init=nothing) theory = c.theory context = c.context ways_of_computing = Dict{Ident, Set{AlgTerm}}() @@ -103,13 +103,13 @@ function equations(theory::GAT, t::TypeInCtx) algacc = getvalue(theory[acc]) bodyof(b.args[i]) => AlgTerm(algacc.declaration, acc, [newterm]) end) - equations(Presentation(theory, extended), Ident[]; init=init) + equations(GATContext(theory, extended), Ident[]; init=init) end """Get equations for a term or type constructor""" function equations(theory::GAT, x::Ident) judgment = getvalue(theory, x) - equations(Presentation(theory, judgment), idents(judgment.localcontext; lid=judgment.args)) + equations(GATContext(theory, judgment), idents(judgment.localcontext; lid=judgment.args)) end function compile(expr_lookup::Dict{Ident}, term::AlgTerm; theorymodule=nothing) diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index 37d49edf..27ac0cb4 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -1,9 +1,9 @@ # AST ##### """Coerce GATs to GAT contexts""" -fromexpr(g::GAT, e, t) = fromexpr(Presentation(g), e, t) +fromexpr(g::GAT, e, t) = fromexpr(GATContext(g), e, t) -function parse_methodapp(c::Presentation, head::Symbol, argexprs) +function parse_methodapp(c::GATContext, head::Symbol, argexprs) args = Vector{AlgTerm}(fromexpr.(Ref(c), argexprs, Ref(AlgTerm))) fun = fromexpr(c, head, Ident) signature = AlgSort.(Ref(c), args) @@ -15,7 +15,7 @@ function parse_methodapp(c::Presentation, head::Symbol, argexprs) MethodApp{AlgTerm}(fun, method, args) end -function fromexpr(c::Presentation, e, ::Type{MethodApp{AlgTerm}}) +function fromexpr(c::GATContext, e, ::Type{MethodApp{AlgTerm}}) @match e begin Expr(:call, head::Symbol, argexprs...) => parse_methodapp(c, head, argexprs) _ => error("expected a call expression") @@ -26,7 +26,7 @@ function toexpr(c::Context, m::MethodApp) Expr(:call, toexpr(c, m.head), toexpr.(Ref(c), m.args)...) end -function fromexpr(c::Presentation, e, ::Type{AlgTerm}) +function fromexpr(c::GATContext, e, ::Type{AlgTerm}) @match e begin s::Symbol => begin x = fromexpr(c, s, Ident) @@ -48,7 +48,7 @@ function toexpr(c::Context, term::AlgTerm) toexpr(c, term.body) end -function fromexpr(p::Presentation, e, ::Type{AlgType})::AlgType +function fromexpr(p::GATContext, e, ::Type{AlgType})::AlgType @match e begin s::Symbol => AlgType(parse_methodapp(p, s, [])) Expr(:call, head, args...) && if head != :(==) end => @@ -71,21 +71,21 @@ function toexpr(c::Context, type::AlgType) end end -function fromexpr(c::Presentation, e, ::Type{AlgSort}) +function fromexpr(c::GATContext, e, ::Type{AlgSort}) e isa Symbol || error("expected a Symbol to parse a sort, got: $e") decl = ident(c.theory; name=e) method = only(allmethods(c.theory.resolvers[decl]))[2] AlgSort(decl, method) end -function toexpr(c::Presentation, s::AlgSort) +function toexpr(c::GATContext, s::AlgSort) toexpr(c, getdecl(s)) end toexpr(c::Context, constant::Constant; kw...) = Expr(:(::), constant.value, toexpr(c, constant.type; kw...)) -function fromexpr(c::Presentation, e, ::Type{InCtx{T}}; kw...) where T +function fromexpr(c::GATContext, e, ::Type{InCtx{T}}; kw...) where T (termexpr, localcontext) = @match e begin Expr(:call, :(⊣), binding, tscope) => (binding, fromexpr(c, tscope, TypeScope)) e => (e, TypeScope()) @@ -144,12 +144,12 @@ end function judgmenthead(theory::GAT, _, judgment::AlgTermConstructor) name = nameof(getdecl(judgment)) untyped = Expr(:call, name, nameof.(argsof(judgment))...) - c = Presentation(theory, judgment.localcontext) + c = GATContext(theory, judgment.localcontext) Expr(:(::), untyped, toexpr(c, judgment.type)) end function judgmenthead(theory::GAT, name, judgment::AlgAxiom) - c = Presentation(theory, judgment.localcontext) + c = GATContext(theory, judgment.localcontext) untyped = Expr(:call, :(==), toexpr(c, judgment.equands[1]), toexpr(c, judgment.equands[2])) if isnothing(name) untyped @@ -172,7 +172,7 @@ function toexpr(c::GAT, binding::Binding{Judgment}) Expr(:call, :(⊣), head, Expr(:vect, bindingexprs(c, judgment.localcontext)...)) end -function toexpr(c::Context, p::Presentation) +function toexpr(c::Context, p::GATContext) c == p.theory || error("Invalid context for presentation") decs = GATs.bindingexprs(c, p.scope) eqs = map(p.eqs) do ts @@ -201,7 +201,7 @@ function parse_scope!(f::Function, scope::Scope, lines::AbstractVector) scope end -function fromexpr(c::Presentation, e, ::Type{Binding{AlgType}}; line=nothing) +function fromexpr(c::GATContext, e, ::Type{Binding{AlgType}}; line=nothing) @match e begin Expr(:(::), name::Symbol, type_expr) => Binding{AlgType}(name, fromexpr(c, type_expr, AlgType), line) @@ -209,7 +209,7 @@ function fromexpr(c::Presentation, e, ::Type{Binding{AlgType}}; line=nothing) end end -function parse_binding_expr!(c::Presentation, pushbinding!, e) +function parse_binding_expr!(c::GATContext, pushbinding!, e) p!(name, type_expr) = pushbinding!(Binding{AlgType}(name, fromexpr(c, type_expr, AlgType))) @match e begin a::Symbol => p!(a, :default) @@ -224,7 +224,7 @@ function parse_binding_expr!(c::Presentation, pushbinding!, e) end end -function fromexpr(p::Presentation, e, ::Type{TypeScope}) +function fromexpr(p::GATContext, e, ::Type{TypeScope}) ts = TypeScope() c = AppendContext(p, ts) parse_scope!(ts.scope, e.args) do pushbinding!, arg @@ -234,7 +234,7 @@ function fromexpr(p::Presentation, e, ::Type{TypeScope}) end function parseargs!(theory::GAT, exprs::AbstractVector, scope::TypeScope) - c = Presentation(theory, scope) + c = GATContext(theory, scope) map(exprs) do expr binding_expr = @match expr begin a::Symbol => getlid(ident(scope; name=a)) @@ -251,7 +251,7 @@ end function parseaxiom!(theory::GAT, localcontext, sort_expr, e; name=nothing) @match e begin Expr(:call, :(==), lhs_expr, rhs_expr) => begin - c = Presentation(theory, localcontext) + c = GATContext(theory, localcontext) equands = fromexpr.(Ref(c), [lhs_expr, rhs_expr], Ref(AlgTerm)) sorts = sortcheck.(Ref(c), equands) @assert allequal(sorts) @@ -294,7 +294,7 @@ function parseconstructor!(theory::GAT, localcontext, type_expr, e) end end _ => begin - c = Presentation(theory, localcontext) + c = GATContext(theory, localcontext) type = fromexpr(c, type_expr, AlgType) decl = if hasname(theory, name) ident(theory; name) @@ -338,7 +338,7 @@ function parse_binding_line!(theory::GAT, e, linenumber) e => (e, TypeScope()) end - c = Presentation(theory, localcontext) + c = GATContext(theory, localcontext) (head, type_expr) = @match binding begin Expr(:(::), head, type_expr) => (head, type_expr) diff --git a/src/syntax/gats/gat.jl b/src/syntax/gats/gat.jl index de923a95..d29c2e1a 100644 --- a/src/syntax/gats/gat.jl +++ b/src/syntax/gats/gat.jl @@ -180,30 +180,30 @@ Base.issubset(t1::GAT, t2::GAT) = all(s->hastag(t2, s), gettag.(Scopes.getscopelist(t1).scopes)) """ -`Presentation` +`GATContext` A context consisting of two parts: a GAT and a TypeCtx -Certain types (like AlgTerm) can only be parsed in a Presentation, because +Certain types (like AlgTerm) can only be parsed in a GATContext, because they require access to the method resolving in the GAT. """ -struct Presentation <: HasContext{Union{Judgment, AlgType}} +struct GATContext <: HasContext{Union{Judgment, AlgType}} theory::GAT context::Context{AlgType} end -Presentation(theory::GAT) = Presentation(theory, EmptyContext{AlgType}()) +GATContext(theory::GAT) = GATContext(theory, EmptyContext{AlgType}()) -gettheory(p::Presentation) = p.theory +gettheory(p::GATContext) = p.theory -gettypecontext(p::Presentation) = p.context +gettypecontext(p::GATContext) = p.context -Scopes.getcontext(c::Presentation) = AppendContext(c.theory, c.context) +Scopes.getcontext(c::GATContext) = AppendContext(c.theory, c.context) -Scopes.AppendContext(c::Presentation, context::Context{AlgType}) = - Presentation(c.theory, AppendContext(c.context, context)) +Scopes.AppendContext(c::GATContext, context::Context{AlgType}) = + GATContext(c.theory, AppendContext(c.context, context)) -function methodlookup(c::Presentation, x::Ident, sig::AlgSorts) +function methodlookup(c::GATContext, x::Ident, sig::AlgSorts) theory = c.theory if haskey(theory.resolvers, x) && haskey(theory.resolvers[x].bysignature, sig) resolvemethod(theory.resolvers[x], sig) diff --git a/src/syntax/module.jl b/src/syntax/module.jl index b5ee07a3..ce2a9add 100644 --- a/src/syntax/module.jl +++ b/src/syntax/module.jl @@ -5,14 +5,14 @@ using Reexport include("Scopes.jl") include("ExprInterop.jl") include("GATs.jl") -include("Presentations.jl") +include("GATContexts.jl") include("TheoryInterface.jl") include("TheoryMaps.jl") @reexport using .Scopes @reexport using .ExprInterop @reexport using .GATs -@reexport using .Presentations +@reexport using .GATContexts @reexport using .TheoryInterface @reexport using .TheoryMaps diff --git a/test/syntax/Presentations.jl b/test/syntax/GATContexts.jl similarity index 93% rename from test/syntax/Presentations.jl rename to test/syntax/GATContexts.jl index 977b8de7..7abf14a5 100644 --- a/test/syntax/Presentations.jl +++ b/test/syntax/GATContexts.jl @@ -1,15 +1,15 @@ -module TestPresentations +module TestGATContexts using GATlab using Test T = ThCategory.THEORY -ctx = Presentation(T) +ctx = GATContext(T) tscope = fromexpr( ctx, :([(a,b,c)::Ob, f::Hom(a,b), g::Hom(b,c), (h,h′)::Hom(a,c), f⋅g == h, h == h′]), TypeScope ) -p1 = Presentation(T, tscope) +p1 = GATContext(T, tscope) # HasContext interface @test nscopes(p1) == nscopes(T) + 1 diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index 248c73c2..e1d6659f 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -60,7 +60,7 @@ A = ident(sortscope; name=:A) ATerm = AlgTerm(A) -c = Presentation(thcat, sortscope) +c = GATContext(thcat, sortscope) HomT = fromexpr(c, :(Hom(A, A)), AlgType) HomS = AlgSort(HomT) diff --git a/test/syntax/tests.jl b/test/syntax/tests.jl index 26a94697..aa62d042 100644 --- a/test/syntax/tests.jl +++ b/test/syntax/tests.jl @@ -15,8 +15,8 @@ end include("GATs.jl") end -@testset "Presentations" begin - include("Presentations.jl") +@testset "GATContexts" begin + include("GATContexts.jl") end @testset "TheoryInterface" begin From 79b6094ecf99df398857701b05b1781c490d77e6 Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Mon, 9 Oct 2023 14:40:43 -0700 Subject: [PATCH 10/12] Meta module inside theory module, old presentations --- src/models/ModelInterface.jl | 4 +- src/models/Presentations.jl | 259 +++++++++++++++++++++++++++++++++ src/models/SymbolicModels.jl | 14 +- src/models/module.jl | 2 + src/syntax/GATContexts.jl | 8 +- src/syntax/TheoryInterface.jl | 16 +- src/syntax/TheoryMaps.jl | 8 +- test/models/ModelInterface.jl | 4 +- test/models/Presentations.jl | 166 +++++++++++++++++++++ test/models/SymbolicModels.jl | 2 +- test/models/tests.jl | 1 + test/stdlib/models/GATs.jl | 2 +- test/syntax/GATContexts.jl | 10 +- test/syntax/GATs.jl | 4 +- test/syntax/TheoryInterface.jl | 8 +- test/syntax/TheoryMaps.jl | 16 +- 16 files changed, 481 insertions(+), 43 deletions(-) create mode 100644 src/models/Presentations.jl create mode 100644 test/models/Presentations.jl diff --git a/src/models/ModelInterface.jl b/src/models/ModelInterface.jl index 5dcd0b9a..25357250 100644 --- a/src/models/ModelInterface.jl +++ b/src/models/ModelInterface.jl @@ -82,7 +82,7 @@ implements(m::Module, ::Type{Val{tag}}) where {tag} = nothing implements(m::Model, tag::ScopeTag) = implements(m, Val{tag}) implements(m::Model, theory_module::Module) = - all(!isnothing(implements(m, gettag(scope))) for scope in theory_module.THEORY.segments.scopes) + all(!isnothing(implements(m, gettag(scope))) for scope in theory_module.Meta.theory.segments.scopes) struct TypeCheckFail <: Exception model::Union{Model, Nothing} @@ -140,7 +140,7 @@ macro instance(head, model, body) end # Get the underlying theory - theory = macroexpand(__module__, :($theory_module.@theory)) + theory = macroexpand(__module__, :($theory_module.Meta.@theory)) # A dictionary to look up the Julia type of a type constructor from its name (an ident) jltype_by_sort = isnothing(instance_types) ? nothing : Dict(zip(sorts(theory), instance_types)) # for type checking diff --git a/src/models/Presentations.jl b/src/models/Presentations.jl new file mode 100644 index 00000000..70dda11a --- /dev/null +++ b/src/models/Presentations.jl @@ -0,0 +1,259 @@ +""" Finite presentations of a model of a generalized algebraic theory (GAT). + +We support two methods for defining models of a GAT: as Julia objects using the +`@instance` macro and as syntactic objects using the `@present` macro. +Instances are useful for casting generic data structures, such as matrices, +abstract tensor systems, and wiring diagrams, in categorical language. +Presentations define small categories by generators and relations and are useful +in applications like knowledge representation. +""" +module Presentations +export @present, Presentation, generator, generators, generator_index, + has_generator, equations, add_generator!, add_generators!, add_definition!, + add_equation!, add_equations!, change_theory + +using Base.Meta: ParseError +using MLStyle: @match + +using ...Util.MetaUtils +using ...Syntax +import ...Syntax: equations +using ..SymbolicModels +import ..SymbolicModels: to_json_sexpr, parse_json_sexpr +using ..GATExprUtils + + +# Data types +############ + +struct Presentation{Theory,Name} + syntax::Module + generators::NamedTuple + generator_name_index::Dict{Name,Pair{Symbol,Int}} + equations::Vector{Pair} +end + +function Presentation{Name}(syntax::Module) where Name + theory_module = syntax.THEORY_MODULE + theory = theory_module.Meta.theory + T = theory_module.Meta.T + names = Tuple(nameof(sort) for sort in sorts(theory)) + vectors = ((getfield(syntax, name){:generator})[] for name in names) + Presentation{T,Name}(syntax, NamedTuple{names}(vectors), + Dict{Name,Pair{Symbol,Int}}(), Pair[]) +end + +Presentation(syntax::Module) = Presentation{Symbol}(syntax) + +function Base.:(==)(pres1::Presentation, pres2::Presentation) + pres1.syntax == pres2.syntax && pres1.generators == pres2.generators && + pres1.equations == pres2.equations +end +""" +Move a presentation to a new syntax, +duplicating all the data on shared names. In particular, +this is lossless if all the generators of the original +presentation are at names in the new syntax. +""" +function change_theory(syntax::Module,pres::Presentation{S,Name}) where {S,Name} + T = syntax.theory() + pres_new = Presentation(syntax) + types = intersect(keys(pres_new.generators),keys(pres.generators)) + for t in types map(pres.generators[t]) do x + add_generator!(pres_new,generator_switch_syntax(syntax,x)) end end + #XX: test on equations + pres_new +end +function Base.copy(pres::Presentation{T,Name}) where {T,Name} + Presentation{T,Name}(pres.syntax, map(copy, pres.generators), + copy(pres.generator_name_index), copy(pres.equations)) +end + +# Presentation +############## + +""" Get all generators of a presentation. +""" +generators(pres::Presentation) = collect(Iterators.flatten(pres.generators)) +generators(pres::Presentation, type::Symbol) = pres.generators[type] +generators(pres::Presentation, type::Type) = generators(pres, nameof(type)) + +""" Retrieve generators by name. + +Generators can also be retrieved using indexing notation, so that +`generator(pres, name)` and `pres[name]` are equivalent. +""" +function generator(pres::Presentation, name) + type, index = pres.generator_name_index[name] + pres.generators[type][index] +end +Base.getindex(pres::Presentation, name) = generator.(Ref(pres), name) + +""" Does the presentation contain a generator with the given name? +""" +function has_generator(pres::Presentation, name) + haskey(pres.generator_name_index, name) +end + +""" Add a generator to a presentation. +""" +function add_generator!(pres::Presentation, expr) + name, type = first(expr), gat_typeof(expr) + generators = pres.generators[type] + if !isnothing(name) + if haskey(pres.generator_name_index, name) + error("Name $name already defined in presentation") + end + pres.generator_name_index[name] = type => length(generators)+1 + end + push!(generators, expr) + expr +end + +""" Add iterable of generators to a presentation. +""" +function add_generators!(pres::Presentation, exprs) + for expr in exprs + add_generator!(pres, expr) + end +end + +""" Get all equations of a presentation. +""" +equations(pres::Presentation) = pres.equations + +""" Add an equation between terms to a presentation. +""" +add_equation!(pres::Presentation, lhs::GATExpr, rhs::GATExpr) = + push!(pres.equations, lhs => rhs) + +""" Add sequence of equations to a presentation. +""" +add_equations!(pres::Presentation, eqs) = append!(pres.equations, eqs) + +""" Add a generator defined by an equation. +""" +function add_definition!(pres::Presentation, name::Symbol, rhs::GATExpr) + generator = SymbolicModels.generator_like(rhs, name) + add_generator!(pres, generator) + add_equation!(pres, generator, rhs) + generator +end + +""" Get the index of a generator, relative to generators of same GAT type. +""" +function generator_index(pres::Presentation{T,Name}, name::Name) where {T,Name} + last(pres.generator_name_index[name]) +end +function generator_index(pres::Presentation, expr::GATExpr{:generator}) + name = first(expr) + !isnothing(name) || error("Cannot lookup unnamed generator by name") + generator_index(pres, name) +end + +""" Shorthand for contructing a term in the presentation +""" +function make_term(pres::Presentation, expr) + @match expr begin + ::Symbol => pres[expr] + Expr(:call, term_constructor, args...) => + invoke_term(pres.syntax, term_constructor, + map(arg -> make_term(pres, arg), args)) + end +end + +""" Create a new generator in a presentation of a given type +""" +function make_generator(pres::Presentation, name::Union{Symbol,Nothing}, + type::Symbol, type_args::Vector) + invoke_term(pres.syntax, type, [name; + map(e -> make_term(pres, e), type_args)]) +end + +""" Create and add a new generator +""" +function construct_generator!(pres::Presentation, name::Union{Symbol,Nothing}, + type::Symbol, type_args::Vector=[]) + add_generator!(pres, make_generator(pres, name, type, type_args)) +end + +""" Create and add multiple generators +""" +function construct_generators!(pres::Presentation, names::AbstractVector, + type::Symbol, type_args::Vector=[]) + for name in names + construct_generator!(pres, name, type, type_args) + end +end + +# Presentation Definition +######################### + +function add_to_presentation(pres, block) + pres = copy(pres) + @match strip_lines(block) begin + Expr(:block, lines...) => + for line in lines + eval_stmt!(pres, line) + end + _ => error("Must pass in a block") + end + pres +end + +function parse_type_expr(type_expr) + @match type_expr begin + Expr(:call, f::Symbol, args...) => (f,[args...]) + f::Symbol => (f,[]) + _ => error("Ill-formed type expression $type_expr") + end +end + +function eval_stmt!(pres::Presentation, stmt::Expr) + @match stmt begin + Expr(:(::), name::Symbol, type_expr) => + construct_generator!(pres, name, parse_type_expr(type_expr)...) + Expr(:(::), Expr(:tuple, names...), type_expr) => + construct_generators!(pres, [names...], parse_type_expr(type_expr)...) + Expr(:(::), type_expr) => + construct_generator!(pres, nothing, parse_type_expr(type_expr)...) + Expr(:(:=), name::Symbol, def_expr) => + add_definition!(pres, name, make_term(pres, def_expr)) + Expr(:call, :(==), lhs, rhs) => + add_equation!(pres, make_term(pres, lhs), make_term(pres, rhs)) + end +end + +# Presentation macro +#################### + +""" Define a presentation using a convenient syntax. +""" +macro present(head, body) + name, pres = @match head begin + Expr(:call, name::Symbol, syntax_name::Symbol) => + (name, :($(GlobalRef(Presentations, :Presentation))($(esc(syntax_name))))) + Expr(:(<:), name::Symbol, parent::Symbol) => (name,:(copy($(esc(parent))))) + _ => throw(ParseError("Ill-formed presentation header $head")) + end + quote + $(esc(name)) = $(esc(add_to_presentation))($pres, $(Expr(:quote, body))) + end +end + +# Serialization +############### + +function to_json_sexpr(pres::Presentation, expr::GATExpr) + to_json_sexpr(expr; + by_reference = name -> has_generator(pres, name)) +end + +function parse_json_sexpr(pres::Presentation{Theory,Name}, + syntax::Module, sexpr) where {Theory,Name} + parse_json_sexpr(syntax, sexpr; + symbols = Name == Symbol, + parse_reference = name -> generator(pres, name)) +end + +end diff --git a/src/models/SymbolicModels.jl b/src/models/SymbolicModels.jl index 94a56a72..80e86fb9 100644 --- a/src/models/SymbolicModels.jl +++ b/src/models/SymbolicModels.jl @@ -226,7 +226,7 @@ end macro symbolic_model(decl, theoryname, body) # Part 0: Parsing input - theory = macroexpand(__module__, :($theoryname.@theory)) + theory = macroexpand(__module__, :($theoryname.Meta.@theory)) (name, abstract_types) = @match decl begin Expr(:curly, name, abstract_types...) => (name, abstract_types) @@ -486,7 +486,7 @@ end function invoke_term(syntax_module::Module, constructor_name::Symbol, args) theory_module = syntax_module.THEORY_MODULE - theory = theory_module.THEORY + theory = theory_module.Meta.theory syntax_types = Tuple(getfield(syntax_module, nameof(sort)) for sort in sorts(theory)) invoke_term(theory_module, syntax_types, constructor_name, args) end @@ -501,6 +501,14 @@ Get syntax module of given expression. """ syntax_module(expr::GATExpr) = parentmodule(typeof(expr)) + +""" Create generator of the same type as the given expression. +""" +function generator_like(expr::GATExpr, value)::GATExpr + invoke_term(syntax_module(expr), gat_typeof(expr), + [value; gat_type_args(expr)]) +end + # Functors ########## @@ -588,7 +596,7 @@ function parse_json_sexpr(syntax_module::Module, sexpr; symbols::Bool = true, ) theory_module = syntax_module.THEORY_MODULE - theory = theory_module.THEORY + theory = theory_module.Meta.theory type_lens = Dict( nameof(getdecl(getvalue(binding))) => length(getvalue(binding).args) for binding in [theory[sort.method] for sort in sorts(theory)] diff --git a/src/models/module.jl b/src/models/module.jl index d44fc8d2..6baa4e86 100644 --- a/src/models/module.jl +++ b/src/models/module.jl @@ -5,9 +5,11 @@ using Reexport include("ModelInterface.jl") include("SymbolicModels.jl") include("GATExprUtils.jl") +include("Presentations.jl") @reexport using .ModelInterface @reexport using .SymbolicModels @reexport using .GATExprUtils +@reexport using .Presentations end diff --git a/src/syntax/GATContexts.jl b/src/syntax/GATContexts.jl index 77d3a6b0..b8da5614 100644 --- a/src/syntax/GATContexts.jl +++ b/src/syntax/GATContexts.jl @@ -1,5 +1,5 @@ module GATContexts -export GATContext, @present +export GATContext, @gatcontext using ...Util using ..Scopes, ..GATs @@ -25,11 +25,11 @@ function fromexpr(p::GATContext, e, ::Type{GATContext}) GATContext(gettheory(p), ScopeList([allscopes(gettypecontext(p)); newscope])) end -macro present(head, body) +macro gatcontext(head, body) (parent, name) = @match head begin - Expr(:call, name, mod) => (:($(GATContext)($(mod).THEORY)), name) + Expr(:call, name, mod) => (:($(GATContext)($(mod).Meta.theory)), name) Expr(:(<:), name, parent) => (parent, name) - _ => error("invalid head for @present macro: $head") + _ => error("invalid head for @gatcontext macro: $head") end esc(quote diff --git a/src/syntax/TheoryInterface.jl b/src/syntax/TheoryInterface.jl index 7aedbf5c..b85a8e5f 100644 --- a/src/syntax/TheoryInterface.jl +++ b/src/syntax/TheoryInterface.jl @@ -30,7 +30,7 @@ For all aliases, `const` declarations that make them equal to their primaries. A macro called `@theory` which expands to the `GAT` data structure for the module. -A constant called `THEORY` which is the `GAT` data structure. +A constant called `Meta.theory` which is the `GAT` data structure. """ """ @@ -55,7 +55,7 @@ function theory_impl(head, body, __module__) end parent = if !isnothing(parentname) - macroexpand(__module__, :($parentname.@theory)) + macroexpand(__module__, :($parentname.Meta.@theory)) else GAT(:_EMPTY) end @@ -83,10 +83,12 @@ function theory_impl(head, body, __module__) push!(modulelines, Expr(:using, Expr(:(.), :(.), :(.), parentname))) end - push!(modulelines, :(const THEORY = $theory)) - - push!(modulelines, :(macro theory() $theory end)) - push!(modulelines, :(macro theory_module() @__MODULE__ end)) + push!(modulelines, Expr(:toplevel, :(module Meta + struct T end + const theory = $theory + macro theory() $theory end + macro theory_module() parentmodule(@__MODULE__) end + end))) for binding in newsegment judgment = getvalue(binding) @@ -135,7 +137,7 @@ function juliadeclaration(name::Symbol, judgment::AlgDeclaration) end function invoke_term(theory_module, types, name, args; model=nothing) - theory = theory_module.THEORY + theory = theory_module.Meta.theory method = getproperty(theory_module, name) type_idx = findfirst(==(name), nameof.(sorts(theory))) if !isnothing(type_idx) && length(args) <= 1 diff --git a/src/syntax/TheoryMaps.jl b/src/syntax/TheoryMaps.jl index 9bec2558..c679b3e0 100644 --- a/src/syntax/TheoryMaps.jl +++ b/src/syntax/TheoryMaps.jl @@ -396,10 +396,10 @@ macro theorymap(head, body) _ => error("could not parse head of @theory: $head") end - dommod = macroexpand(__module__, :($domname.@theory_module)) - codommod = macroexpand(__module__, :($codomname.@theory_module)) - dom = macroexpand(__module__, :($domname.@theory)) - codom = macroexpand(__module__, :($codomname.@theory)) + dommod = macroexpand(__module__, :($domname.Meta.@theory_module)) + codommod = macroexpand(__module__, :($codomname.Meta.@theory_module)) + dom = macroexpand(__module__, :($domname.Meta.@theory)) + codom = macroexpand(__module__, :($codomname.Meta.@theory)) tmap = fromexpr(dom, codom, body, TheoryMap) esc( diff --git a/test/models/ModelInterface.jl b/test/models/ModelInterface.jl index b960e62e..55181643 100644 --- a/test/models/ModelInterface.jl +++ b/test/models/ModelInterface.jl @@ -38,8 +38,8 @@ try ThCategory.Hom[FinSetC()]([1,2,3], 3, 2) catch e @test e.model == FinSetC() - @test e.theory == ThCategory.THEORY - @test e.type == ident(ThCategory.THEORY; name=:Hom) + @test e.theory == ThCategory.Meta.theory + @test e.type == ident(ThCategory.Meta.theory; name=:Hom) @test e.val == [1, 2, 3] @test e.args == [3, 2] @test e.reason == "index not in codomain: 3" diff --git a/test/models/Presentations.jl b/test/models/Presentations.jl new file mode 100644 index 00000000..aad0fcc0 --- /dev/null +++ b/test/models/Presentations.jl @@ -0,0 +1,166 @@ +module TestPresentations + +using Test +using GATlab + +presentation_theory(::Presentation{Theory}) where Theory = Theory + +# Presentation +############## + + +@symbolic_model FreeCategory{GATExpr, GATExpr} ThCategory begin + compose(f::Hom, g::Hom) = associate(new(f,g)) +end +using .ThCategory +A, B, C = Ob.(Ref(FreeCategory.Ob),[:A, :B, :C]) +f, g, h = Hom(:f, A, B), Hom(:g, B, C), Hom(:h, A, C) + +# Generators +pres = Presentation(FreeCategory) +@test presentation_theory(pres) == ThCategory.Meta.T +@test !has_generator(pres, :A) +add_generator!(pres, A) +@test generators(pres) == [A] +@test generator(pres, :A) == A +@test has_generator(pres, :A) +add_generator!(pres, B) +@test generators(pres) == [A, B] +@test_throws Exception add_generator!(pres, A) +@test pres[:A] == A +@test pres[[:A,:B]] == [A, B] +@test generator_index(pres, :B) == 2 +@test generator_index(pres, B) == 2 + +add_generators!(pres, (f,g)) +@test generators(pres) == [A, B, f, g] +@test generators(pres, :Ob) == [A, B] +@test generators(pres, :Hom) == [f, g] +@test generators(pres, FreeCategory.Ob) == [A, B] +@test generators(pres, FreeCategory.Hom) == [f, g] + +# Equations +add_equation!(pres, compose(f,g), h) +@test length(equations(pres)) == 1 + +f′, g′ = Hom(:f′, A, B), Hom(:g′, B, C) +add_generators!(pres, [f′, g′]) +add_equations!(pres, [f => f′, g => g′]) +@test length(equations(pres)) == 3 + +# Presentation macro +#################### + +@present Company(FreeCategory) begin + # Primitive concepts. + Employee::Ob + Department::Ob + Str::Ob + + first_name::Hom(Employee, Str) + last_name::Hom(Employee, Str) + manager::Hom(Employee, Employee) + works_in::Hom(Employee, Department) + secretary::Hom(Department, Employee) + + # Defined concepts. + second_level_manager := compose(manager, manager) + third_level_manager := compose(manager, compose(manager, manager)) + + # Managers work in the same department as their employees. + compose(manager, works_in) == works_in + # The secretary of a department works in that department. + compose(secretary, works_in) == id(Department) +end + +# Check type parameter. +@test presentation_theory(Company) == ThCategory.Meta.T + +# Check generators. +Employee, Department, Str = Ob.(Ref(FreeCategory.Ob),[:Employee, :Department, :Str]) +@test generators(Company) == [ + Employee, + Department, + Str, + Hom(:first_name, Employee, Str), + Hom(:last_name, Employee, Str), + Hom(:manager, Employee, Employee), + Hom(:works_in, Employee, Department), + Hom(:secretary, Department, Employee), + Hom(:second_level_manager, Employee, Employee), + Hom(:third_level_manager, Employee, Employee), +] + +# Check equations. +manager = Hom(:manager, Employee, Employee) +works_in = Hom(:works_in, Employee, Department) +secretary = Hom(:secretary, Department, Employee) +@test equations(Company) == [ + Hom(:second_level_manager, Employee, Employee) => compose(manager, manager), + Hom(:third_level_manager, Employee, Employee) => compose(manager, compose(manager, manager)), + compose(manager, works_in) => works_in, + compose(secretary, works_in) => id(Department), +] + +# Generators with compound type arguments. + +using .ThStrictMonCat +@symbolic_model FreeMC{GATExpr, GATExpr} ThStrictMonCat begin + compose(f::Hom, g::Hom) = associate(new(f,g)) +end + +@present C(FreeMC) begin + A::Ob + B::Ob + f::Hom(mcompose(A,B),mcompose(B,A)) + scalar::Hom(munit(),munit()) # Nullary case. +end +A, B = Ob(FreeMC.Ob, :A), Ob(FreeMC.Ob, :B) +I = munit(FreeMC.Ob) +@test generator(C, :f) == Hom(:f, mcompose(A,B), mcompose(B,A)) +@test generator(C, :scalar) == Hom(:scalar, I, I) + +# Inheritance. +@present SchSet(FreeCategory) begin + X::Ob +end +@present SchDDS <: SchSet begin + Φ::Hom(X,X) +end +X = Ob(FreeCategory.Ob, :X) +Φ = Hom(:Φ, X, X) +@test generators(SchDDS, :Ob) == [X] +@test generators(SchDDS, :Hom) == [Φ] + +# Abbreviated syntax. +@present SchGraph(FreeCategory) begin + V::Ob + E::Ob + src::Hom(E,V) + tgt::Hom(E,V) +end +@present SchGraph′(FreeCategory) begin + (V, E)::Ob + (src, tgt)::Hom(E,V) +end +@test SchGraph == SchGraph′ + +# Serialization +############### + +to_json(expr) = to_json_sexpr(Company, expr) +from_json(sexpr) = parse_json_sexpr(Company, FreeCategory, sexpr) + +# To JSON +to_json(generator(Company, :Employee)) == "Employee" +to_json(generator(Company, :manager)) == "manager" +to_json(compose(generator(Company, :manager), generator(Company, :manager))) == + ["compose", "manager", "manager"] + +# From JSON +@test from_json("Employee") == generator(Company, :Employee) +@test from_json("manager") == generator(Company, :manager) +@test from_json(["compose", "manager", "manager"]) == + compose(generator(Company, :manager), generator(Company, :manager)) + +end diff --git a/test/models/SymbolicModels.jl b/test/models/SymbolicModels.jl index e62cf5a2..f522f992 100644 --- a/test/models/SymbolicModels.jl +++ b/test/models/SymbolicModels.jl @@ -122,7 +122,7 @@ module CatTests using GATlab, Test -@theory ThCategory begin +@theory ThCategory begin # should this be deleted? Ob::TYPE Hom(dom::Ob, codom::Ob)::TYPE diff --git a/test/models/tests.jl b/test/models/tests.jl index 08d0b65c..2a75f09b 100644 --- a/test/models/tests.jl +++ b/test/models/tests.jl @@ -2,5 +2,6 @@ module ModelTests include("ModelInterface.jl") include("SymbolicModels.jl") +include("Presentations.jl") end diff --git a/test/stdlib/models/GATs.jl b/test/stdlib/models/GATs.jl index 04bb366a..abd0aaeb 100644 --- a/test/stdlib/models/GATs.jl +++ b/test/stdlib/models/GATs.jl @@ -15,7 +15,7 @@ end codom(SwapMonoid.MAP) == dom(NatPlusMonoid.MAP) - x = toexpr(compose(id(ThMonoid.THEORY), compose(SwapMonoid.MAP, NatPlusMonoid.MAP))) + x = toexpr(compose(id(ThMonoid.Meta.theory), compose(SwapMonoid.MAP, NatPlusMonoid.MAP))) @test x == toexpr(expected.MAP) diff --git a/test/syntax/GATContexts.jl b/test/syntax/GATContexts.jl index 7abf14a5..8d56c778 100644 --- a/test/syntax/GATContexts.jl +++ b/test/syntax/GATContexts.jl @@ -2,7 +2,7 @@ module TestGATContexts using GATlab using Test -T = ThCategory.THEORY +T = ThCategory.Meta.theory ctx = GATContext(T) tscope = fromexpr( ctx, @@ -19,7 +19,7 @@ p1 = GATContext(T, tscope) @test !hasname(p1, :q) @test getlevel(p1, :id) < getlevel(p1, :f) -@present SchGraph(ThCategory) begin +@gatcontext SchGraph(ThCategory) begin (E,V)::Ob src::Hom(E,V) tgt::Hom(E,V) @@ -30,7 +30,7 @@ Hom = ident(SchGraph; name=:Hom) @test getvalue(SchGraph[src]).body.head == Hom -@present SchFiberedGraph <: SchGraph begin +@gatcontext SchFiberedGraph <: SchGraph begin (FE, FV)::Ob fsrc::(FE → FV) ftgt::(FE → FV) @@ -42,7 +42,7 @@ end @test nscopes(gettypecontext(SchFiberedGraph)) == 2 -@present Z(ThGroup) begin +@gatcontext Z(ThGroup) begin (a,) end @@ -51,7 +51,7 @@ a = ident(Z; name=:a) @test compile(Dict(a => :a), t; theorymodule=ThGroup) == :($(ThGroup).:(⋅)($(ThGroup).i(a), 2)) -@present D₄(ThGroup) begin +@gatcontext D₄(ThGroup) begin (r,f) :: default (f⋅f) == e() diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index e1d6659f..d0f67360 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -93,8 +93,8 @@ BTerm_expected = AlgTerm(ident(Bsortscope;name=:B)) # Subset #------- -T = ThCategory.THEORY -TG = ThGraph.THEORY +T = ThCategory.Meta.theory +TG = ThGraph.Meta.theory @test TG ⊆ T @test T ⊈ TG diff --git a/test/syntax/TheoryInterface.jl b/test/syntax/TheoryInterface.jl index cfa15b3c..f5eae269 100644 --- a/test/syntax/TheoryInterface.jl +++ b/test/syntax/TheoryInterface.jl @@ -10,8 +10,8 @@ end @test ThCategoryTypes.Ob isa Function @test ThCategoryTypes.Hom isa Function -@test Set(allnames(ThCategoryTypes.THEORY)) == Set([:Ob, :Hom, :dom, :codom]) -@test Set(allnames(ThCategoryTypes.THEORY; aliases=true)) == Set([:Ob, :Hom, :(→), :dom, :codom]) +@test Set(allnames(ThCategoryTypes.Meta.theory)) == Set([:Ob, :Hom, :dom, :codom]) +@test Set(allnames(ThCategoryTypes.Meta.theory; aliases=true)) == Set([:Ob, :Hom, :(→), :dom, :codom]) using .ThCategoryTypes @@ -30,8 +30,8 @@ using .ThLawlessCategory @test compose isa Function @test compose == (⋅) @test parentmodule(id) == ThLawlessCategory -@test Set(allnames(ThLawlessCategory.THEORY)) == Set([:Ob, :Hom, :dom, :codom, :compose, :id]) -@test nameof(ThLawlessCategory.THEORY) == :ThLawlessCategory +@test Set(allnames(ThLawlessCategory.Meta.theory)) == Set([:Ob, :Hom, :dom, :codom, :compose, :id]) +@test nameof(ThLawlessCategory.Meta.theory) == :ThLawlessCategory @test_throws Exception @eval @theory ThDoubleCategory <: ThCategory begin Hom(dom::Ob, codom::Ob) :: TYPE diff --git a/test/syntax/TheoryMaps.jl b/test/syntax/TheoryMaps.jl index ce11f01f..850649ba 100644 --- a/test/syntax/TheoryMaps.jl +++ b/test/syntax/TheoryMaps.jl @@ -6,11 +6,11 @@ using Test # Set up ######## -T = ThCategory.THEORY -TP = ThPreorder.THEORY -TLC = ThLawlessCat.THEORY -TM = ThMonoid.THEORY -TNP = ThNatPlus.THEORY +T = ThCategory.Meta.theory +TP = ThPreorder.Meta.theory +TLC = ThLawlessCat.Meta.theory +TM = ThMonoid.Meta.theory +TNP = ThNatPlus.Meta.theory PC = PreorderCat.MAP NP = NatPlusMonoid.MAP @@ -73,7 +73,7 @@ end xterm = fromexpr(TM, :(x ⊣ [x]), TermInCtx) res = NP(xterm) -toexpr(ThNat.THEORY, res) +toexpr(ThNat.Meta.theory, res) xterm = fromexpr(TM, :(e()⋅(e()⋅x) ⊣ [x]), TermInCtx) res = NP(xterm) @@ -103,8 +103,8 @@ end incl = TheoryIncl(TLC, T) @test TheoryMaps.dom(incl) == TLC @test TheoryMaps.codom(incl) == T -incl2 = TheoryIncl(ThGraph.THEORY, TLC) -incl3 = TheoryIncl(ThGraph.THEORY, T) +incl2 = TheoryIncl(ThGraph.Meta.theory, TLC) +incl3 = TheoryIncl(ThGraph.Meta.theory, T) @test TheoryMaps.compose(incl2, incl) == incl3 From 45418d7ea313630714f0c71513443a8c9a4ed241 Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Mon, 9 Oct 2023 15:19:41 -0700 Subject: [PATCH 11/12] fix docs, more tests --- docs/src/concepts/theories.md | 27 +-------------------------- docs/src/stdlib.md | 15 ++++++--------- src/models/Presentations.jl | 18 ++---------------- src/syntax/Scopes.jl | 20 -------------------- test/syntax/GATs.jl | 7 +++++++ test/syntax/Scopes.jl | 3 +++ 6 files changed, 19 insertions(+), 71 deletions(-) diff --git a/docs/src/concepts/theories.md b/docs/src/concepts/theories.md index dd941c14..3cb7d887 100644 --- a/docs/src/concepts/theories.md +++ b/docs/src/concepts/theories.md @@ -2,33 +2,8 @@ A theory in GATlab consists of two parts. -1. A Julia value of type `Theory` that describes the theory. +1. A Julia value of type `GAT` that describes the theory. 2. A module named by the theory with various Julia types that give us handles for metaprogramming. -These metaprogramming types include: -- A singleton struct named `Th` and subtyping `AbstractTheory` that has an - overload of `gettheory` on it returning the Julia value from 1. This is used - to pass around the entire theory at the type level. -- A singleton struct for each type constructor and term constructor in the - theory. These are used in place of `Lvl` in types, so that backtraces are - more readable. - -Example: - -```julia -module Category -using GATlab.Theories - -struct Th <: AbstractTheory end - -Theories.gettheory(::Th) = ... - -struct Ob <: TypCon{1} end -struct Hom <: TypCon{2} end - -struct compose <: TrmCon{3} end -... -end -``` diff --git a/docs/src/stdlib.md b/docs/src/stdlib.md index b4623239..4fd24cd9 100644 --- a/docs/src/stdlib.md +++ b/docs/src/stdlib.md @@ -5,7 +5,7 @@ Our friend `ThCategory` is the main theory in this module. ```@docs -GATlab.Stdlib.StdTheories.Categories.ThCategory +GATlab.Stdlib.StdTheories.ThCategory ``` You can specialize a theory by adding more axioms. In this case we can specialize the theory of categories to that of thin category by adding the axiom that two morphisms are equal if they have the same domain and codomain. @@ -15,33 +15,30 @@ thineq := f == g :: Hom(A,B) ⊣ [A::Ob, B::Ob, f::Hom(A,B), g::Hom(A,B)] ``` ```@docs -GATlab.Stdlib.StdTheories.Categories.ThThinCategory +GATlab.Stdlib.StdTheories.ThThinCategory ``` ### Category Building Blocks The remaining theories in this module are not necessarily useful, but go to show demonstrate the theory hierarchy can be built up in small increments. ```@docs -GATlab.Stdlib.StdTheories.Categories.ThClass +GATlab.Stdlib.StdTheories.ThClass ``` ```@docs -GATlab.Stdlib.StdTheories.Categories.ThLawlessCat +GATlab.Stdlib.StdTheories.ThLawlessCat ``` ```@docs -GATlab.Stdlib.StdTheories.Categories.ThAscCat +GATlab.Stdlib.StdTheories.ThAscCat ``` ```@docs -GATlab.Stdlib.StdTheories.Categories.ThIdLawlessCat +GATlab.Stdlib.StdTheories.ThIdLawlessCat ``` ```@autodocs Modules = [GATlab.Stdlib, GATlab.Stdlib.StdTheories, - GATlab.Stdlib.StdTheories.Algebra, - GATlab.Stdlib.StdTheories.Monoidal, - GATlab.Stdlib.StdTheories.Naturals, GATlab.Stdlib.StdModels, GATlab.Stdlib.StdModels.FinSets, ] diff --git a/src/models/Presentations.jl b/src/models/Presentations.jl index 70dda11a..b1e7fb16 100644 --- a/src/models/Presentations.jl +++ b/src/models/Presentations.jl @@ -10,7 +10,7 @@ in applications like knowledge representation. module Presentations export @present, Presentation, generator, generators, generator_index, has_generator, equations, add_generator!, add_generators!, add_definition!, - add_equation!, add_equations!, change_theory + add_equation!, add_equations! using Base.Meta: ParseError using MLStyle: @match @@ -49,21 +49,7 @@ function Base.:(==)(pres1::Presentation, pres2::Presentation) pres1.syntax == pres2.syntax && pres1.generators == pres2.generators && pres1.equations == pres2.equations end -""" -Move a presentation to a new syntax, -duplicating all the data on shared names. In particular, -this is lossless if all the generators of the original -presentation are at names in the new syntax. -""" -function change_theory(syntax::Module,pres::Presentation{S,Name}) where {S,Name} - T = syntax.theory() - pres_new = Presentation(syntax) - types = intersect(keys(pres_new.generators),keys(pres.generators)) - for t in types map(pres.generators[t]) do x - add_generator!(pres_new,generator_switch_syntax(syntax,x)) end end - #XX: test on equations - pres_new -end + function Base.copy(pres::Presentation{T,Name}) where {T,Name} Presentation{T,Name}(pres.syntax, map(copy, pres.generators), copy(pres.generator_name_index), copy(pres.equations)) diff --git a/src/syntax/Scopes.jl b/src/syntax/Scopes.jl index ab6725d5..e9cb32f3 100644 --- a/src/syntax/Scopes.jl +++ b/src/syntax/Scopes.jl @@ -352,8 +352,6 @@ struct Scope{T} <: HasScope{T} end end -Scope(s::Scope) = s - Base.:(==)(s1::Scope, s2::Scope) = s1.tag == s2.tag Base.hash(s::Scope, h::UInt64) = hash(s.tag, h) @@ -701,24 +699,6 @@ function ScopeList{T}(scopes::Vector{<:HasScope{T}}) where {T} c end -function Scope(hsl::ScopeList{T}) where T - if nscopes(hsl) == 0 - Scope{T}() - else - res = Scope{T}() - newtag = gettag(res) - retagdict = Dict{ScopeTag, ScopeTag}() - for nextscope in getscope.(hsl.scopes) - retagdict[gettag(nextscope)] = newtag - nextscope = retag(retagdict, nextscope) - for b in getbindings(nextscope) - unsafe_pushbinding!(res, b) - end - end - res - end -end - function Base.copy(c::ScopeList{T}) where {T} ScopeList{T}(copy(c.scopes), copy(c.taglookup), copy(c.namelookup)) end diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index d0f67360..47e993bb 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -65,6 +65,9 @@ c = GATContext(thcat, sortscope) HomT = fromexpr(c, :(Hom(A, A)), AlgType) HomS = AlgSort(HomT) +@test rename(gettag(sortscope), Dict(:A=>:Z), HomT) isa AlgType +@test retag(Dict(gettag(sortscope)=>newscopetag()), HomT) isa AlgType + @test sortcheck(c, AlgTerm(A)) == ObS # # Good term and bad term @@ -98,6 +101,10 @@ TG = ThGraph.Meta.theory @test TG ⊆ T @test T ⊈ TG +# ToExpr +#------- +toexpr.(Ref(T), T.segments) + # InCtx #---------- # tic = fromexpr(T, :(compose(f,compose(id(b),id(b))) ⊣ [a::Ob, b::Ob, f::Hom(a,b)]), TermInCtx); diff --git a/test/syntax/Scopes.jl b/test/syntax/Scopes.jl index 64725a4c..bbce2075 100644 --- a/test/syntax/Scopes.jl +++ b/test/syntax/Scopes.jl @@ -90,6 +90,9 @@ bind_X, bind_Y = Binding{String}(:X, Alias(x)), Binding{String}(:Y, Alias(y)) xy_scope = Scope([bind_x, bind_y, bind_X, bind_Y]; tag=tag1) xy_scope′ = Scope([bind_x]; tag=tag1) +@test alltags(retag(Dict(tag1=>tag2),xy_scope)) == Set([tag2]) +@test identvalues(xy_scope′) == [x => "ex"] + @test xy_scope == xy_scope′ @test hash(xy_scope) == hash(xy_scope′) @test basicprinted(xy_scope) == "{$(basicprinted(bind_x)), $(basicprinted(bind_y)), X = x, Y = y}" From 76cbd998f4459f2cec9de6c9de2973a1ba59776a Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Mon, 9 Oct 2023 15:39:15 -0700 Subject: [PATCH 12/12] more tests, fewer problems --- src/syntax/gats/exprinterop.jl | 14 +------------- test/syntax/GATContexts.jl | 2 ++ test/syntax/GATs.jl | 6 ++++++ test/syntax/TheoryInterface.jl | 5 +++++ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/syntax/gats/exprinterop.jl b/src/syntax/gats/exprinterop.jl index 27ac0cb4..4211141d 100644 --- a/src/syntax/gats/exprinterop.jl +++ b/src/syntax/gats/exprinterop.jl @@ -67,7 +67,7 @@ function toexpr(c::Context, type::AlgType) Expr(:call, toexpr(c, type.body.head), toexpr.(Ref(c), type.body.args)...) end else - Expr(:call, :(==), type.body.equands...) + Expr(:call, :(==), toexpr.(Ref(c), type.body.equands)...) end end @@ -105,8 +105,6 @@ end # Judgments ########### -# toexpr - function toexpr(p::Context, b::Binding{AlgType}) val = getvalue(b) if isapp(val) @@ -172,16 +170,6 @@ function toexpr(c::GAT, binding::Binding{Judgment}) Expr(:call, :(⊣), head, Expr(:vect, bindingexprs(c, judgment.localcontext)...)) end -function toexpr(c::Context, p::GATContext) - c == p.theory || error("Invalid context for presentation") - decs = GATs.bindingexprs(c, p.scope) - eqs = map(p.eqs) do ts - exprs = zip(toexpr.(Ref(p), ts),Iterators.repeated(:(==))) - Expr(:comparison, collect(Iterators.flatten(exprs))[1:(end-1)]...) - end - Expr(:block, [decs..., eqs...]...) -end - # fromexpr """ diff --git a/test/syntax/GATContexts.jl b/test/syntax/GATContexts.jl index 8d56c778..cb0e4bb0 100644 --- a/test/syntax/GATContexts.jl +++ b/test/syntax/GATContexts.jl @@ -58,4 +58,6 @@ a = ident(Z; name=:a) (r⋅r⋅r⋅r) == e() end +@test sprint(show, D₄)[1] == 'G' # etc. + end # module diff --git a/test/syntax/GATs.jl b/test/syntax/GATs.jl index 47e993bb..48e64663 100644 --- a/test/syntax/GATs.jl +++ b/test/syntax/GATs.jl @@ -52,6 +52,7 @@ O, H, i, cmp = idents(thcat; name=[:Ob, :Hom, :id, :compose]) ObT = fromexpr(thcat, :Ob, AlgType) ObS = AlgSort(ObT) +@test toexpr(GATContext(thcat), ObS) == :Ob # Extend seg with a context of (A: Ob) sortscope = TypeScope(:A => ObT) @@ -63,6 +64,11 @@ ATerm = AlgTerm(A) c = GATContext(thcat, sortscope) HomT = fromexpr(c, :(Hom(A, A)), AlgType) + +AA = :(A == A) +eqA = fromexpr(c, AA, AlgType) +@test toexpr(c, eqA) == AA + HomS = AlgSort(HomT) @test rename(gettag(sortscope), Dict(:A=>:Z), HomT) isa AlgType diff --git a/test/syntax/TheoryInterface.jl b/test/syntax/TheoryInterface.jl index f5eae269..d41f2f34 100644 --- a/test/syntax/TheoryInterface.jl +++ b/test/syntax/TheoryInterface.jl @@ -37,6 +37,11 @@ using .ThLawlessCategory Hom(dom::Ob, codom::Ob) :: TYPE end +@test_throws Exception @eval @theory ThMonoid <: ThSemiGroup begin + e() :: default + e ⋅ x == x ⊣ [x] +end + @test_throws Exception @eval @theory ThBadAliases <: ThCategory begin @op 1 + 1 end