view julia/parallel/ParallelProcessing/src/metaparser.jl @ 37:37a528750178

Make JSON struct parser generator work
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 24 Mar 2023 22:07:52 +0100
parents 03d20a4dd8f2
children bae384998d85
line wrap: on
line source


function parse_struct(s::Expr)
    s.head == :struct || error("Expr must be struct but is $s")
    args = s.args[2:end]
    typedef = args[1]

    fields = args[2]
    fields.head == :block || error("Expr must contain a block but has $fields")
    fields_exprs = Pair[]
    for f in fields.args
        if typeof(f) == LineNumberNode
            continue
        end
        typeof(f) == Expr || error("Field $f in $typedef must have type!")
        f.head == :(::) || error(
            "Field $f in $typedef must be type! (we don't support constructors etc. - struct must be plain)",
        )
        name, typ = f.args
        typeof(typ) == Symbol ||
            error("Type of $f should be symbol (simple type) but is $(typeof(typ)): $typ")
        typ = eval(typ)
        isconcretetype(typ) ||
            error("Type of field $name must be concrete, but is not (is $typ)!")
        push!(fields_exprs, name => eval(typ))
    end

    fields_exprs
end

function get_type_of_struct(s::Expr)
    s.head == :struct || error("Expr must be struct, is $s")
    typedef = s.args[2]

    if typeof(typedef) == Symbol
        typedef
    elseif typeof(typedef) == Expr
        error("We don't support generic structs yet :(")
    end
end

function json_parseable(strct)
    typs::Vector{Pair{Symbol,Type}} = parse_struct(strct)
    typ = get_type_of_struct(strct)

    fieldvars = [:($(name)::Union{$typ,Nothing} = nothing) for (name, typ) in typs]
    fieldnames = [name for (name, _) in typs]
    Mod = :ParallelProcessing

    method_map = Dict(
        Int64 => :take_num!,
        Float64 => :take_num!,
        String => :take_str!,
        Dict => :take_object!,
        Bool => :take_bool!,
    )
    methods = [(name, method_map[typ]) for (name, typ) in typs]
    field_dispatch = [
        quote
            if !matched && key == $(string(name))
                $name = $Mod.$(method)(jp)
                matched = true
            end
        end for (name, method) in methods
    ]

    quote
        $strct

        function ParallelProcessing.take_struct!(
            ::Type{$typ},
            jp::$(Mod).JP,
        )::Union{Nothing,$typ}
            $Mod.expect!(jp, '{') || return nothing

            $(fieldvars...)

            while true
                key = $Mod.take_str!(jp)
                if isnothing(key)
                    break
                end

                $Mod.expect!(jp, ':') || error("malformed object - expected ':'")

                matched = false
                $(field_dispatch...)

                if !matched
                    $Mod.take_val!(jp)
                end

                if $Mod.expect!(jp, ',')
                    continue
                else
                    break
                end
            end

            #if $(fields_filled_cond...)
            #    error("Elements missing from object")
            #end

            $Mod.expect!(jp, '}') || error("unclosed object")

            $(typ)($(fieldnames...))
        end
    end |> esc
end

macro json_parseable(strct)
    json_parseable(strct)
end