Expronicon.jl
Expronicon.jl copied to clipboard
Generate default keyword arguments in codegen
fixes #17
thanks for the PR! actually I just realize we cannot change this convention for functions, this is because the JLKwField
and JLField
are used specifically for the structs instead of functions at the moment, e.g as a result of this convention the struct codegen will treat them as "field"s when they appear in a struct body.
https://github.com/Roger-luo/Expronicon.jl/blob/master/src/codegen.jl#L392
the function keyword should be specified as Expr(:kw, a, b)
, I'm not sure if we should have a more unified type for the syntax name = <value>
, any idea?
I'm not sure, maybe have another arg you can pass in to denote the context is a function or sturct?
I added from_function
kwarg for JLKwField codegen. It looks like it works for my use case. I guess another way could be to add new structs, like JLFnArg and JLFnKwarg which are like the Field versions but codegen slightly differently?
Actually I'd like to learn your use case first, what's your use case of using JLKwField?
Part of the reason why I didn't use a type for function kwargs was because it's Expr is quite simple (unlike struct field that can have line number and docstring)
I'm using it to generate GraphQL types/functions from a schema - https://github.com/domluna/GraphQLGen.jl.
I'm rewriting some portions to use Expronicon instead of pure exprs since it's easier to manage things that way, and to parse things for testing. https://github.com/domluna/GraphQLGen.jl/blob/docstrings/src/jltype.jl#L234-L249
The kwarg generation is used to create functions
julia> str = """
schema {
query: Query
}
type Query {
"this returns some books"
books(ids: [ID!]!): Book!
authorBooks(authorName: String!, genre: Genre, limit: Float = 10): [Book!]
}
"""
"schema {\n query: Query\n}\n\ntype Query {\n \"this returns some books\"\n books(ids: [ID!]!): Book!\n authorBooks(authorName: String!, genre: Genre, limit: Float = 10): [Book!]\n}\n"
julia> types, functions = GraphQLGen.tojl(GraphQLGen.parse(str));
┌ Info:
│ args =
│ 1-element Vector{Expronicon.JLKwField}:
│ ids::Vector{String}
│ kwargs = Expronicon.JLKwField[]
│ funcsigs =
│ 1-element Vector{NamedTuple{(:name, :typ, :default), Tuple{String, String, String}}}:
│ (name = "ids", typ = "[ID!]!", default = "")
│ jlf =
│ function books(ids::Vector{String})
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│ q = s -> *("query Books(ids: [ID!]!) {
│ books(ids: $ids) {
│ ", s, "}
│ }") #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:215 =#
│ query = q(f.query)
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:216 =#
│ variables = Dict("ids" => ids)
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
│ filter!(v -> !isnothing(v[2]) #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#, variables)
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:219 =#
│ return :($(Expr(:parameters, :query, :variables)))
│ end
└ map((kw->begin
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:223 =#
kw.default
end), kwargs) = Any[]
┌ Info:
│ args =
│ 1-element Vector{Expronicon.JLKwField}:
│ authorName::String
│ kwargs =
│ 2-element Vector{Expronicon.JLKwField}:
│ genre::Union{Genre, Missing, Nothing}
│ limit::Union{Float64, Missing, Nothing}
│ funcsigs =
│ 3-element Vector{NamedTuple{(:name, :typ, :default), Tuple{String, String, String}}}:
│ (name = "authorName", typ = "String!", default = "")
│ (name = "genre", typ = "Genre", default = "")
│ (name = "limit", typ = "Float", default = "10")
│ jlf =
│ function authorBooks(authorName::String; genre::Union{Genre, Missing, Nothing}, limit::Union{Float64, Missing, Nothing})
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│ q = s -> *("query AuthorBooks(authorName: String!, genre: Genre, limit: Float = 10) {
│ authorBooks(authorName: $authorName, genre: $genre, limit: $limit) {
│ ", s, "}
│ }") #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:215 =#
│ query = q(f.query)
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:216 =#
│ variables = Dict("authorName" => authorName, "genre" => genre, "limit" => limit)
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
│ filter!(v -> !isnothing(v[2]) #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#, variables)
│ #= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:219 =#
│ return :($(Expr(:parameters, :query, :variables)))
│ end
│ map((kw->begin
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:223 =#
kw.default
end), kwargs) =
│ 2-element Vector{Any}:
│ :nothing
└ 10
julia> functions[end]
quote
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:226 =#
struct authorBooks
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:227 =#
query::String
end
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:230 =#
function authorBooks(authorName::String; genre::Union{Genre, Missing, Nothing}, limit::Union{Float64, Missing, Nothing})
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
q = (s->begin
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:208 =#
"query AuthorBooks(authorName: String!, genre: Genre, limit: Float = 10) {\nauthorBooks(authorName: \$authorName, genre: \$genre, limit: \$limit) {\n" * s * "}\n}"
end)
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:215 =#
query = q(f.query)
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:216 =#
variables = Dict("authorName" => authorName, "genre" => genre, "limit" => limit)
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
filter!((v->begin
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:217 =#
!(isnothing(v[2]))
end), variables)
#= /home/domluna/.julia/dev/GraphQL/src/jltype.jl:219 =#
return (; query, variables)
end
end
This part here
function authorBooks(authorName::String; genre::Union{Genre, Missing, Nothing}, limit::Union{Float64, Missing, Nothing})
should generate the default values and with patch it does.
Ah I see why now, function kwargs could have type annotations, this would be a case when a struct for it comes in convenience, what about implementing a JLKwarg
then?
Would we need JLArg too then? or would be reuse JLField for that?
Umm now after more thinking on this I believe the right way is to actually have types that represent certain syntax such as arg-like objects that can be used as fields or function arguments and kwarg like objects that can be used as kw fields or kwargs.
I also have something similar in Comonicon. I think I should have a closer look on an implementation but I got quite occupied recently (so sorry for this late reply) I think I'd like to work on this early next month after a paper I'm working on is out. Meanwhile would it be ok for now to use the Expr(:kw, a, b) as a workaround for now?
Meanwhile would it be ok for now to use the Expr(:kw, a, b) as a workaround for now?
what do you mean?
I mean for now you can use
julia> fn = JLFunction(;name=:foo, kwargs=[Expr(:kw, :(name::Int), 1)])
function foo(; name::Int = 1)
end
julia> codegen_ast(fn)
:(function foo(; name::Int = 1)
end)
julia> codegen_ast(fn)|>eval
foo (generic function with 1 method)
for now, this interface should always work.
also note, the function name should be a Symbol
instead of String
, I should have error on this
We just need to canonicalize the internal representation on these syntax types.
oh ic, yeah that should be a sufficient workaround