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");
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.