deepcopy/src/DeepCopy.m3


UNSAFE MODULE DeepCopy;

IMPORT
  RTAllocator, RTTypeMap, RefSeq, IntRefTbl, Fmt, Atom, RTTypeSRC;

CONST
  Debug = FALSE;

TYPE
  Copier = RTTypeMap.Visitor OBJECT
    done, copies: RefSeq.T;
  METHODS
    init(READONLY dontcopy: Refs): Copier := CopierInit;
    seen(ref: REFANY; VAR copy: REFANY): BOOLEAN := CopierSeen;
    copy(from: REFANY): REFANY := CopierCopy;
    special(ref: REFANY; VAR copy: REFANY): BOOLEAN := CopierSpecial;
  OVERRIDES
    apply := CopierApply;
  END;

  AtomSpec = Special OBJECT
  OVERRIDES
    copy := AtomSpecCopy;
  END;

VAR
  Spec: IntRefTbl.T := NIL;

PROCEDURE Copy(from: REFANY; READONLY dontcopy := Refs{}): REFANY =
VAR
  ret: REFANY;
BEGIN
  WITH copier = NEW(Copier).init(dontcopy) DO
    ret := copier.copy(from);
    IF Debug THEN <*DEBUG "Count = "&Fmt.Int(copier.done.size())*> END;
    RETURN ret;
  END;
END Copy;

PROCEDURE CopierInit(this: Copier; READONLY dontcopy: Refs): Copier =
BEGIN
  this.copies := NEW(RefSeq.T).fromArray(dontcopy);
  this.done := NEW(RefSeq.T).fromArray(dontcopy);
  RETURN this;
END CopierInit;

PROCEDURE CopierCopy(this: Copier; from: REFANY): REFANY =
VAR
  copy: REFANY := NIL;
BEGIN
  IF from = NIL THEN RETURN NIL END;
  IF Debug THEN <*DEBUG "Copying "&Fmt.Unsigned(LOOPHOLE(from, INTEGER))*> END;
  IF NOT this.seen(from, copy) AND NOT this.special(from, copy) THEN
    (* shallow copy*)
    copy := RTAllocator.Clone(from);
    (* memo *)
    this.done.addhi(from);
    this.copies.addhi(copy);
    (* do structure *)
    RTTypeMap.WalkRef(copy, RTTypeMap.Mask{RTTypeMap.Kind.Ref}, this);
  END;
  RETURN copy;
END CopierCopy;

PROCEDURE CopierSeen(this: Copier; ref: REFANY; VAR copy: REFANY): BOOLEAN =
BEGIN
  (* forced to do linear searches because ref values can change at any time *)
  IF Debug THEN <*DEBUG "Check seen "&Fmt.Unsigned(LOOPHOLE(ref, INTEGER))*> END;
  FOR i := 0 TO this.done.size()-1 DO
    IF ref = this.done.get(i) THEN
      copy := this.copies.get(i);
      RETURN TRUE;
    END;
  END;
  RETURN FALSE;
END CopierSeen;

PROCEDURE CopierSpecial(<*UNUSED*> this: Copier; ref: REFANY; VAR copy: REFANY): BOOLEAN =
VAR
  s: REFANY;
BEGIN
  IF Spec = NIL THEN RETURN FALSE END;
  IF Spec.get(TYPECODE(ref), s) THEN
    IF Debug THEN <*DEBUG "Doing special: "&RTTypeSRC.TypecodeName(TYPECODE(ref))*> END;
    copy := NARROW(s, Special).copy(ref);
    RETURN TRUE;
  ELSE
    RETURN FALSE;
  END;
END CopierSpecial;

PROCEDURE CopierApply(this: Copier; field: ADDRESS; <*UNUSED*> k: RTTypeMap.Kind) RAISES ANY =
BEGIN
  IF Debug THEN <*DEBUG "Apply of "&Fmt.Unsigned(LOOPHOLE(field, REF INTEGER)^)*> END;
  WITH ref = LOOPHOLE(field, UNTRACED REF REFANY) DO
    ref^ := this.copy(ref^);
  END;
END CopierApply;

PROCEDURE RegisterSpecial(tc: CARDINAL; copier: Special) =
BEGIN
  IF Spec = NIL THEN
    Spec := NEW(IntRefTbl.Default).init();
  END;
  EVAL Spec.put(tc, copier);
END RegisterSpecial;
Atom Special Methods

PROCEDURE AtomSpecCopy(<*UNUSED*> this: AtomSpec; from: REFANY): REFANY =
BEGIN
  RETURN Atom.FromText(Atom.ToText(from));
END AtomSpecCopy;

BEGIN
  RegisterSpecial(TYPECODE(Atom.T), NEW(AtomSpec));
END DeepCopy.