Skip to content

Commit

Permalink
Merge pull request #100 from AlgebraicJulia/intertype-docstrings
Browse files Browse the repository at this point in the history
Docstrings for InterTypes
  • Loading branch information
olynch authored Jan 24, 2024
2 parents 3e93d2e + 01ff30d commit 3c958fc
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 5 deletions.
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 CompTime
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)
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 @@ function joinwith(io::IO, f, xs, separator)
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 @@ intertype(::Type{Optional{T}}) where {T} = Optional{intertype(T)}
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 @@ function write(io::IO, format::JSONFormat, acs::ACSet)
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 @@ end
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

0 comments on commit 3c958fc

Please sign in to comment.