Skip to content

Commit

Permalink
Merge pull request #96 from AlgebraicJulia/objects-optionals
Browse files Browse the repository at this point in the history
Optional and Object
  • Loading branch information
olynch committed Jan 23, 2024
2 parents 7e3e833 + 261663d commit 3e93d2e
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 58 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StructEquality = "6ec83bb0-ed9f-11e9-3b4c-2b04cb4e219c"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[weakdeps]
Expand Down
35 changes: 34 additions & 1 deletion src/intertypes/InterTypes.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module InterTypes
export InterType, InterTypeDecl, Binary, LanguageTarget, SerializationTarget, generate_module, intertype, @intertypes
export InterType, InterTypeDecl, Object, Optional,
LanguageTarget, SerializationTarget, generate_module, intertype, @intertypes

using MLStyle
using OrderedCollections
Expand Down Expand Up @@ -56,6 +57,8 @@ end
Binary
List(elemtype::InterType)
Map(keytype::InterType, valuetype::InterType)
ObjectType(elemtype::InterType)
OptionalType(elemtype::InterType)
Record(fields::Vector{Field{InterType}})
Sum(variants::Vector{Variant{InterType}})
ACSetInterType(schema::TypedSchema{InterType})
Expand Down Expand Up @@ -132,6 +135,36 @@ function generate_module(mod::Module, target::Type{<:ExportTarget}, path="."; ta
generate_module(mod.Meta, target, path; target_specific_args...)
end

module InterTypeSupport
using OrderedCollections
using StructEquality
import StructTypes
export Object, Optional

@struct_hash_equal struct Object{T}
fields::OrderedDict{Symbol, T}
end

function Object{T}(pairs::(Pair{Symbol, S} where {S<:T})...) where {T}
Object{T}(OrderedDict{Symbol, T}(pairs...))
end

function Object(pairs::(Pair{Symbol, T} where {T})...)
Object{Any}(pairs...)
end

Base.getindex(obj::Object, key::Symbol) = obj.fields[key]
Base.setindex!(obj::Object, x, key::Symbol) = (obj.fields[key] = x)

Base.pairs(obj::Object) = pairs(obj.fields)

StructTypes.StructType(::Type{<:Object}) = StructTypes.DictType()

const Optional{T} = Union{T, Nothing}
end

using .InterTypeSupport

include("json.jl")
include("sexp.jl")
include("julia.jl")
Expand Down
135 changes: 81 additions & 54 deletions src/intertypes/json.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ function write(io::IO, format::JSONFormat, d::Vector{T}) where {T}
print(io, "]")
end

intertype(::Type{Object{T}}) where {T} = ObjectType{intertype(T)}
function read(format::JSONFormat, ::Type{Object{T}}, s::JSON3.Object) where {T}
Object{T}(
[k => read(format, T, v) for (k, v) in pairs(s)]...
)
end
function write(io::IO, format::JSONFormat, d::Object)
writeobject(io) do next
for p in pairs(d)
next()
writekv(io, p)
end
end
end

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}
res = OrderedDict{K, V}()
Expand Down Expand Up @@ -242,68 +263,75 @@ function write(io::IO, format::JSONFormat, acs::ACSet)
end
end

const Object = OrderedDict{String, Any}

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

