From 3f49e0381ccf8a44aebd16e9421d873b344ed2b6 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 21 Nov 2023 17:43:48 -0600 Subject: [PATCH] improvements to KDL library --- CHANGELOG | 9 +++ core/encoding/kdl/encoder.onyx | 72 +++++++++++++++++ core/encoding/kdl/kdl.onyx | 11 +++ core/encoding/kdl/kql.onyx | 139 +++++++++++++++++++++++++-------- core/encoding/kdl/parser.onyx | 2 +- core/encoding/kdl/utils.onyx | 16 +++- 6 files changed, 216 insertions(+), 33 deletions(-) create mode 100644 core/encoding/kdl/encoder.onyx diff --git a/CHANGELOG b/CHANGELOG index f84fa8dd..89890db3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,13 @@ Additions: - New installation script - sh <(curl https://get.onyxlang.io -sSfL) - Works on Linux and MacOS +- KDL document parsing support + - Used as the new format for the package manager. + - See https://kdl.dev for details +- `os.chdir` for changing the current directory + - Supported on WASIX and Onyx runtime +- `os.getcwd` for getting the current directory + - Supported on WASIX and Onyx runtime Removals: @@ -22,6 +29,8 @@ Bugfixes: - Formatting of days and months were incorrect `time.strftime`. - Infinite loop in TCP server when a client disconnects. + + Release v0.1.7 -------------- October 25th, 2023 diff --git a/core/encoding/kdl/encoder.onyx b/core/encoding/kdl/encoder.onyx new file mode 100644 index 00000000..f644298e --- /dev/null +++ b/core/encoding/kdl/encoder.onyx @@ -0,0 +1,72 @@ +package core.encoding.kdl +#allow_stale_code + +use core {io} + +write :: (d: &Document, w: &io.Writer) { + for d.nodes { + write_node(it, w); + io.write(w, "\n"); + } +} + +write_node :: (n: &Node, w: &io.Writer, indentation := 0) { + for indentation * 4 do io.write(w, " "); + + n.type_annotation->with([ta] { + io.write_format(w, "({}) ", ta); + }); + + io.write(w, n.node); + io.write(w, " "); + + for n.values { + write_value(it, w); + io.write(w, " "); + } + + for n.props->as_iter() { + io.write_format(w, "{}=", it.key); + write_value(it.value, w); + io.write(w, " "); + } + + if n.children { + io.write(w, "{\n"); + + for n.children { + write_node(it, w, indentation + 1); + } + + for indentation * 2 do io.write(w, " "); + io.write(w, "}"); + } + + io.write(w, "\n"); +} + +write_value :: (v: Value, w: &io.Writer) { + v.type_annotation->with([ta] { + io.write_format(w, "({}) ", ta); + }); + + switch v.data { + case s: .String { + io.write_format(w, "{\"}", s); + } + + case n: .Number do switch n { + case i: .Integer do io.write_format(w, "{}", i); + case f: .Float do io.write_format(w, "{}", f); + case s: .String do io.write_format(w, "{\"}", s); + } + + case b: .Boolean { + io.write_format(w, "{}", b); + } + + case .Null { + io.write_format(w, "null"); + } + } +} diff --git a/core/encoding/kdl/kdl.onyx b/core/encoding/kdl/kdl.onyx index b6653f4a..f2ee270a 100644 --- a/core/encoding/kdl/kdl.onyx +++ b/core/encoding/kdl/kdl.onyx @@ -2,6 +2,7 @@ package core.encoding.kdl #allow_stale_code #load "./parser" +#load "./encoder" #load "./utils" #load "./kql" @@ -44,6 +45,16 @@ KDL_Number :: union { String: str; } +#doc """ + Creates a new KDL document, using the allocator provided. +""" +new_doc :: (allocator := context.allocator) -> Document { + doc: Document; + doc.allocator = allocator; + doc.nodes = make([..] &Node, allocator); + return doc; +} + #doc """ Parses a string or `io.Reader` into a KDL document, using the allocator provided for internal allocations. diff --git a/core/encoding/kdl/kql.onyx b/core/encoding/kdl/kql.onyx index 60fe8dc5..4854454a 100644 --- a/core/encoding/kdl/kql.onyx +++ b/core/encoding/kdl/kql.onyx @@ -4,39 +4,93 @@ package core.encoding.kdl use core {iter, alloc, array, string} #inject Document { - query :: query - query_all :: query_all + query :: query_doc + query_all :: query_doc_all } -query :: (d: &Document, query: str) -> ? &Node { - query_iter := query_all(d, query); - node, empty := iter.next(query_iter); +#inject Node { + query :: query_node + query_all :: query_node_all +} + +query :: #match #local { + query_doc, query_node +} + +query_all :: #match #local { + query_doc_all, query_node_all +} + +query_doc :: (d: &Document, query: str) -> ? &Node { + query_iter := query_doc_all(d, query); + node, cont := iter.next(query_iter); iter.close(query_iter); - if !empty do return node; - return null; + if cont do return node; + return .{}; } -query_all :: (d: &Document, query: str) -> Iterator(&Node) { +query_doc_all :: (d: &Document, query: str) -> Iterator(&Node) { arena := alloc.arena.make(context.allocator, 16 * 1024); q := parse_query(query, alloc.as_allocator(&arena)); - ctx := .{ - d = d, - q = q, - stack = make([..] QueryStack, 8, alloc.as_allocator(&arena)), - top_level_node = -1, - current_selector = 0, - arena = arena - }; + ctx := QueryIterator.make(d, q, arena); + + return iter.generator( + &ctx, + query_next, + ctx => { alloc.arena.free(&ctx.arena); delete(&ctx.stack); } + ); +} + +query_node :: (n: &Node, query: str) -> ? &Node { + query_iter := query_node_all(n, query); + node, cont := iter.next(query_iter); + iter.close(query_iter); + + if cont do return node; + return .{}; +} + +query_node_all :: (n: &Node, query: str) -> Iterator(&Node) { + if n == null do return iter.empty(&Node); + + arena := alloc.arena.make(context.allocator, 16 * 1024); + q := parse_query(query, alloc.as_allocator(&arena)); + + ctx := QueryIterator.make(null, q, arena); + ctx.stack << .{ n, 0 }; return iter.generator( &ctx, query_next, - ctx => { alloc.arena.free(&ctx.arena); } + ctx => { alloc.arena.free(&ctx.arena); delete(&ctx.stack); } ); } +#local +QueryIterator :: struct { + d: &Document; + q: Query; + stack: [..] QueryStack; + arena: alloc.arena.Arena; + + top_level_node := -1; + current_selector := 0; +} + +#inject +QueryIterator.make :: ( + d: &Document, + q: Query, + arena: alloc.arena.Arena +) -> QueryIterator { + return QueryIterator.{ + d = d, q = q, arena = arena, + stack = make([..] QueryStack, 16) + }; +} + #local QueryStack :: struct { node: &Node; @@ -44,16 +98,18 @@ QueryStack :: struct { } #local -query_next :: ctx => { +query_next :: (ctx: &QueryIterator) -> (&Node, bool) { while true { if !ctx.stack { - if !ctx.d do break; - - // If the stack is empty, populate with a node - ctx.top_level_node += 1; - if ctx.top_level_node >= ctx.d.nodes.length do break; + if ctx.d { + // If the stack is empty, populate with a node + ctx.top_level_node += 1; + if ctx.top_level_node >= ctx.d.nodes.length do break; - ctx.stack << .{ ctx.d.nodes[ctx.top_level_node], 0 }; + ctx.stack << .{ ctx.d.nodes[ctx.top_level_node], 0 }; + } else { + break; + } } last_query := array.get_ptr(ctx.stack, -1); @@ -82,6 +138,7 @@ query_selector_matches :: (s: &Selector, trail: [] QueryStack) -> bool { node_index: i32 = trail.count - 1; if !query_matcher_matches(s.segments[0].matcher, trail[node_index].node) { + // logf(.Error, "NO MATCH BETWEEN '{*p}' and '{*p}'\n=======================", s.segments[0].matcher, trail[node_index].node); return false; } @@ -90,15 +147,21 @@ query_selector_matches :: (s: &Selector, trail: [] QueryStack) -> bool { switch segment.op->unwrap() { case .Child, .Descendant { while node_index >= 0 { - defer node_index -= 1; if query_matcher_matches(segment.matcher, trail[node_index].node) { // Continue from the outer for loop + node_index -= 1; continue continue; } if segment.op->unwrap() == .Child { break; } + + node_index -= 1; + } + + if segment->is_scope() { + if node_index < 0 do return true; } return false; @@ -208,6 +271,10 @@ Selector :: struct { SelectorSegment :: struct { op: ? SelectorOp; matcher: &Matcher; + + is_scope :: (s: &SelectorSegment) => + s.matcher.details.length == 1 && + s.matcher.details[0].accessor.tag == .Scope } #local @@ -342,13 +409,23 @@ parse_matcher :: (p: &QueryParser) -> ? &Matcher { }); if p.query[p.cursor] != '[' { - id := parse_identifier(p); + if string.starts_with(p->rem(), "top()") { + p.cursor += 5; + m.details << .{ + accessor = .{ Scope = .{} }, + op = .Equal, + value = .{ None = .{} }, + }; - m.details << .{ - accessor = .{ Node = .{} }, - op = .Equal, - value = .{ Some = .{ data = .{ String = id } } } - }; + } else { + id := parse_identifier(p); + + m.details << .{ + accessor = .{ Node = .{} }, + op = .Equal, + value = .{ Some = .{ data = .{ String = id } } } + }; + } } while p.query[p.cursor] == '[' { diff --git a/core/encoding/kdl/parser.onyx b/core/encoding/kdl/parser.onyx index bdb8d1fb..b17e6cfc 100644 --- a/core/encoding/kdl/parser.onyx +++ b/core/encoding/kdl/parser.onyx @@ -146,7 +146,7 @@ Token :: union { return self->handle_word(); } - assert(false, tprintf("Unhandled character, {}", c)); + // assert(false, tprintf("Unhandled character, {}", c)); return .{ Error = .{} }; } diff --git a/core/encoding/kdl/utils.onyx b/core/encoding/kdl/utils.onyx index 1c738e6b..fdbf7a13 100644 --- a/core/encoding/kdl/utils.onyx +++ b/core/encoding/kdl/utils.onyx @@ -4,7 +4,7 @@ package core.encoding.kdl use core {string} #inject Value { - as_str :: (v: &Value) -> ? str { + as_str :: (v: Value) -> ? str { return v.data.String; } @@ -44,4 +44,18 @@ use core {string} .{ data = value } ); } + + value :: (n: &Node, index := 0) -> ? Value { + if index >= n.values.length do return .{}; + + return n.values[index]; + } + + value_or_null :: (n: &Node, index := 0) -> Value { + if index >= n.values.length do return .{ + data = .{ Null = .{} } + }; + + return n.values[index]; + } } \ No newline at end of file -- 2.25.1