Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docstrings for InterTypes #100

Merged
merged 5 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion src/intertypes/InterTypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,28 @@ using OrderedCollections
using ..Schemas
import ..Schemas: toexpr

# InterType Definitions
#######################

"""
A field of a struct. Used in [`Variant`](@ref) and [`Record`](@ref).

The `T` parameter will always be [`InterType`](@ref), but this is mutually-recursive
with `InterType` so we have to be generic here.
"""
struct Field{T}
name::Symbol
type::T
end

Base.nameof(field::Field) = field.name

"""
One of the summands of a sum type.

Like [`Field`](@ref), the `T` parameter will always be [`InterType`](@ref), but
this is mutually-recursive with `InterType` so we have to be generic here.
"""
struct Variant{T}
tag::Symbol
fields::Vector{Field{T}}
Expand All @@ -26,6 +41,13 @@ Base.nameof(variant::Variant) = variant.tag
RefThere(mod::RefPath, name::Symbol)
end

@doc """
A non-empty linked list of symbols representing something like `foo.bar.baz`.
""" RefPath

@doc """E.g. mod.name""" RefThere


function RefPath(s::Symbol)
RefHere(s)
end
Expand Down Expand Up @@ -61,11 +83,43 @@ end
OptionalType(elemtype::InterType)
Record(fields::Vector{Field{InterType}})
Sum(variants::Vector{Variant{InterType}})
ACSetInterType(schema::TypedSchema{InterType})
ACSetInterType(schema::TypedSchema{Symbol, InterType})
Annot(desc::String, type::InterType)
TypeRef(to::RefPath)
end

@doc """
An intertype expression representing a type.

TODO: Generic types
TODO: Remove anonymous sums, anonymous products
TODO: Separate out primitives, so that this is something like

```julia
@data InterType begin
PrimType(prim::InterTypePrimitive)
TypeRef(path::RefPath)
TypeApp(type::InterType, args::Vector{InterType})
end
```
""" InterType

"""
A specification for the type of an acset.

## Fields
- `genericname::Union{Symbol, Nothing}`: The name for the generic version of the acset, with type parameters.

Note that the name assigned to this in the declaration is the name *with*
type parameters pre-specified.

If there are no attribute types, then this is nothing.
- `abstract_type::Union{Symbol, Nothing}`: The parent abstract type for the acset.
- `schemaname::Symbol`
- `schema::TypedSchema{Symbol, InterType}`
- `index::Vector{Symbol}`
- `unique_index::Vector{Symbol}`
"""
struct ACSetTypeSpec
genericname::Union{Symbol, Nothing}
abstract_type::Union{Symbol, Nothing}
Expand All @@ -85,6 +139,38 @@ end
NamedACSetType(typespec::ACSetTypeSpec)
end

@doc """
An intertype declaration.

Does not include the name of the declaration.
""" InterTypeDecl

@doc """An alias for an existing type""" Alias

@doc """A sum type, also known as a tagged union.""" SumType

@doc """
A variant of a sum type, i.e. one of the summands. These are implicitly
produced when declaring a sum type, and the data of the variant (i.e. the
fields) are in the parent sum type.
""" VariantOf

@doc """A struct type, also known as a product type or record type.""" Struct

@doc """
A schema for acsets. Does not declare the acset type yet, however, that is
done by [`NamedACSetType`](@ref).
""" SchemaDecl

@doc """
An abstract acset type which ACSets can subtype. Mostly used for backwards
compatibility with AlgebraicJulia code.
""" AbstractACSetType

@doc """
An acset type, with customizations like choice of indices, etc.
""" NamedACSetType

exports(::InterTypeDecl) = Symbol[]

function exports(acset_type::NamedACSetType)
Expand All @@ -97,6 +183,10 @@ end

function hashdecls end

"""
A collection of intertype declarations packaged together. May refer to other
InterTypeModules via the `imports` field.
"""
struct InterTypeModule
name::Symbol
imports::OrderedDict{Symbol, InterTypeModule}
Expand Down Expand Up @@ -126,6 +216,8 @@ abstract type ExportTarget end
abstract type LanguageTarget <: ExportTarget end
abstract type SerializationTarget <: ExportTarget end

abstract type SerializationFormat end

"""
generate_module(mod::Module, target::Type{<:ExportTarget}, path="."; target_specific_args...)

Expand Down
71 changes: 68 additions & 3 deletions src/intertypes/json.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,44 @@
using ..ACSetInterface
using ..DenseACSets

struct JSONFormat
"""
A signifier for serialization to and from JSON.
"""
struct JSONFormat <: SerializationFormat
end

struct InterTypeConversionError <: Exception
expected::InterType
got::Any
end

function read(format, type::Type, x)
"""
read(format::SerializationFormat, type::Type, x)

A generic read method dispatch-controlled by format of the input and expected
type.

TODO: Currently this *shadows* Base.read, instead of overloading it. Would it
be type piracy to overload it instead?
"""
function read(format::SerializationFormat, type::Type, x)

Check warning on line 32 in src/intertypes/json.jl

View check run for this annotation

Codecov / codecov/patch

src/intertypes/json.jl#L32

Added line #L32 was not covered by tests
throw(InterTypeConversionError(intertype(type), x))
end

"""
write(io::IO, format::SerializationFormat, x)

Writes `x` the the provided IO stream, using serialization format `format`.

See docstring for [`read`](@ref) about shadowing vs. overloading Base.write.
"""
function write end

"""
joinwith(io::IO, f, xs, separator)

