view julia/parallel/ParallelProcessing/src/metaparser.jl @ 36:03d20a4dd8f2

json: metaparser
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 24 Mar 2023 21:30:03 +0100
parents
children 37a528750178
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]

    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 = $(method)(jp)
                matched = true
            end
        end for (name, method) in methods
    ]

    quote
        $strct

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

            $(fieldvars...)

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

                matched = false
                $(field_dispatch...)

                if !matched
                    take_val!(jp)
                end

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

            if isnothing(level) || isnothing(kind)
                error("Elements missing from object: $level, $kind")
            end

            expect!(jp, '}') || error("unclosed Details object")

            Details(level, kind)
        end
    end
end

macro json_parseable(strct)
    json_parseable(strct)
end

function take_struct_!(::Type{Details}, jp::JP)::Union{Nothing,Details}
    expect!(jp, '{') || return nothing

    level::Union{Nothing,String} = nothing
    kind::Union{Nothing,Int} = nothing
    while true
        key = take_str!(jp)
        if isnothing(key)
            break
        end

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

        # Custom code!
        if key == "level"
            level = take_str!(jp)
        elseif key == "kind"
            kind = take_num!(jp)
        else
            # Ignore unknown keys
            take_val!(jp)
        end

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

    if isnothing(level) || isnothing(kind)
        error("Elements missing from object: $level, $kind")
    end

    expect!(jp, '}') || error("unclosed Details object")

    Details(level, kind)
end