Module: dood Synopsis: The Dylan object-oriented database Author: Jonathan Bachrach Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define constant $dood-unbound = unbound(); // version define constant $dood-version-id = 0; define constant $dood-user-version-id = 1; // book keeping define constant $dood-corruption-id = 2; define constant $dood-free-address-id = 3; define constant $dood-root-id = 4; define constant $dood-proxies-id = 5; // predefines define constant $dood-predefines-begin = 10; define constant $dood-true-id = 10; define constant $dood-false-id = 11; define constant $dood-unbound-id = 12; define constant $dood-empty-list-id = 13; define constant $dood-empty-vector-id = 14; define constant $dood-class-program-binding-proxy-class-id = 15; define constant $dood-program-module-proxy-class-id = 16; define constant $dood-symbol-class-id = 17; define constant $dood-string-class-id = 18; define constant $dood-pair-class-id = 19; define constant $dood-predefines-end = 20; //define constant $dood-predefines-count // = $dood-predefines-end - $dood-predefines-begin; // header finish define constant $dood-header-size = $dood-predefines-end; define constant $dood-version = 4; define constant $dood-no-version = -1; define constant $dood-empty-vector = #[]; define constant $dood-empty-stretchy-vector = make(, size: 0); // TODO: PLUG IN REAL THANG define constant $default-dood-table-size = 128; define inline function make-weak-key-table (#rest all-keys, #key size, #all-keys) apply(make, , /* weak: #"key", */ all-keys) end function; define inline function make-weak-value-table (#rest all-keys, #key size, #all-keys) apply(make, , /* weak: #"value", */ all-keys) end function; define inline function make-object->address-table (#rest all-keys, #key size) apply(make-weak-key-table, all-keys) // apply(make-two-level-table, all-keys) end function; define inline function make-address->object-table (#key size = $default-dood-table-size) // make-two-level-table() make(, size: size) end function; // define function two-level-table-sizes (x :: ) // let sizes = collecting () for (e in x) collect(size(e)) end end; // list(reduce(\+, 0, sizes), sizes) // end function; // define function dood-object->address-table-sizes (x ::
) // two-level-table-sizes(x) // end function; // define function dood-address->object-table-sizes (x ::
) // // two-level-table-sizes(x) // size(x) // end function; define inline function make-weak-table (kind :: false-or(), #rest all-keys, #key size, #all-keys) if (kind == #"key") apply(make-weak-key-table, all-keys) elseif (kind == #"value") apply(make-weak-value-table, all-keys) else // false apply(make, , all-keys) end if end function; define macro dood-state-definer { define ?adjectives:* dood-state ?:name of ?class:name using ?trampoline:name (?supers:*) ?slots:* end } => { define dood-state-class (?adjectives) ?name (?supers) ?slots end; define dood-state-accessors using ?trampoline (?class) ?slots end } end macro; define macro dood-state-class-definer { define dood-state-class (?adjectives:*) ?:name (?supers:*) ?cslots end } => { define ?adjectives class ?name (?supers) ?cslots end } cslots: { } => { } { ?cslot:*; ...} => { ?cslot; ...} cslot: { ?mods:* slot ?slot-variable, ?rest:* } => { ?mods slot ?slot-variable, ?rest } slot-variable: { ?slot-name :: ?:expression ?maybe-init-expression } => { ?slot-name :: ?expression ?maybe-init-expression } slot-name: { ?:name } => { ?name ## "-state" } maybe-init-expression: { = ?:expression } => { = ?expression } { } => { } end macro; define macro dood-state-accessors-definer { define dood-state-accessors using ?trampoline:name (?class:name) end } => { } { define dood-state-accessors using ?trampoline:name (?class:name) constant slot ?accessor:name :: ?type:expression ?maybe-init-expression, ?rest:*; ?more:* end } => { define inline method ?accessor (object :: ?class) => (val :: ?type) ?accessor ## "-state"(?trampoline(object)) end method; define dood-state-accessors using ?trampoline (?class) ?more end } { define dood-state-accessors using ?trampoline:name (?class:name) slot ?accessor:name :: ?type:expression ?maybe-init-expression, ?rest:*; ?more:* end } => { define inline method ?accessor (object :: ?class) => (val :: ?type) ?accessor ## "-state"(?trampoline(object)) end method; define inline method ?accessor ## "-setter" (value :: ?type, object :: ?class) ?accessor ## "-state"(?trampoline(object)) := value end method; define dood-state-accessors using ?trampoline (?class) ?more end } maybe-init-expression: { = ?:expression } => { = ?expression } { } => { } end macro; // (gts,98sep20) moved from object.dylan to avoid emulator forward ref. issue. define constant = ; define class () slot dood-segment-name = #"default", init-keyword: name:; slot dood-segment-id :: = 0, init-keyword: id:; end class; ignore(dood-segment-name); define generic dood-segment-instance? (segment :: , x) => (well? :: ); define method dood-segment-instance? (segment :: , x) => (well? :: ) #t end method; define class () constant slot dood-segment-type :: , required-init-keyword: type:; end class; define sealed method initialize (segment :: , #key name, type, #all-keys) next-method(); unless (name) dood-segment-name(segment) := type; end unless; end method; define method dood-segment-instance? (segment :: , x) => (well? :: ) subtype?(x, dood-segment-type(segment)) end method; define class () constant slot dood-segment-test :: , required-init-keyword: test:; end class; define method dood-segment-instance? (segment :: , x) => (well? :: ) dood-segment-test(segment)(x) end method; define class () constant slot dood-segment-state-segment :: , required-init-keyword: segment:; slot dood-segment-free-address ::
= 0, init-keyword: free-address:; end class; ignore(dood-segment-state-segment); // ok, done with stuff moved from object.dylan define open primary class () slot dood-name = #f, init-keyword: name:; slot dood-given-stream :: false-or() = #f, init-keyword: stream:; slot dood-backups? :: = #t, init-keyword: backups?:; slot dood-root = #f; slot dood-state :: = make(); slot dood-backup :: = make(); constant slot dood-world :: = dood-world-default(), init-keyword: world:; constant slot dood-read-only? :: = #f, init-keyword: read-only?:; constant slot dood-batch-mode? :: = #t, init-keyword: batch-mode?:; constant slot dood-specified-user-version :: = $dood-version, init-keyword: version:; slot dood-forwarding-address :: false-or(
) = #f; // should be locked slot dood-walked-addresses :: = make-object->address-table(); /* // used for walking slot dood-current-mark :: = 0; slot dood-walked-mark-addresses ::
= make-weak-key-table(size: $default-dood-table-size); slot dood-walked-objects :: = make(); */ constant slot dood-back-pointers :: = make(); slot dood-walked-count :: = 0; slot dood-locator, init-keyword: locator:; slot dood-init-keys :: ; slot dood-walk-queue :: = make(); constant slot dood-work :: = make(); constant slot dood-default-segment :: = make(), init-keyword: default-segment:; slot dood-segments :: = make-default-segments(), init-keyword: segments:; end class; // This slot is not really needed... ignore(dood-given-stream); define inline method dood-classes (dood :: ) => (res :: ) dood-world-classes(dood-world(dood)) end method; define function make-default-segments () => (res :: ) // #[] vector(make(, name: "symbol", type: type-union(, ))) end function; define dood-state of using dood-state () slot dood-dood :: , init-keyword: dood:; slot dood-stream :: , init-keyword: stream:; slot dood-free-address ::
= 0; slot dood-addresses :: = make-object->address-table(); constant slot dood-objects :: = make-address->object-table(); // HACK: SHOULD BE SET constant slot dood-predefines :: = make(); constant slot dood-predefine-addresses :: = make(); constant slot dood-disk-objects :: = make(); constant slot dood-module-proxies :: = make(); // same as dood-disk-objects.key-sequence.. slot dood-proxies :: = make(); constant slot dood-lock :: = make(); slot dood-segment-states :: = #[]; slot dood-current-segment :: ; end dood-state; ignore(dood-lock); // HACK: SHOULD AVOID CONSTRUCTING THIS ignore(dood-dood); ignore(dood-dood-setter); // HACK: SHOULD USE CONSTANT ADJECTIVE // define method dood-reinitialize-state // (state :: , #rest all-keys, #key, #all-keys) => () // // rerun init expressions // apply(reinitialize, state, all-keys); // end method; define method dood-flush-state (x :: ) => (res :: ) if (slot-initialized?(x, dood-stream-state)) make(, dood: dood-dood-state(x), stream: dood-stream-state(x)); else x end if end method; define method dood-flush-state (x :: ) => (res :: ) dood-state(x) := dood-flush-state(dood-state(x)); dood-backup(x) := dood-flush-state(dood-backup(x)); x end method; define method dood-exchange-states (dood :: ) let tmp-state = dood-backup(dood); dood-backup(dood) := dood-state(dood); dood-state(dood) := tmp-state; end method; define open class () end class; define inline method do-with-dood-state (dood :: , state :: , f1 :: , f2 :: ) with-lock (dood-lock-state(state)) if (dood-state(dood) == state) f1() else block () dood-exchange-states(dood); if (dood-state(dood) == state) f2(); else signal(make()); end if; cleanup dood-exchange-states(dood); end block; end if; end with-lock; end method; define macro with-dood-state { with-dood-state (?dood:expression, ?state:expression) ?:body end } => { do-with-dood-state(?dood, ?state, method () ?body end, method () ?body end) } end macro; define method dood-open-stream (dood :: , #rest extra-keys, #key, #all-keys) => (stream :: ) let all-keys = concatenate(extra-keys, dood-init-keys(dood)); apply(make, , buffer-vector: dood-world-buffer-pool(dood-world(dood)), // number-of-buffers: $dood-default-number-of-buffers, // buffer-size: $dood-default-buffer-size, // direction: #"input-output", all-keys); end method; define method dood-new-locator (dood :: ) => (locator) let locator = dood-locator(dood); make(, directory: locator-directory(locator), base: locator-base(locator), extension: "new") end method; define method dood-open-new-stream (dood :: ) => (stream :: ) dood-open-stream (dood, locator: dood-new-locator(dood), if-exists: #"replace"); end method; define method dood-save-state (dood :: ) => () when (dood-backups?(dood)) dood-exchange-states(dood); dood-state(dood) := dood-flush-state(dood-state(dood)); dood-stream-state(dood-state(dood)) := dood-open-new-stream(dood); dood-boot(dood); end when; end method; define method dood-close-state-stream (dood :: , state :: , #rest all-keys, #key abort? = #t, #all-keys) => () if (slot-initialized?(state, dood-stream-state)) apply(close, dood-stream-state(state), abort?: abort?, all-keys) end if; end method; define method dood-size (dood :: ) => (res :: ) let state = dood-state(dood); if (slot-initialized?(state, dood-stream-state)) stream-size(dood-stream(dood)) else 0 end if end method; define method dood-restore-state (dood :: ) => () when (dood-backups?(dood)) dood-exchange-states(dood); dood-close-state-stream(dood, dood-backup(dood)); delete-file(dood-new-locator(dood)); dood-backup(dood) := dood-flush-state(dood-backup(dood)); end when; end method; define method dood-close-streams (dood :: ) => () dood-close-state-stream(dood, dood-state(dood)); dood-close-state-stream(dood, dood-backup(dood)); end method; define method dood-exchange-stream-names (dood :: ) dood-close-streams(dood); rename-file (dood-new-locator(dood), dood-locator(dood), if-exists: #"replace"); dood-stream(dood) // REOPEN := dood-open-stream(dood, if-exists: #"overwrite"); end method; define method dood-flush-backup (dood :: ) => () when (dood-backups?(dood)) dood-exchange-stream-names(dood); dood-backup(dood) := dood-flush-state(dood-backup(dood)); end when; end method; // define method dood-read-only? (dood :: ) => (res :: ) // stream-direction(dood) == #"input" // end method; define method boot-predefines (dood :: ) => () // TODO: EVERY DATABASE HAS COPY OF THESE! local method register-predefine (object, address) dood-register-object(dood, object, address); dood-predefines(dood)[object] := object; dood-predefine-addresses(dood)[object] := address; end method; register-predefine(#t, $dood-true-id); register-predefine(#f, $dood-false-id); register-predefine($dood-unbound, $dood-unbound-id); register-predefine(#(), $dood-empty-list-id); register-predefine($dood-empty-vector, $dood-empty-vector-id); register-predefine(, $dood-string-class-id); register-predefine(, $dood-pair-class-id); register-predefine(, $dood-symbol-class-id); register-predefine (, $dood-class-program-binding-proxy-class-id); register-predefine (, $dood-program-module-proxy-class-id); end method; /* define method dood-reset-live-objects (dood :: , live-objects :: ) => () let objects = make-address->object-table(); format-out("NUMBER LIVE OBJECTS %=\n", size(live-objects)); for (object keyed-by address in dood-objects(dood)) when (member?(object, live-objects)) format-out(" FOUND %= @ %=\n", object, address); objects[address] := object; end when; end for; format-out("NUMBER FOUND LIVE OBJECTS %=\n", size(objects)); dood-objects(dood) := objects; let addresses = make-object->address-table(); for (address keyed-by object in dood-addresses(dood)) when (member?(object, live-objects)) format-out(" FOUND %= @ %=\n", object, address); addresses[object] := address; end when; end for; format-out("NUMBER FOUND LIVE ADDRESSES %=\n", size(addresses)); dood-addresses(dood) := addresses; for (object keyed-by address in dood-objects(dood)) unless (dood-address(dood, object)) // INVERT MAPPING TABLE ENTRY format-out("INVERTING %=\n", object); dood-register-address(dood, object, address); mark-lazy-slots(dood, object); end unless; end for; boot-predefines(dood); end method; */ define sealed method dood-initial-segment-states (dood :: ) => (res :: ) let states = collecting (as ) for (segment in dood-segments(dood)) collect(make(, segment: segment)); end for; end collecting; states; end method; define method dood-boot (dood :: ) => () dood-segment-states(dood) := dood-initial-segment-states(dood); let segment = dood-default-segment(dood); let segment-state = dood-lookup-segment-state-by-id(dood, dood-segment-id(segment)); dood-segment-free-address(segment-state) := $dood-header-size; dood-current-segment(dood) := segment; dood-free-address(dood) := dood-number-pages(dood, $dood-header-size) * dood-page-size(dood); dood-world-register-dood(dood-world(dood), dood); boot-predefines(dood); end method; define method dood-flush (dood :: ) => (dood :: ) dood-flush-state(dood); boot-predefines(dood); dood-free-address(dood) := untag(dood-read-at(dood, $dood-free-address-id)); dood-root(dood) := #f; // INITIAL VALUE FOR THOSE THAT CARE dood-root(dood) := read-object-at(dood, $dood-root-id); dood-proxies(dood) := as(, read-object-at(dood, $dood-proxies-id)); dood-world-register-dood(dood-world(dood), dood); dood end method; define class () constant slot dood-failed-dood :: , required-init-keyword: dood:; end class; define class () end class; define class () end class; define class () end class; define method make-dood-stream (#rest all-keys, #key locator, if-exists, direction) apply(make, , all-keys) end method; define method initialize (dood :: , #rest all-keys, #key name, locator, if-exists, stream, backups?, segments, #all-keys) next-method(); dood-init-keys(dood) := copy-sequence(all-keys); unless (segments) dood-segments(dood) := add(dood-segments(dood), dood-default-segment(dood)); end unless; for (segment in dood-segments(dood), i :: from 0) dood-segment-id(segment) := i; end for; when (locator) dood-locator(dood) := as(, locator); unless (name) dood-name(dood) := as(, as(, locator)); end unless; end when; dood-dood-state(dood-state(dood)) := dood; dood-dood-state(dood-backup(dood)) := dood; let replaceable? = /* stream | */ if-exists == #"replace"; if (stream) dood-stream(dood) := dood-open-stream(dood, locator: stream); dood-given-stream(dood) := stream; dood-backups?(dood) := #f; elseif (~(replaceable? & ~file-exists?(dood-locator(dood)))) // dont create file until after first commit dood-stream(dood) := dood-open-stream(dood); end if; if (replaceable? | ~dood-booted?(dood) | dood-corrupted?(dood) | dood-outdated?(dood) | dood-user-outdated?(dood)) unless (replaceable? | ~dood-booted?(dood)) case dood-outdated?(dood) => signal(make(, dood: dood, format-string: "DOOD %= OUTDATED VERSION %= EXPECTED %=", format-arguments: vector(dood-name(dood) | "", dood-version(dood), $dood-version))); dood-user-outdated?(dood) => signal(make(, dood: dood, format-string: "DOOD %= OUTDATED USER-VERSION %= EXPECTED %=", format-arguments: vector(dood-name(dood) | "", dood-user-version(dood), dood-specified-user-version(dood)))); dood-corrupted?(dood) => signal(make(, dood: dood, format-string: "DOOD %= CORRUPTED", format-arguments: vector(dood-name(dood) | ""))); end case; end unless; dood-boot(dood); else dood-flush(dood); end if; end method; define method dood-close (dood :: , #rest all-keys, #key abort?) => () // unless (abort?) // dood-commit(dood); // end unless; apply(dood-close-state-stream, dood, dood-state(dood), all-keys); dood-close-state-stream(dood, dood-backup(dood), abort?: #f); dood-flush-state(dood); dood-clean-proxies(dood); dood-world-unregister-dood(dood-world(dood), dood); end method; /// BOOKKEEPING define abstract open primary dood-class () // weak slot dood-pointer :: false-or(
) = #f, // // HACK: WONT WORK FOR NON-BATCH-MODE CAUSE IT WILL UNDO ATTACHMENT // // reinit-expression: #f, // init-keyword: pointer:; end dood-class; // define method dood-address // (dood :: , object :: ) // => (address :: false-or(
)) // let pointer = dood-pointer(object); // pointer & untag(pointer) // end method; // // define method dood-address-setter // (address ::
, dood :: , object :: ) // => (address ::
) // dood-pointer(object) := tag-as-address(address); // end method; // // define method dood-unregister-address // (dood :: , object :: ) // dood-pointer(object) := #f // end method; define abstract open primary dood-class () // keyword slot dood:; weak slot object-dood-state :: false-or() = #f, // HACK: WONT WORK FOR NON-BATCH-MODE CAUSE IT WILL UNDO ATTACHMENT // reinit-expression: #f, init-keyword: dood-state:; end dood-class; define method initialize (x :: , #key dood, #all-keys) next-method(); when (dood) object-dood-state(x) := dood-state(dood); end when; end method; define inline method object-dood (x :: ) => (res :: false-or()) let state = object-dood-state(x); state & dood-dood-state(state) end method; define inline method object-dood-setter (nv :: false-or(), x :: ) => (res :: false-or()) object-dood-state(x) := nv & dood-state(nv); nv end method; define method dood-register-object-dood (dood :: , object :: ) object-dood(object) := dood; end method; /* define method dood-unregister-object-dood (dood :: , object :: ) object-dood(object) := #f; end method; */ define open generic object-dood (object /*, #key world */) => (dood :: false-or()); /* define method dood-object-dood (dood :: , object) => (dood :: false-or()) object-dood(object /*, world: dood-world(dood)*/) end method; */ define method object-dood (object /*, #key world = dood-world-default() */) => (dood :: false-or()) #f // dood-world-object-dood(world, object) end method; define method dood-register-object-dood (dood :: , object) // dood-world-register-object-dood(dood-world(dood), dood, object) end method; /* define method dood-unregister-object-dood (dood :: , object) // dood-world-unregister-object-dood(dood-world(dood), dood, object); end method; */ // define constant $default-two-level-table-size = 10; // define inline function make-two-level-table // (#key size = $default-two-level-table-size) => (res :: ) // make(, size: size) // end function; define inline function first-level-table (weak-kind :: false-or(), tables :: , key) => (table :: ) element(tables, key, default: #f) | (element(tables, key) := make-weak-table(weak-kind)); end function; define inline function two-level-table-element (weak-kind :: false-or(), tables :: , key, first-key :: , second-key :: , default) => (value) let table :: = first-level-table(weak-kind, tables, first-key(key)); element(table, second-key(key), default: default) end function; define inline function two-level-table-element-setter (value, weak-kind :: false-or(), tables :: , key, first-key :: , second-key :: ) => (value) let table :: = first-level-table(weak-kind, tables, first-key(key)); element(table, second-key(key)) := value; end function; /* define inline function two-level-table-remove-key! (tables :: , key, first-key :: , second-key :: ) => () let key-1 = first-key(key); let table :: false-or() = element(tables, key-1, default: #f); if (table) remove-key!(table, second-key(key)); if (empty?(table)) remove-key!(tables, key-1); end if; end if; end function; */ define method dood-address (dood :: , object) => (address :: false-or(
)) // element(dood-addresses(dood), object, default: #f) two-level-table-element (#"key", dood-addresses(dood), object, object-class, identity, #f); end method; define method dood-address-setter (address ::
, dood :: , object) => (address ::
) // element(dood-addresses(dood), object) := address two-level-table-element (#"key", dood-addresses(dood), object, object-class, identity) := address; end method; define method dood-register-address (dood :: , object, address ::
) dood-address(dood, object) := address end method; /* define method dood-unregister-address (dood :: , object) // remove-key!(dood-addresses(dood), object) two-level-table-remove-key! (dood-addresses(dood), object, object-class, identity) end method; */ // define constant $dood-address-key-1-size // = 10; // define constant $dood-address-key-1-mask // = ash(1, $dood-address-key-1-size) - 1; // // define function dood-address-key-1 // (address ::
) => (key ::
) // ash(address, -$dood-address-key-1-size) // end function; // // define function dood-address-key-2 // (address ::
) => (key ::
) // logand(address, $dood-address-key-1-mask) // end function; define inline function dood-object (dood :: , address ::
, #rest optionals, #key default) => (object) apply(element, dood-objects(dood), address, optionals) // two-level-table-element // (#"value", dood-objects(dood), // address, dood-address-key-1, dood-address-key-2, default) end function; define inline function dood-object-setter (object, dood :: , address ::
) element(dood-objects(dood), address) := object // two-level-table-element // (#"value", dood-objects(dood), // address, dood-address-key-1, dood-address-key-2) // := object; end function; define function dood-register-object-maybe-read (dood :: , object, address ::
, read? :: ) if (read?) // used for wrapper proxies to avoid circularities let forwarding-address = dood-forwarding-address(dood); if (forwarding-address) // format-out("FORWARDING %= TO %= FOR %=\n", // address, forwarding-address, object-class(object)); dood-object(dood, forwarding-address) := object; dood-forwarding-address(dood) := #f; // ONE SHOT ONLY end if; end if; // unless (read? & dood-batch-mode?(dood)) dood-register-object-dood(dood, object); // COMMENT THIS OUT FOR FLUSHABILITY unless (read? & dood-batch-mode?(dood)) dood-register-address(dood, object, address); end unless; dood-object(dood, address) := object; dood-format("REGISTERING %= @ %d IN %s\n", object-class(object), address, dood-name(dood)); end function; define method dood-register-object (dood :: , object, address ::
) dood-register-object-maybe-read(dood, object, address, #f); end method; define method dood-register-read-object (dood :: , object, address ::
) dood-register-object-maybe-read(dood, object, address, #t); end method; /* define method dood-unregister-object (dood :: , object) if (dood == dood-object-dood(dood, object)) dood-unregister-object-dood(dood, object) end if; let address = dood-address(dood, object); remove-key!(dood-objects(dood), address); // two-level-table-remove-key! // (dood-objects(dood), address, dood-address-key-1, dood-address-key-2); // unless (dood-read-only?(dood)) dood-unregister-address(dood, object); // end unless; end method; */ define method dood-booted? (dood :: ) => (well? :: ) let well? = stream-size(dood-stream(dood)) > $dood-header-size; dood-format("BOOTED? %=\n", well?); well? end method; define method dood-corrupted? (dood :: ) => (res :: ) let well? = if (dood-booted?(dood)) untag(dood-read-at(dood, $dood-corruption-id)) ~== $dood-false-id else #f end if; dood-format("CORRUPTED? %=\n", well?); well? end method; define method dood-corrupted?-setter (x :: , dood :: ) => (res :: ) dood-write-at (dood, tag-as-address(x, if (x) $dood-true-id else $dood-false-id end), $dood-corruption-id); x end method; define method dood-version (dood :: ) => (res :: ) if (dood-booted?(dood)) untag(dood-read-at(dood, $dood-version-id)) else $dood-no-version end if end method; define method dood-outdated? (dood :: ) => (res :: ) let version = dood-version(dood); let well? = version ~= $dood-no-version // valid version & version ~= $dood-version; // but wrong dood-format("OUTDATED? %=\n", well?); well? end method; define method dood-user-version (dood :: ) => (res :: ) if (dood-booted?(dood)) let version = untag(dood-read-at(dood, $dood-user-version-id)); version else $dood-no-version end if end method; define method dood-user-outdated? (dood :: ) => (res :: ) let version = dood-user-version(dood); let well? = version ~= $dood-no-version // valid version & version ~= dood-specified-user-version(dood); // but wrong dood-format("USER OUTDATED? %=\n", well?); well? end method; /// FORWARD DECLARED FOR EMULATOR define class () constant slot walk-info-function :: = identity, init-keyword: function:; constant slot walk-info-flush? :: = #f, init-keyword: flush?:; constant slot walk-info-force? :: = #t, init-keyword: force?:; constant slot walk-info-parents? :: = #f, init-keyword: parents?:; constant slot walk-info-commit? :: = #f, init-keyword: commit?:; constant slot walk-info-batch? :: = #t, init-keyword: batch?:; end class; define sealed domain initialize (); define sealed domain make (subclass()); // eof