improvements to KDL library
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 21 Nov 2023 23:43:48 +0000 (17:43 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 21 Nov 2023 23:43:48 +0000 (17:43 -0600)
CHANGELOG
core/encoding/kdl/encoder.onyx [new file with mode: 0644]
core/encoding/kdl/kdl.onyx
core/encoding/kdl/kql.onyx
core/encoding/kdl/parser.onyx
core/encoding/kdl/utils.onyx

index f84fa8dd88852f1a59fc9e3bbdd53b7c440cb7e7..89890db3448be83bf03e677324aa9f8bf1861d36 100644 (file)
--- 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 (file)
index 0000000..f644298
--- /dev/null
@@ -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");
+        }
+    }
+}
index b6653f4ababb1eb46635a8cae0f7516a99d41275..f2ee270a0cb860f28edc09721ee5da194d53cb60 100644 (file)
@@ -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.
index 60fe8dc5defe1dbac87be478da6ed2fcf16551bf..4854454a8f1dbca21c828a3e5803740ed71681be 100644 (file)
@@ -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] == '[' {
index bdb8d1fb4d54e02830f366818f6c6bc515fde6e8..b17e6cfc28cf2625eaa8d5533993aec3cc1a85ae 100644 (file)
@@ -146,7 +146,7 @@ Token :: union {
             return self->handle_word();
         }
 
-        assert(false, tprintf("Unhandled character, {}", c));
+        // assert(false, tprintf("Unhandled character, {}", c));
         return .{ Error = .{} };
     }
 
index 1c738e6b43891ce51e33f67e70be146067c9fe82..fdbf7a13fd9257da9201d460b7304bc4cacb5557 100644 (file)
@@ -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