function tojsonschema(type::InterType)
@match type begin
I32 => Object(
"type" => "integer",
"\$comment" => "I32",
"minimum" => typemin(Int32),
"maximum" => typemax(Int32)
:type => "integer",
Symbol("\$comment") => "I32",
:minimum => typemin(Int32),
:maximum => typemax(Int32)
)
U32 => Object(
"type" => "integer",
"\$comment" => "U32",
"minimum" => typemin(UInt32),
"maximum" => typemax(UInt32)
:type => "integer",
Symbol("\$comment") => "U32",
:minimum => typemin(UInt32),
:maximum => typemax(UInt32)
)
I64 => Object(
"type" => "string",
"\$comment" => "I64"
:type => "string",
Symbol("\$comment") => "I64"
)
U64 => Object(
"type" => "string",
"\$comment" => "U64"
:type => "string",
Symbol("\$comment") => "U64"
)
F64 => Object(
"type" => "number",
"\$comment" => "F64"
:type => "number",
Symbol("\$comment") => "F64"
)
Boolean => Object(
"type" => "boolean",
"\$comment" => "Boolean"
:type => "boolean",
Symbol("\$comment") => "Boolean"
)
Str => Object(
"type" => "string",
"\$comment" => "Str"
:type => "string",
Symbol("\$comment") => "Str"
)
Sym => Object(
"type" => "string",
"\$comment" => "Sym"
:type => "string",
Symbol("\$comment") => "Sym"
)
Binary => Object(
"type" => "string",
"contentEncoding" => "base64",
"\$comment" => "Binary"
:type => "string",
:contentEncoding => "base64",
Symbol("\$comment") => "Binary"
)
OptionalType(elemtype) => begin
schema = tojsonschema(elemtype)
schema[:type] = [schema[:type], "null"]
schema
end
ObjectType(elemtype) => Object(
:type => "object",
:additionalProperties => tojsonschema(elemtype)
)
List(elemtype) => Object(
"type" => "array",
"items" => tojsonschema(elemtype)
:type => "array",
:items => tojsonschema(elemtype)
)
Map(keytype, valuetype) => Object(
"type" => "array",
"items" => Object(
"type" => "object",
"properties" => Object(
"key" => tojsonschema(keytype),
"value" => tojsonschema(valuetype)
:type => "array",
:items => Object(
:type => "object",
:properties => Object(
:key => tojsonschema(keytype),
:value => tojsonschema(valuetype)
)
)
)
Expand All @@ -321,24 +349,24 @@ function tojsonschema(type::InterType)
end

reftype(name) = Object(
"\$ref" => "#/\$defs/$(name)"
Symbol("\$ref") => "#/\$defs/$(name)"
)

recordtype(fields) = Object(
"type" => "object",
"properties" => Object(fieldproperties(fields)),
"required" => string.(nameof.(fields))
:type => "object",
:properties => Object(fieldproperties(fields)...),
:required => string.(nameof.(fields))
)

varianttype(variant) = Object(
"type" => "object",
"properties" => Object(
"tag" => Object(
"const" => string(variant.tag)
:type => "object",
:properties => Object(
:tag => Object(
:const => string(variant.tag)
),
fieldproperties(variant.fields)...
),
"required" => string.(nameof.(variant.fields))
:required => string.(nameof.(variant.fields))
)

function acsettype(spec)
Expand All @@ -353,8 +381,8 @@ function acsettype(spec)
Field{InterType}(ob, List(Record([idfield; homfields; attrfields])))
end
Object(
"type" => "object",
"properties" => recordtype(tablespecs)
:type => "object",
:properties => recordtype(tablespecs)
)
end

Expand All @@ -370,27 +398,26 @@ function generate_module(
mod::InterTypeModule, ::Type{JSONTarget}, path
;ac=JSON3.AlignmentContext(indent=2)
)
defs = Pair{String, Object}[]
defs = Pair{Symbol, Object}[]
for (name, decl) in mod.declarations
sname = string(name)
@match decl begin
Alias(type) => push!(defs, sname => tojsonschema(type))
Struct(fields) => push!(defs, sname => recordtype(fields))
Alias(type) => push!(defs, name => tojsonschema(type))
Struct(fields) => push!(defs, name => recordtype(fields))
VariantOf(parent) => begin
sum = mod.declarations[parent]
variant = only(filter(v -> v.tag == name, sum.variants))
push!(defs, sname => varianttype(variant))
push!(defs, name => varianttype(variant))
end
SumType(variants) =>
push!(defs, sname => Object("oneOf" => reftype.([v.tag for v in variants])))
push!(defs, name => Object(:oneOf => reftype.([v.tag for v in variants])))
NamedACSetType(spec) =>
push!(defs, sname => acsettype(spec))
push!(defs, name => acsettype(spec))
_ => nothing
end
end
schema = Object(
"\$schema" => "http://json-schema.org/draft-07/schema#",
"\$defs" => Object(defs)
Symbol("\$schema") => "http://json-schema.org/draft-07/schema#",
Symbol("\$defs") => Object(defs...)
)
schema_filepath = joinpath(path, string(mod.name)*"_schema.json")
open(schema_filepath, "w") do io
Expand Down
7 changes: 7 additions & 0 deletions src/intertypes/julia.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ...ACSets


function parse_fields(fieldexprs; mod)
map(enumerate(fieldexprs)) do (i, fieldexpr)
@match fieldexpr begin
Expand Down Expand Up @@ -111,6 +112,10 @@ function parse_intertype(e; mod::InterTypeModule)
InterTypes.List(parse_intertype(elemtype; mod))
Expr(:curly, :OrderedDict, keytype, valuetype) =>
InterTypes.Map(parse_intertype(keytype; mod), parse_intertype(valuetype; mod))
Expr(:curly, :Object, elemtype) =>
InterTypes.ObjectType(parse_intertype(elemtype; mod))
Expr(:curly, :Optional, elemtype) =>
InterTypes.OptionalType(parse_intertype(elemtype; mod))
Expr(:curly, :Record, fieldexprs...) => begin
InterTypes.Record(parse_fields(fieldexprs; mod))
end
Expand Down Expand Up @@ -200,6 +205,8 @@ function toexpr(intertype::InterType)
Binary => :(Vector{UInt8})
List(elemtype) => Expr(:curly, :Vector, toexpr(elemtype))
Map(keytype, valuetype) => Expr(:curly, :OrderedDict, toexpr(keytype), toexpr(valuetype))
ObjectType(elemtype) => Expr(:curly, InterTypeSupport.Object, toexpr(elemtype))
OptionalType(elemtype) => Expr(:curly, InterTypeSupport.Optional, toexpr(elemtype))
Record(fields) =>
Expr(:curly, :Record, toexpr.(fields)...)
Sum(variants) =>
Expand Down
2 changes: 2 additions & 0 deletions src/intertypes/python.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ function topy(intertype::InterType; forward_ref=true)
Str => "str"
Sym => "str"
Binary => "str"
OptionalType(elemtype) => "$(topy(elemtype)) | None"
ObjectType(elemtype) => "dict[str, $(topy(elemtype))]"
List(elemtype) => "list[$(topy(elemtype))]"
Map(keytype, valuetype) => "OrderedDict[$(topy(keytype)), $(topy(valuetype))]"
Record(_) => error("no native record type for python")
Expand Down
4 changes: 4 additions & 0 deletions src/intertypes/sexp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ function tosexp(t::InterType)
SExp(:List, tosexp(elemtype))
Map(keytype, valuetype) =>
SExp(:Map, tosexp(keytype), tosexp(valuetype))
ObjectType(elemtype) =>
SExp(:Object, tosexp(elemtype))
OptionalType(elemtype) =>
SExp(:Optional, tosexp(elemtype))
Record(fields) => SExp(:Record, fieldsexps(fields)...)
Sum(variants) => SExp(:Sum, variantsexps(variants)...)
ACSetInterType(schema) => :ACset
Expand Down
Loading

0 comments on commit 3e93d2e

Please sign in to comment.