Argentum objects got parent pointers and safe-n-fast object reattachments

Each mutable object can tell who is its parent.
These parent pointer are maintained automatically at a very low cost (single move instruction).
They simplify handling of document data models and allow integrity checks if needed.

using array;  // this module extends Array with `append` and many other methods
class Node {
    tag = "default-tag";
    children = sys_Array(Node);

    withTag(v str) this { tag := v }
    add(c()@Node) this { children.append(c) }
    parent() Node {
        sys_getParent(this)   // our nodes live in children arrays, so the first getParent returns array
        && sys_getParent(_)   // the second getParent returns array`s parent i.e Node object
        && _~Node             // cast it to Node
        : Node.withTag("no-parent")  // If any parents are absent or it is not a Node, return a temp dummy Node
                                     // Alternatively we can call sys_terminate, though it's not resilient
                                     // Or (preferably) return ?Node, informing the caller code of absence
    }
    getAt(index int) Node { children[index] : Node.withTag("no-child") } // again, there are plenty of ways of handling index OOB here 
}

root = Node.withTag("document")
    .add(Node.withTag("head"))
    .add(Node.withTag("body")
        .add(Node.withTag("div")));
div = root[1][0];

sys_log(div.parent().parent().tag);  // prints "document"

In this example we build a toy HTML DOM and traverse it.

The built-in sys_getParent functions extract the actual parent pointers that are automatically maintained by the Argentum runtime.

These parent pointers also assist a new "splice" operation, that allows to extract an existing object from one part of the object hierarchy and insert it in other place:

// Example continued
root.children[1] := Node; // Now body does not belong to the root document.
                          // it will be deleted,
                          // but its nested div is still retained with `div` variable.
root.children.spliceAt(1, div); // Now div is inserted in root collection.

Splice operation checks if the new host object is not nested in the object-been-spliced, and if so, doesn't do splicing and returns false. So splicing is a 100% safe (but not 100% successful) operation.

Splicing also available for scalar fields:

// This class definition should be declared before all imperative code
class Pair {
   a = Node;
   b = Node;
}

// Continued example
p = Pair;
head = root[0];
root := Node; // Now all document is deleted, but the head is retained by the local reference.
p.a @= head;  // "Splice it to the field" operator @=

TL;DR

Now parts of tree-like data structures can be rearranged in safe manner instead of copy-destroy sequences as it was before.

Now parent pointers are built-in all Argentum objects.

The full example (try it on the playground):

using array;
using string;
const LN = utf32_(0x0a);

fn log(s str) { sys_log("{s}{LN}") }

class Pair {
   a = Node;
   b = Node;
}

class Node {
    tag = "empty";
    children = sys_Array(Node);

    call(v str) this { tag := v }
    add(c()@Node) this { children.append(c) }
    parent() Node {
        sys_getParent(this) && sys_getParent(_) && _~Node : Node("no-parent")
    }
    getAt(index int) Node { children[index] : Node("no-child") } // Again, there are plenty of ways of handling index OOB here.
    dump(title str) {
        log("{title}: ");
        dumpRecur("")
    }
    dumpRecur(prefix str) {
        log("{prefix}{tag}");
        children.each\_.dumpRecur("{prefix}   ")
    }
}

root = Node("document")
    .add(Node("head"))
    .add(Node("body")
        .add(Node("div")));
root.dump("Initial");

div = root[1][0];
div.dump("div-subree");

log("div.parent.parent: {div.parent().parent().tag}");  // prints "document"

root.children[1] := Node;
root.dump("after root.children[1] := Node");

root.children.spliceAt(1, div);
root.dump("root.children.spliceAt(1, div)");

p = Pair;
head = root[0];
root := Node;
p.a @= head;
p.a.dump("head is spliced into a pair");

6 Comments

  1. I’ve tried to play with the first code sample in this post.
    1. I get error for “setTag”:
    Error at (x40tst:7:38): Expected type: *sys_String not sys_String in assign to field tag(x40tst:4:11)

    Changing “tag := v” to “tag = v;” resolves the problem.

    2. What is the proper way to work with “children” Array? I’ve looked at https://aglang.org/array-weakarray-sharedarray/, and there are e.g. lines like “cats.add(_)”. But if I try to use .add, I get the error:
    Error at (x43tst:11:18): class sys_Array(x43tst_Node) doesn’t have field/method add

    The same applies to .size()

    For .insert(0, 1) I get
    Error at (x46tst:13:35): Expected type: x46tst_Node not ?sys_Object in checking actual result type against fn declaration

    It would be great to have a couple of working examples

    • >Expected type: *sys_String not sys_String
      `tag` -field is a shared pointer to strings (from initializer `tag=””`) so it should be assigned with shared immutable string: `*sys_String` or `str` (it’s the same)

      > Changing “tag := v” to “tag = v;” resolves the problem.
      `tag = v` this doesn’t set field, it creates a local variable `tag` initialized with `v`

    • > What is the proper way to work with “children” Array
      The build-in array is extremely dumb. Luckily any class in Argentum can be extended with new methods.
      `using array` imports a module a handful of methods – among others – append.

      • > `using array` imports a module a handful of methods – among others – append.
        I thought `using sys { Array }` is enough. Should be more attentive. `using array` is mentioned in the “Modules” article. It seems that I’m starting to understand 🙂 Those in curly brackets are specific classes/functions/etc like in Rust.

        • `Using moduleName`
          adds the whole module to your application or to your other module. It’s a dependency declaration. Once added the whole module content is accessible with long names: moduleName_ClassName. But adding a curly-enclosed list makes all listed entities accessible with short names. There is also name=import syntax that allows to import with custom names.

    • I turned all illustrative code into actually working examples and added the combined example at the end.
      Many thanks for your feedback!
      Btw I also fixed the `object.&delegate()` crash in the compiler that you’ve spotted.

Leave a Reply

Your email address will not be published. Required fields are marked *