Module: environment-protocols Synopsis: Environment protocols Author: Andy Armstrong, Chris Page, Jason Trenouth 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 /// (environment-protocols) define constant = one-of( #"created", #"destroyed", #"stop?", #"message?", #"transient?", #"profile?", #"directions", #"enabled?", #"test" ); /// (environment-protocols) define constant = one-of(#"in", #"out"); /// INITIALIZATION CONSTANTS define constant $default-breakpoint-stop? = #t; define constant $default-breakpoint-message? = ""; define constant $default-breakpoint-transient? = #f; define constant $default-breakpoint-enabled? = #t; define constant $default-breakpoint-profile? = #f; define constant $default-breakpoint-test = #f; define constant $default-breakpoint-directions = vector(#"in", #"out"); define constant $default-breakpoint-entry-function? = #f; /// (environment-protocols) define abstract class () constant slot breakpoint-project :: , required-init-keyword: project:; slot breakpoint-library :: false-or() = #f, init-keyword: library:; slot breakpoint-object :: , init-keyword: object:; slot breakpoint-stop? :: = $default-breakpoint-stop?, setter: %breakpoint-stop?-setter, init-keyword: stop?:; slot breakpoint-message? :: false-or() = $default-breakpoint-message?, setter: %breakpoint-message?-setter, init-keyword: message?:; slot breakpoint-transient? :: = $default-breakpoint-transient?, setter: %breakpoint-transient?-setter, init-keyword: transient?:; slot breakpoint-enabled? :: = $default-breakpoint-enabled?, setter: %breakpoint-enabled?-setter, init-keyword: enabled?:; slot breakpoint-profile? :: = $default-breakpoint-profile?, setter: %breakpoint-profile?-setter, init-keyword: profile?:; slot breakpoint-test = $default-breakpoint-test, setter: %breakpoint-test-setter, init-keyword: test:; end class ; /// (environment-protocols) define abstract class () end class ; define method environment-object-library (server :: , breakpoint :: ) => (library :: false-or()) breakpoint.breakpoint-library | begin let object = breakpoint.breakpoint-object; //---*** andrewa: why wouldn't this always be ? if (instance?(object, )) let library = environment-object-library(server, object); breakpoint.breakpoint-library := library else debug-message("Breakpoint %= has unexpected object %=", breakpoint, object) end end end method environment-object-library; /// (environment-protocols) define abstract class () slot breakpoint-directions :: = $default-breakpoint-directions, setter: %breakpoint-directions-setter, init-keyword: directions:; slot breakpoint-entry-function? :: = $default-breakpoint-entry-function?, init-keyword: entry-function?:; slot breakpoint-entry-point? :: = #f, init-keyword: entry-point?:; end class ; define class () end class ; define class () end class ; define class () end class ; define class () end class ; /// (environment-protocols) define class () end class ; define method environment-object-library (server :: , breakpoint :: ) => (library :: false-or()) breakpoint.breakpoint-library | begin let project = server.server-project; let location = breakpoint.breakpoint-object; let record = location.source-location-source-record; let library = find-source-record-library(project, record); breakpoint.breakpoint-library := library end end method environment-object-library; /// SOURCE-LOCATION-BREAKPOINTS (environment-protocols) define open generic source-location-breakpoints (project :: ) => (breakpoints :: ); /// ENVIRONMENT-OBJECT-BREAKPOINTS (environment-protocols) define open generic environment-object-breakpoints (project :: ) => (breakpoints :: ); /// PROJECT-BREAKPOINTS (environment-protocols) define open generic project-breakpoints (project :: ) => (breakpoints :: ); define method project-breakpoints (project :: ) => (breakpoints :: ) concatenate-as(, as(, project.source-location-breakpoints), as(, project.environment-object-breakpoints)); end method project-breakpoints; /// MAKE (dylan) define method make (class == , #rest args, #key object) => (breakpoint :: ) let class = class-for-breakpoint(object); assert(class, "Attempting to make breakpoint for unrecognized object %=", object); apply(make, class, args); end method make; define method make (class :: subclass(), #rest args, #key) => (breakpoint :: ) let breakpoint = apply(find-breakpoint, class, args); if (breakpoint) apply(reinitialize-breakpoint, breakpoint, args); breakpoint; else next-method(); end if; end method make; /// CLASS-FOR-BREAKPOINT define generic class-for-breakpoint (object :: ) => (class :: false-or()); //---*** was this, but the compiler can't deal with it! // => (class :: false-or(subclass())); define method class-for-breakpoint (object :: ) => (class :: singleton(#f)) #f end method class-for-breakpoint; define method class-for-breakpoint (object :: ) => (class :: subclass()) end method class-for-breakpoint; define method class-for-breakpoint (object :: ) => (class :: subclass()) end method class-for-breakpoint; define method class-for-breakpoint (object :: ) => (class :: subclass()) end method class-for-breakpoint; define method class-for-breakpoint (object :: ) => (class :: subclass()) end method class-for-breakpoint; define method class-for-breakpoint (object :: ) => (class :: subclass()) end method class-for-breakpoint; /// ENSURE-BREAKPOINT-FOR-ALL-METHODS define function ensure-breakpoint-for-all-methods (breakpoint :: ) => () let project = breakpoint.breakpoint-project; let object = breakpoint.breakpoint-object; let enabled? = breakpoint.breakpoint-enabled?; let transient? = breakpoint.breakpoint-transient?; let message? = breakpoint.breakpoint-message?; let profile? = breakpoint.breakpoint-profile?; let stop? = breakpoint.breakpoint-stop?; let test = breakpoint.breakpoint-test; let directions = breakpoint.breakpoint-directions; let entry-function? = breakpoint.breakpoint-entry-function?; let entry-point? = breakpoint.breakpoint-entry-point?; for (function in generic-function-object-methods(project, object)) make(, project: project, object: function, enabled?: enabled?, transient?: transient?, message?: message?, profile?: profile?, stop?: stop?, test: test, directions: directions, entry-function?: entry-function?, entry-point?: entry-point?) end end function; /// INITIALIZE (dylan) define method initialize (breakpoint :: , #rest args, #key) => () next-method(); apply(initialize-breakpoint, breakpoint, args); note-breakpoint-state-changed(breakpoint, #"created") end method initialize; /// INITIALIZE-BREAKPOINT (environment-protocol) define open generic initialize-breakpoint (breakpoint :: , #key, #all-keys) => (); define method initialize-breakpoint (breakpoint :: , #key) => () end method initialize-breakpoint; define method initialize-breakpoint (breakpoint :: , #key object) => () next-method(); let breakpoints = breakpoint.breakpoint-project.source-location-breakpoints; element(breakpoints, object) := breakpoint end method initialize-breakpoint; define method initialize-breakpoint (breakpoint :: , #key object) => () next-method(); let breakpoints = breakpoint.breakpoint-project.environment-object-breakpoints; element(breakpoints, object) := breakpoint end method initialize-breakpoint; define method initialize-breakpoint (breakpoint :: , #key) => () next-method(); ensure-breakpoint-for-all-methods(breakpoint); end method initialize-breakpoint; /// FIND-BREAKPOINT (environment-protocol) define open generic find-breakpoint (class :: subclass(), #rest args, #key, #all-keys) => (breakpoint :: false-or()); define method find-breakpoint (class == , #rest args, #key project, object) => (breakpoint :: false-or()); let class = class-for-breakpoint(object); class & apply(find-breakpoint, class, args) end method find-breakpoint; define method find-breakpoint (class :: subclass(), #rest args, #key project, object) => (breakpoint :: false-or()) element(project.environment-object-breakpoints, object, default: #f) end method find-breakpoint; define method find-breakpoint (class :: subclass(), #rest args, #key project, object) => (breakpoint :: false-or()) element(project.source-location-breakpoints, object, default: #f) end method find-breakpoint; /// REINITIALIZE-BREAKPOINT (environment-protocol) define open generic reinitialize-breakpoint (breakpoint :: , #rest args, #key, #all-keys) => (); define method reinitialize-breakpoint (breakpoint :: , #rest args, #key enabled? = unsupplied(), message? = unsupplied(), stop? = unsupplied(), transient? = unsupplied(), profile? = unsupplied(), test = unsupplied()) => () if (supplied?(enabled?)) breakpoint.breakpoint-enabled? := enabled?; end if; if (supplied?(transient?)) breakpoint.breakpoint-transient? := transient?; end if; if (supplied?(profile?)) breakpoint.breakpoint-profile? := profile?; end if; if (supplied?(message?)) breakpoint.breakpoint-message? := message?; end if; if (supplied?(stop?)) breakpoint.breakpoint-stop? := stop?; end if; if (supplied?(test)) breakpoint.breakpoint-test := test; end if; end method reinitialize-breakpoint; define method reinitialize-breakpoint (breakpoint :: , #rest args, #key directions = unsupplied(), entry-function? = unsupplied(), entry-point? = unsupplied()) => () next-method(); if (supplied?(directions)) breakpoint.breakpoint-directions := directions; end if; if (supplied?(entry-function?)) breakpoint.breakpoint-entry-function? := entry-function?; end if; if (supplied?(entry-point?)) breakpoint.breakpoint-entry-point? := entry-point?; end if; end method reinitialize-breakpoint; define method reinitialize-breakpoint (breakpoint :: , #rest args, #key) => () next-method(); ensure-breakpoint-for-all-methods(breakpoint); end method reinitialize-breakpoint; /// DESTROY-BREAKPOINT (environment-protocol) define open generic destroy-breakpoint (breakpoint :: ) => (); define method destroy-breakpoint (breakpoint :: ) => () note-breakpoint-state-changed(breakpoint, #"destroyed") end method destroy-breakpoint; define method destroy-breakpoint (breakpoint :: ) => () let breakpoints = breakpoint.breakpoint-project.source-location-breakpoints; remove-key!(breakpoints, breakpoint.breakpoint-object); next-method() end method destroy-breakpoint; define method destroy-breakpoint (breakpoint :: ) => () let breakpoints = breakpoint.breakpoint-project.environment-object-breakpoints; remove-key!(breakpoints, breakpoint.breakpoint-object); next-method() end method destroy-breakpoint; define method destroy-breakpoint (breakpoint :: ) => () next-method(); do-generic-breakpoint-methods(destroy-breakpoint, breakpoint) end method destroy-breakpoint; define method destroy-breakpoint (breakpoint :: ) => () let project = breakpoint.breakpoint-project; ///// BREAKPOINT-FOR-METHOD (Local convenience function) // Given a , returns a if // there is a breakpoint on the method, otherwise returns #f. // The implementation is clearly trivial, but having this improves // the clarity of some other code in this function. local method breakpoint-for-method (m :: ) => (maybe-bp :: false-or()) find-breakpoint(, project: project, object: m) end method breakpoint-for-method; ///// ZERO-BREAKPOINTED-METHODS? (Local convenience function). // Given a , returns #f if one or more of // its methods has an associated breakpoint, otherwise returns #t. local method zero-breakpointed-methods? (gf :: ) => (well? :: ) let methods = generic-function-object-methods(project, gf); ~any?(breakpoint-for-method, methods); end method zero-breakpointed-methods?; ///// MAYBE-GARBAGE-COLLECT-GF-BREAKPOINT (Local convenience function). // Takes a . If a breakpoint exists for the // generic function, but none of its methods have associated // breakpoints, removes the breakpoint on the generic. local method maybe-garbage-collect-gf-breakpoint (gf :: ) => () let gf-breakpoint = find-breakpoint(, project: project, object: gf); if (gf-breakpoint & zero-breakpointed-methods?(gf)) //---*** phoward. Jason noted that this call to DESTROY-BREAKPOINT // will try to destroy all subordinate method breakpoints. But, // we've obviously just determined that there aren't any, meaning // that DESTROY-BREAKPOINT here is going to do some redundant and // fruitless work. We should try to abstract out a more primitive // function for destroying a GF breakpoint. destroy-breakpoint(gf-breakpoint); end if; end method maybe-garbage-collect-gf-breakpoint; next-method(); let method-object = breakpoint.breakpoint-object; // The breakpoint might be on an object that no longer exists, so // be careful not to do any unnecessary queries on it. if (environment-object-exists?(project, method-object)) let gf-object = method-generic-function(project, method-object); if (gf-object) // It maybe the case that we have just destroyed the last surviving // method breakpoint for a breakpointed generic function. If this is // the case, we can destroy the generic function breakpoint itself. maybe-garbage-collect-gf-breakpoint(gf-object); end if; end if; end method destroy-breakpoint; /// BREAKPOINT GETTERS (environment-protocol) define open generic breakpoint-project (breakpoint :: ) => (project :: ); define open generic breakpoint-object (breakpoint :: ) => (object :: ); define open generic breakpoint-object-setter (object :: , breakpoint :: ) => (object :: ); define open generic breakpoint-stop? (breakpoint :: ) => (stop? :: ); define open generic breakpoint-message? (breakpoint :: ) => (message? :: false-or()); define open generic breakpoint-transient? (breakpoint :: ) => (transient? :: ); define open generic breakpoint-profile? (breakpoint :: ) => (profile? :: ); define open generic breakpoint-enabled? (breakpoint :: ) => (enabled? :: ); define open generic breakpoint-directions (breakpoint :: ) => (directions :: ); define open generic breakpoint-test (breakpoint :: ) => (test); define open generic breakpoint-entry-function? (breakpoint :: ) => (entry-function? :: ); /// BREAKPOINT SETTERS (environment-protocol) define open generic breakpoint-stop?-setter (stop? :: , breakpoint :: ) => (stop? :: ); define open generic breakpoint-message?-setter (message? :: false-or(), breakpoint :: ) => (message? :: false-or()); define open generic breakpoint-transient?-setter (transient? :: , breakpoint :: ) => (transient? :: ); define open generic breakpoint-profile?-setter (profile? :: , breakpoint :: ) => (profile? :: ); define open generic breakpoint-enabled?-setter (enabled? :: , breakpoint :: ) => (enabled? :: ); define open generic breakpoint-directions-setter (directions :: , breakpoint :: ) => (directions :: ); define open generic breakpoint-test-setter (test, breakpoint :: ) => (test); define open generic breakpoint-entry-function?-setter (entry-function? :: , breakpoint :: ) => (entry-function? :: ); define open generic breakpoint-entry-point?-setter (entry-point? :: , breakpoint :: ) => (entry-point? :: ); define method breakpoint-stop?-setter (stop? :: , breakpoint :: ) => (stop? :: ) breakpoint.%breakpoint-stop? := stop?; note-breakpoint-state-changed(breakpoint, #"stop?"); stop? end method breakpoint-stop?-setter; define method breakpoint-stop?-setter (stop? :: , breakpoint :: ) => (stop? :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-stop?-setter, stop?), breakpoint); stop? end method breakpoint-stop?-setter; define method breakpoint-message?-setter (message? :: false-or(), breakpoint :: ) => (message? :: false-or()) breakpoint.%breakpoint-message? := message?; note-breakpoint-state-changed(breakpoint, #"message?"); message? end method breakpoint-message?-setter; define method breakpoint-message?-setter (message? :: false-or(), breakpoint :: ) => (message? :: false-or()) next-method(); do-generic-breakpoint-methods(curry(breakpoint-message?-setter, message?), breakpoint); message? end method breakpoint-message?-setter; define method breakpoint-transient?-setter (transient? :: , breakpoint :: ) => (transient? :: ) breakpoint.%breakpoint-transient? := transient?; note-breakpoint-state-changed(breakpoint, #"transient?"); transient? end method breakpoint-transient?-setter; define method breakpoint-transient?-setter (transient? :: , breakpoint :: ) => (transient? :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-transient?-setter, transient?), breakpoint); transient? end method breakpoint-transient?-setter; define method breakpoint-profile?-setter (profile? :: , breakpoint :: ) => (profile? :: ) breakpoint.%breakpoint-profile? := profile?; note-breakpoint-state-changed(breakpoint, #"profile?"); profile? end method breakpoint-profile?-setter; define method breakpoint-profile?-setter (profile? :: , breakpoint :: ) => (profile? :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-profile?-setter, profile?), breakpoint); profile? end method breakpoint-profile?-setter; define method breakpoint-enabled?-setter (enabled? :: , breakpoint :: ) => (enabled? :: ) breakpoint.%breakpoint-enabled? := enabled?; note-breakpoint-state-changed(breakpoint, #"enabled?"); enabled? end method breakpoint-enabled?-setter; define method breakpoint-enabled?-setter (enabled? :: , breakpoint :: ) => (enabled? :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-enabled?-setter, enabled?), breakpoint); enabled? end method breakpoint-enabled?-setter; define method breakpoint-directions-setter (directions :: , breakpoint :: ) => (directions :: ) breakpoint.%breakpoint-directions := directions; note-breakpoint-state-changed(breakpoint, #"directions"); directions end method breakpoint-directions-setter; define method breakpoint-directions-setter (directions :: , breakpoint :: ) => (directions :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-directions-setter, directions), breakpoint); directions end method breakpoint-directions-setter; define method breakpoint-test-setter (test, breakpoint :: ) => (test) breakpoint.%breakpoint-test := test; note-breakpoint-state-changed(breakpoint, #"test"); test end method breakpoint-test-setter; define method breakpoint-test-setter (test, breakpoint :: ) => (test) next-method(); do-generic-breakpoint-methods(curry(breakpoint-test-setter, test), breakpoint); test end method breakpoint-test-setter; define method breakpoint-entry-function?-setter (entry-function? :: , breakpoint :: ) => (entry-function? :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-entry-function?-setter, entry-function?), breakpoint); entry-function? end method breakpoint-entry-function?-setter; define method breakpoint-entry-point?-setter (entry-point? :: , breakpoint :: ) => (entry-point? :: ) next-method(); do-generic-breakpoint-methods(curry(breakpoint-entry-point?-setter, entry-point?), breakpoint); entry-point? end method breakpoint-entry-point?-setter; /// NOTE-BREAKPOINT-STATE-CHANGED (environment-protocol) define open generic note-breakpoint-state-changed (breakpoint :: , state :: ) => (); //---*** andrewa: temporary solution to solve the problem that //---*** breakpoint objects don't always have a proxy. Ultimately //---*** we should use the id protocol to create breakpoints that //---*** aren't connected to a particular server. define method choose-server (project :: , breakpoint :: , #key error?, default-server) => (application :: false-or()) ignore(default-server); project-application(project) | if (error?) closed-server-error(breakpoint) end end method choose-server; define method note-breakpoint-state-changed (breakpoint :: , state :: ) => () let project :: = breakpoint.breakpoint-project; server-note-breakpoint-state-changed(project, breakpoint, state); let server = choose-server(project, breakpoint); if (server) server-note-breakpoint-state-changed(server, breakpoint, state); end if; /* // phoward alternative implementation that might fix bug 2030 for (project in open-projects()) let application = project.project-application; let proxy = project.project-proxy; server-note-breakpoint-state-changed(project, breakpoint, state); if (application) server-note-breakpoint-state-changed(application, breakpoint, state, use-project-proxy: proxy); end if end for */ end method note-breakpoint-state-changed; /// WITH-COMPRESSED-BREAKPOINT-STATE-CHANGES (environment-protocols) /// These three thread variables are "(internal)". define thread variable *compress-breakpoint-state-changes* :: = #f; define thread variable *last-breakpoint-state-change-state* :: false-or() = #f; define thread variable *last-breakpoint-state-change-project* :: false-or() = #f; define inline function do-with-compressed-breakpoint-state-changes (continuation :: ) => () dynamic-bind (*compress-breakpoint-state-changes* = #t) continuation(); end; if (*last-breakpoint-state-change-project* & *last-breakpoint-state-change-state*) broadcast($project-channel, make(, project: *last-breakpoint-state-change-project*, state: *last-breakpoint-state-change-state*)); end if; end function do-with-compressed-breakpoint-state-changes; define macro with-compressed-breakpoint-state-changes { with-compressed-breakpoint-state-changes () ?body:body end } => { do-with-compressed-breakpoint-state-changes(method () ?body end) } end macro; /// NOTE-BREAKPOINT-STATE-CHANGES-FAILED (environment-protocols) define open generic note-breakpoint-state-changes-failed (server :: , breakpoints :: , state :: ) => (); define method note-breakpoint-state-changes-failed (project :: , breakpoints :: , state :: ) => () broadcast($project-channel, make(, project: project, breakpoints: breakpoints, state: state)); end method note-breakpoint-state-changes-failed; /// SERVER-NOTE-BREAKPOINT-STATE-CHANGED (environment-protocols) define open generic server-note-breakpoint-state-changed (server :: , breakpoint :: , state :: , #key use-project-proxy) => (); define method server-note-breakpoint-state-changed (project :: , breakpoint :: , state :: , #key use-project-proxy = #f) => () if (*compress-breakpoint-state-changes*) *last-breakpoint-state-change-project* := project; *last-breakpoint-state-change-state* := state; else broadcast($project-channel, make(, project: project, breakpoint: breakpoint, state: state)); end if; end method server-note-breakpoint-state-changed; /// DO-GENERIC-BREAKPOINT-METHODS (environment-protocol) /// /// NB Only changes the state of those breakpoints that still exist. /// Does not make any new breakpoints to fill in the gaps made by the /// user explicitly destroying them (or by hits on transients). define open generic do-generic-breakpoint-methods (operation :: , breakpoint :: ) => (); define method do-generic-breakpoint-methods (operation :: , breakpoint :: ) => () let project = breakpoint.breakpoint-project; let function = breakpoint.breakpoint-object; for (m in generic-function-object-methods(project, function)) let breakpoint = element(project.environment-object-breakpoints, m, default: #f); if (breakpoint) operation(breakpoint); end if; end for; end method do-generic-breakpoint-methods; /// ENVIRONMENT-OBJECT-SOURCE-LOCATION{-SETTER} define method environment-object-source-location (project :: , breakpoint :: ) => (location :: ) breakpoint-object(breakpoint) end method environment-object-source-location; define method environment-object-source-location (project :: , breakpoint :: ) => (location :: ) environment-object-source-location(project, breakpoint-object(breakpoint)) end method environment-object-source-location; /// Tracing define function trace-function (project :: , function :: ) => (breakpoint :: ) make(, project: project, object: function, stop?: #f, entry-point?: #t) end function trace-function;