import std.stdio;
import std.algorithm.mutation;
struct DeepCopyContext { // memory to save links
DomElement[DomElement] org_copy_map;
void register_element(DomElement key, DomElement copy) {
org_copy_map [key] = copy;
}
DomElement resolve(DomElement original) {
auto res = original in org_copy_map;
return res is null ? original : *res;
}
}
class DomElement {
DomElement parent;
this() {}
this(DomElement c) { this.parent = c.parent; }
~this() { parent = null; }
DomElement deep_copy(ref DeepCopyContext ctx) {
auto rc = clone();
ctx.register_element(this, rc);
return rc;
}
void restore_link(ref DeepCopyContext){}
DomElement clone() { return new DomElement(this); }
}
class DomGroup(ElementType: DomElement): DomElement {
ElementType[] items;
DomGroup add_item(ElementType item) {
for (DomElement i = this; i; i = i.parent) {
if (i == item)
throw new Exception("Cycle in parent chain");
}
if (item.parent)
throw new Exception("Multiparenting");
item.parent = this;
items ~= item;
return this;
}
void remove_item(ElementType item) {
assert(item.parent == this);
items = remove!(x => x == item)(items);
item.parent = null;
// invalidate all Connectors?
// or easier way - check connector.from.parent == null
}
override DomGroup deep_copy(ref DeepCopyContext ctx) {
auto result = new DomGroup;
result.parent = this;
ctx.register_element(this, result);
result.items.length = items.length; // pre-alloc
for(int i = 0; i < items.length; i++)
result.items[i] = items[i].deep_copy(ctx);
return result;
}
override void restore_link(ref DeepCopyContext ctx){
foreach(i; items) i.restore_link(ctx);
}
}
class Style {
immutable string font;
immutable float size;
immutable int weight;
this(string f, float s, int w) { font = f; size = s; weight = w; }
this(const ref Style st) { font = st.font; size = st.size; weight = st.weight; }
}
struct Bitmap {
immutable string src;
this(string s) { src = s; }
}
alias CardItem = DomElement;
alias Card = DomGroup!(DomElement);
alias GroupItem = DomGroup!(DomElement);
alias Document = DomGroup!(Card);
class TextItem : CardItem {
string text;
Style style;
this(string t, Style s) { super(); text = t; style = s; }
this(TextItem c) { super(c); text = c.text; style = c.style; }
override DomElement clone() { return new TextItem(this); }
}
class ImageItem : CardItem {
Bitmap bitmap;
this(Bitmap b) { bitmap = b; }
this(ImageItem c) { super(c); bitmap = c.bitmap; }
override DomElement clone() { return new ImageItem(this); }
}
class ButtonItem : CardItem {
string caption;
Card target_card;
this(string cap, Card target) { caption = cap; target_card = target; }
this(ButtonItem c) { super(c); caption = c.caption; target_card = c.target_card; }
override DomElement clone() { return new ButtonItem(this); }
~this() { target_card = null; }
override void restore_link(ref DeepCopyContext ctx) {
target_card = cast(Card)ctx.resolve(target_card);
}
}
class ConnectorItem : CardItem {
CardItem from, to;
this(CardItem f, CardItem t) { from = f; to = t; }
this(ConnectorItem c) { super(c); from = c.from; to = c.to; }
override DomElement clone() { return new ConnectorItem(this); }
override void restore_link(ref DeepCopyContext ctx) {
from = ctx.resolve(from);
to = ctx.resolve(to);
}
}
ElementType deep_copy(ElementType: DomElement) (ref ElementType src) {
DeepCopyContext ctx;
auto result = cast(ElementType)src.deep_copy(ctx);
result.restore_link(ctx);
return result;
}
void main() {
auto doc = new Document;
{
auto style = new Style("Times", 16.5f, 600);
auto card = new Card;
auto hello = new TextItem("Hello", style);
auto button = new ButtonItem("Click me", card);
auto conn = new ConnectorItem(hello, button);
doc.add_item(card.add_item(hello)
.add_item(button)
.add_item(conn)
);
}
{
/// "Unshare on modification" enforcement at compile-time.
auto hello_text = cast(TextItem)(doc.items[0].items[0]);
/// This won't compile:
//hello_text.style.size++;
/// This compiles and unshares on modification
auto new_style = new Style(hello_text.style.font, hello_text.style.size +1, hello_text.style.weight);
hello_text.style = new_style;
/// Object protection by stack pointers:
doc.items[0].remove_item(hello_text);
/// Check that hello text is alive and pointed to by weak pointers
/// as long as it's retained by hello_text local
assert(doc.items[0].items[1] !is null); // ConnectorItem
/// here it's finally deleted
}
/// Weaks handling:
/// Let's check that connector is no more pointing to the deleted text:
//assert(doc.items[0].items[1] is null); // ConnectorItem FAIL, GC
//коннектор будет держать удаленные связи
// Topologically correct copy operation:
// Let's copy the whole doc...
auto new_doc = deep_copy(doc);
/// ...and check that connector copy points to the button copy
assert(new_doc.items[0].items[0] ==
(cast(ConnectorItem)new_doc.items[0].items[1]).to);
/// Let's check that button copy points to the card copy
assert(new_doc.items[0] ==
(cast(ButtonItem)new_doc.items[0].items[0]).target_card);
/// Protection against multiparenting (runtime-only)
try {
doc.add_item(new_doc.items[0]);
} catch (Exception e) {
writeln(e.msg);
}
/// Protection against cycles (runtime-only)
try {
auto group = new GroupItem;
auto subgroup = new GroupItem;
group.add_item(subgroup);
subgroup.add_item(group);
} catch (Exception e) {
writeln(e.msg);
}
}
To embed this program on your website, copy the following code and paste it into your website's HTML: