changeset 36:03d20a4dd8f2

json: metaparser
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 24 Mar 2023 21:30:03 +0100
parents 8208faa6c42f
children 37a528750178
files julia/parallel/ParallelProcessing/src/ParallelProcessing.jl julia/parallel/ParallelProcessing/src/json.jl julia/parallel/ParallelProcessing/src/jsonparser.jl julia/parallel/ParallelProcessing/src/metaparser.jl
diffstat 4 files changed, 193 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/julia/parallel/ParallelProcessing/src/ParallelProcessing.jl	Fri Mar 24 20:04:38 2023 +0100
+++ b/julia/parallel/ParallelProcessing/src/ParallelProcessing.jl	Fri Mar 24 21:30:03 2023 +0100
@@ -2,4 +2,5 @@
 
 include("jsonparser.jl")
 include("json.jl")
+include("metaparser.jl")
 end # module ParallelProcessing
--- a/julia/parallel/ParallelProcessing/src/json.jl	Fri Mar 24 20:04:38 2023 +0100
+++ b/julia/parallel/ParallelProcessing/src/json.jl	Fri Mar 24 21:30:03 2023 +0100
@@ -13,7 +13,7 @@
     expect!(jp, '{') || return nothing
 
     level::Union{Nothing,String} = nothing
-    kind::Union{Nothing, Int} = nothing
+    kind::Union{Nothing,Int} = nothing
     while true
         key = take_str!(jp)
         if isnothing(key)
@@ -44,7 +44,7 @@
     end
 
     expect!(jp, '}') || error("unclosed Details object")
-    
+
     Details(level, kind)
 end
 
@@ -62,33 +62,38 @@
     SimpleEntry(s, a, f, Details(level, kind))
 end
 
-function generate_json(file, n=1000)
-    open(file; write=true) do fh
+function generate_json(file, n = 1000)
+    open(file; write = true) do fh
         for i = 1:n
             println(fh, json(generate_entry()))
         end
     end
 end
 
-fib(n) = if n <= 2 1 else fib(n-1) + fib(n-2) end
+fib(n) =
+    if n <= 2
+        1
+    else
+        fib(n - 1) + fib(n - 2)
+    end
 
 function expensive_mapper(m::Dict)::Int
     i = abs(m["a"]) % 35
     fib(i)
 end
 
-function process_json(file, mapper=x -> ())::Vector
-    open(file; read=true) do fh
-        [mapper(JSON.parse(line)) for line = eachline(fh)]
+function process_json(file, mapper = x -> ())::Vector
+    open(file; read = true) do fh
+        [mapper(JSON.parse(line)) for line in eachline(fh)]
     end
 end
 
 
-function process_json_parallel(file, mapper=x -> ())::Vector
-    open(file; read=true) do fh
+function process_json_parallel(file, mapper = x -> ())::Vector
+    open(file; read = true) do fh
         ch = Channel(100)
         count = 0
-        for line = eachline(fh)
+        for line in eachline(fh)
             Threads.@spawn put!(ch, mapper(JSON.parse(line)))
             count += 1
         end
--- a/julia/parallel/ParallelProcessing/src/jsonparser.jl	Fri Mar 24 20:04:38 2023 +0100
+++ b/julia/parallel/ParallelProcessing/src/jsonparser.jl	Fri Mar 24 21:30:03 2023 +0100
@@ -1,11 +1,13 @@
 
+# JSON parser struct.
 mutable struct JP
     s::String
     pos::Int
+    length::Int
 end
 
 @inline function JP(s::AbstractString)::JP
-    JP(string(s), 1)
+    JP(string(s), 1, length(s))
 end
 
 @inline function current(jp::JP)::Char
@@ -17,33 +19,25 @@
 end
 
 @inline function isend(jp::JP)
-    jp.pos > length(jp.s)
+    jp.pos > jp.length
 end
 
-function take_num!(jp::JP)::Union{Nothing,Float64}
-    pred(c) = isdigit(c) || (c == '.') # todo: exponential numbers
-    span = takewhile!(jp, pred)
-    if isnothing(span)
-        nothing
-    else
-        a, b = span
-        parse(Float64, (@view jp.s[a:b]))
-    end
+@inline function reset!(jp::JP)
+    jp.pos = 1
 end
 
+const FLOAT_CHARS = ['e', '.', '-']
+
 function take_num!(jp::JP)::Union{Nothing,Float64,Int}
-    pred(c) = isdigit(c) || (c == '-') || (c == '.') # todo: exponential numbers
+    isfloatstr(c) = c in FLOAT_CHARS
+    pred(c) = isdigit(c) || isfloatstr(c)
     span = takewhile!(jp, pred)
     if isnothing(span)
         nothing
     else
         a, b = span
         s = (@view jp.s[a:b])
-        if contains(s, '.')
-            parse(Float64, s)
-        else
-            parse(Int, s)
-        end
+        parse(Float64, s)
     end
 end
 
@@ -123,10 +117,6 @@
 
 """value is anything - object/list/number/boolean/string"""
 function take_val!(jp::JP)::Union{Nothing,Any}
-    d = take_object!(jp)
-    if !isnothing(d)
-        return d
-    end
     n = take_num!(jp)
     if !isnothing(n)
         return n
@@ -135,14 +125,18 @@
     if !isnothing(s)
         return s
     end
+    l = take_list!(jp)
+    if !isnothing(l)
+        return l
+    end
+    d = take_object!(jp)
+    if !isnothing(d)
+        return d
+    end
     b = take_bool!(jp)
     if !isnothing(b)
         return b
     end
-    l = take_list!(jp)
-    if !isnothing(l)
-        return l
-    end
     nothing
 end
 
@@ -152,13 +146,17 @@
     take_val!(jp)
 end
 
+function parse_struct(t::Type{T}, s::AbstractString)::T where {T}
+    take_struct!(t, JP(s))
+end
+
 function strip_ws!(jp::JP)
     while !isend(jp) && isspace(jp.s[jp.pos])
         jp.pos += 1
     end
 end
 
-function takewhile!(jp::JP, pred::Function, stripws=true)::Union{Nothing,Tuple{Int,Int}}
+function takewhile!(jp::JP, pred::Function, stripws = true)::Union{Nothing,Tuple{Int,Int}}
     if stripws
         strip_ws!(jp)
     end
@@ -167,13 +165,13 @@
         while !isend(jp) && pred(current(jp))
             next!(jp)
         end
-        (a, jp.pos-1)
+        (a, jp.pos - 1)
     else
         nothing
     end
 end
 
-function expect!(jp::JP, c::Char)::Bool
+@inline function expect!(jp::JP, c::Char)::Bool
     strip_ws!(jp)
     if current(jp) == c
         next!(jp)
@@ -185,9 +183,10 @@
 
 function expect_prefix!(jp::JP, pref::AbstractString)::Bool
     strip_ws!(jp)
+    pl = length(pref)
 
-    if (@view jp.s[jp.pos:min(length(jp.s), jp.pos+length(pref)-1)]) == pref
-        jp.pos += length(pref)
+    if (@view jp.s[jp.pos:min(jp.length, jp.pos + pl - 1)]) == pref
+        jp.pos += pl
         true
     else
         false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/julia/parallel/ParallelProcessing/src/metaparser.jl	Fri Mar 24 21:30:03 2023 +0100
@@ -0,0 +1,147 @@
+
+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