Prints the elements in `xs` using `x -> f(io, x)`, separated by `separator`.
"""
function joinwith(io::IO, f, xs, separator)
for x in xs[1:end-1]
f(io, x)
Expand All @@ -29,13 +55,38 @@
f(io, xs[end])
end

"""
jsonwrite(x)
jsonwrite(io::IO, x)

A wrapper around [`write`](@ref) which calls it with [`JSONFormat`](@ref).

TODO: Should we just overload JSON3.write instead? See [1]

[1]: https://github.com/AlgebraicJulia/ACSets.jl/issues/83
"""
jsonwrite(x) = sprint(jsonwrite, x)
jsonwrite(io::IO, x) = write(io, JSONFormat(), x)

"""
jsonread(s::String, T::Type)

A wrapper around [`read`](@ref) which calls it with [`JSONFormat`](@ref)

TODO: See comment for [`jsonwrite`](@ref)
"""
function jsonread(s::String, ::Type{T}) where {T}
json = JSON3.read(s)
read(JSONFormat(), T, json)
end

# JSON Serialization for basic types
####################################

# We write our own serialization methods instead of using JSON3.write directly
# because we want to serialize 64 bit integers as strings rather than integers
# which get lossily converted to floats.

intertype(::Type{Nothing}) = Unit
read(::JSONFormat, ::Type{Nothing}, ::Nothing) = nothing
write(io::IO, ::JSONFormat, ::Nothing) = print(io, "null")
Expand Down Expand Up @@ -115,7 +166,6 @@
read(::JSONFormat, ::Type{Optional{T}}, ::Nothing) where {T} = nothing
read(format::JSONFormat, ::Type{Optional{T}}, s) where {T} =
read(format, T, s)
write(io::IO, format::JSONFormat, d::Nothing) = print(io, "null")

intertype(::Type{OrderedDict{K,V}}) where {K,V} = Map(intertype(K), intertype(V))
function read(format::JSONFormat, ::Type{OrderedDict{K, V}}, s::JSON3.Array) where {K, V}
Expand Down Expand Up @@ -263,12 +313,23 @@
end
end

# JSONSchema Export
###################

function fieldproperties(fields::Vector{Field{InterType}})
map(fields) do field
field.name => tojsonschema(field.type)
end
end

"""
tojsonschema(type::InterType)

Convert an InterType to a JSONSchema representation.

TODO: We could use multiple dispatch instead of the `@match` here, which might
be cleaner
"""
function tojsonschema(type::InterType)
@match type begin
I32 => Object(
Expand Down Expand Up @@ -391,9 +452,13 @@

Specifies a serialization target of JSON Schema when
generating a module.

TODO: This should really be called something like JSONSchemaTarget.
"""
struct JSONTarget <: SerializationTarget end

# TODO: Should this be ::JSONTarget instead of ::Type{JSONTarget} so
# that we pass in `JSONTarget()` instead of `JSONTarget`?
function generate_module(
mod::InterTypeModule, ::Type{JSONTarget}, path
;ac=JSON3.AlignmentContext(indent=2)
Expand Down
25 changes: 24 additions & 1 deletion src/intertypes/julia.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using ...ACSets


function parse_fields(fieldexprs; mod)
map(enumerate(fieldexprs)) do (i, fieldexpr)
@match fieldexpr begin
Expand Down Expand Up @@ -165,7 +164,13 @@ function parse_intertype_decl(e; mod::InterTypeModule)
end
end

"""
We use these macros so that we don't have to, for instance, pattern match on
`Expr(:macrocall, var"@sum", _, args...)`, and can instead pattern match on
`Expr(:sum, args...)`.
"""
module InterTypeDeclImplPrivate

macro sum(head, body)
esc(Expr(:sum, head, body))
end
Expand All @@ -183,6 +188,17 @@ macro abstract_acset_type(head)
end
end

"""
function toexpr(x) end

Used to convert intertype data types to `Expr`.

TODO: Should we unify [`tojsonschema`](@ref), [`toexpr`](@ref), and
[`topy`](@ref) into a single function with an extra argument to control
dispatch?
"""
function toexpr end

function toexpr(field::Field)
Expr(:(::), field.name, toexpr(field.type))
end
Expand Down Expand Up @@ -324,6 +340,13 @@ function include_intertypes(into::Module, file::String, imports::AbstractVector)
mod
end

"""
@intertypes "file.it" module modulename end

Used to import an intertypes file into Julia.

TODO: maybe we should just build a .jl file from an .it file directly.
"""
macro intertypes(file, modexpr)
name, imports = @match modexpr begin
Expr(:module, _, name, body) => begin
Expand Down
11 changes: 11 additions & 0 deletions src/intertypes/python.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
export PydanticTarget

"""
topy(intertype::InterType; forward_ref=true)

Converts an intertype into a python code string.

TODO: See comment for [`toexpr`](@ref)

TODO: Should we use something like a stringbuilder instead of manually
concatenating strings? I.e., a tree of strings with O(1) append/splice,
that we write out to a single string at the end?
"""
function topy(intertype::InterType; forward_ref=true)
@match intertype begin
I32 => "int"
Expand Down
6 changes: 6 additions & 0 deletions src/intertypes/sexp.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
using SHA
using Random

"""
A very simple s-expression data structure.

We use this to write the intertype schema to a string and then hash it to get a
version identifier.
"""
struct SExp
args::Vector{Union{Symbol, SExp}}
end
Expand Down