(* ::Package:: *) (*......................................................................*) (* :Title: PackageSymbolsDependencies *) (* :Author: Leonid B.Shifrin *) (* :Summary: This package is intended to be an inspection/debugging tool for programs involving a (possibly large) number of (possibly inter-related) packages. It allows to track which symbols of a given package/context depend on symbols in other packages/contexts (in the sense that the symbol's global definitions/properties reference other context's symbols). In addition to debugging purposes, the package can be used to monitor modifications introduced by some package to global properties/definitions of symbols in other contexts/ packages, including possible overloading of System symbols. Finally, one can use it to monitor shadowing and test symbols for being shadowed. *) (* :Context: PackageSymbolsDependencies` *) (* :Package version: 1.0 *) (* :Copyright: Copyright 2009, Leonid B.Shifrin. Permission is granted to distribute verbatim copies of this package together with any of your packages that use it, provided the following acknowledgement is printed in a standard place: "PackageOptionChecks.m is distributed with permission by Leonid B. Shifrin." *) (* :History: Version 1.0 January 2009 *) (* :Keywords: package development, debugging, dependencies, symbols, shadowing, contexts *) (* :Mathematica version: 5.1 *) (* :Discussion: The idea behind the package is simple: just deconstruct to symbols all of the global definitions of all symbols in the specified set of contexts, and see if the set of all these symbols contains one or more of those dependence on which is investigated. It is possible to answer this question for a specific global property (DownValues for example).Implementation is mostly straighforward, subtle points being the treatment of Locked and ReadProtected symbols and avoiding the creation of entries in the symbol table for symbols whose creation was delayed from parse-time to run-time by using Symbol[stringname] construct. Another subtlety was related to analyzing Options[symbol], since Options does evaluate its argument and thus an extra Unevaluated has to be inserted. Finally, for version 6, there is one particular symbol $FrontEndSession, for which (after removing Read-protection), I could not read options without an error message from ToExpression, which I had to switch off/on manually. Option checks are induced through package when it is available. The use of Module-s is avoided throughout the code in favor of nested With-s. *) BeginPackage["PackageSymbolsDependencies`","PackageOptionChecks`"]; (********************************************************************) (* Usage messages *) (********************************************************************) AllContexts::usage = "AllContexts[s_Symbol] gives a list of all contexts present \ in $Packages, in which the symbol is present. If there is more \ than one context in the resulting list, then the symbol is \ shadowed"; ShadowedQ::usage = "ShadowedQ[s_Symbol] tests whether a given symbol is shadowed \ in the present environment"; SearchedContexts::usage = "SearchedContexts[opts___?OptionQ] give a list of all \ contexts that are consired \" Non-system\", or, user-defined \ (or, possibly, user-modified) - with contexts \"System`\" and \ \"Global\" included by default. Options can be used to change \ default settings"; SymbolsContexts::usage = "SymbolsContexts[expr_] produces a list of contexts of all symbols \ used in building an expression. Heads are taken into consideration"; DependentSymbolsInDefinitions::usage = "DependentSymbolsInDefinitions[property_Symbol,context_String, symbols:{___String}] \ produces a list of sublists containing dependent symbols together with \ their contexts. The symbol is dependent wrt the property if \ the code for property[symbol] contains symbols in the context "; DependentSymbols::usage = "DependentSymbols[context_String,opts___?OptionQ] produces a list \ of all dependent functions (or, generally, symbols) wrt the context \ . The set of properties considered (DownValues, OwnValues \ etc) can be specified by the option. The exact set of \ contexts included in analysis can be regulated by \ option and monitored by function. There is \ also an option , which can be set to or \ . If it is set to , then only contexts given by \ will be checked. Setting it to will \ cause all contexts currently in to be checked. This may \ be a rather time-consuming operation and also (in current \ implementation) it expands the total namespace after it has finished, \ possibly making other operations slow. It is best to keep this option \ set to whenever possible. If the option is \ set to True, all symbols in contexts regulated by \ will be checked. Setting it to False will cause the function not to \ check any complete contexts. However, regardless of the value of this \ option, one can enforce checks of specific symbols by providing their \ list through the SymbolsToCheck option"; DependentSymbolsExistQ::usage = "DependentSymbolsExistQ[context_String,opts___?OptionQ] checks \ whether functions (symbols) dependent on some symbols of the context \ do exist. The range of symbols checked is defined in the \ same way as for . The former function is less \ specific and is optimized so it may perform faster than \ "; ExcludeContexts::usage = "ExcludeContexts is an option for SearchedContexts, \ DependentSymbols,DependentSymbolsExistQ functions. Should \ be set to a list of contexts that have to be excluded from \ search in dependency check."; ExcludeSystem::usage = "ExcludeSystem is an option for SearchedContexts, \ DependentSymbols,DependentSymbolsExistQ functions. \ It is useful to set to True when one knows for sure that \ system functions were not modified, since this speeds up \ the analysis"; DependencyCheck::usage = "DependencyCheck is an option for DependentSymbols and \ DependentSymbolsExistQ functions. Possible settings: \ DependencyCheck-> Normal and DependencyCheck ->Full. In \ the former case, search in dependency check is performed \ over a set of contexts given by SearchedContexts[] \ function. In the latter case, all symbols currently known \ to the system, are checked."; PropertiesToCheck::usage = "PropertiesToCheck is an option for DependentSymbols and \ DependentSymbolsExistQ functions. Should be set to a \ list of global properties to be checked for dependencies. \ These include DownValues,UpValues,OwnValues,SubValues, \ FormatValues,NValues,Options,DefaultValues."; SymbolsToCheck::usage = "SymbolsToCheck is an option for DependentSymbols and \ DependentSymbolsExistQ functions"; CheckContexts::usage = "CheckContexts is an option for DependentSymbols and \ DependentSymbolsExistQ functions. It is used to check \ dependencies of specific symbols, list of which should \ be the r.h.s. of the rule in this option."; ContextsOnly::usage = "ContextsOnly is an option for SymbolsContexts. Setting \ ContextsOnly-> True will cause SymbolsContexts to return \ only a set of contexts symbols from which are used in \ building an input expression. Setting ContextsOnly-> False \ will result in SymbolsContexts returning a list of all \ these symbols (wrapped in Hold), with their contexts"; SymbolsOnly::usage = "SymbolsOnly is an option for DependentSymbols. When set to \ True, will cause DependentSymbols to return only dependent \ symbols, without their contexts"; IncludeOnlyContexts::usage = "IncludeOnlyContexts is an option for SearchedContexts, \ DependentSymbols,DependentSymbolsExistQ functions. When set \ to a non-empty list of contexts, forces search in the \ dependency check to go only over the contexts from this list. \ In this case, all other related options (such as ExcludeContexts) \ are overridden."; If[ValueQ[PackageOptionChecksAvailableQ[]], PackageSymbolsDependenciesOptionChecks::usage = "This symbol is idle and is in the package just to remind that \ an additional option-checking functionality is loaded and can \ be controlled by functions from package. \ To display their names, type ?PackageOptionChecks`*"; ]; (********************************************************************) (* Error messages *) (********************************************************************) AllContexts::badarg = "An argument is supposed to be a Symbol"; AllContexts::argnum = "The function was called with `1` arguments. Exactly 1 \ argument was expected"; DependentSymbols::badarg = "The argument is supposed to be a String (context name), plus \ possibly options"; DependentSymbols::argnum = "Exactly 1 non-optional argument was expected"; DependentSymbolsExistQ::badarg = "The argument is supposed to be a String (context name), plus \ possibly options"; DependentSymbolsExistQ::argnum = "Exactly 1 non-optional argument was expected"; DependentSymbolsInDefinitions::badarg = "The arguments must match the pattern \ DependentSymbolsInDefinitions[property_Symbol,context_String,\ symbols:{___String}], where can be only one of the \ global properties associated with the symbol, such as UpValues,\ DownValues etc."; DependentSymbolsInDefinitions::argnum = "The function was called with `1` argument(s). Exactly \ 3 arguments were expected"; SearchedContexts::argnum = "The function was called with some non-optional arguments. Only \ optional arguments or no arguments were expected"; ShadowedQ::badarg = "An argument is supposed to be a Symbol"; ShadowedQ::argnum = "The function was called with `1` arguments. Exactly 1 \ argument was expected"; SymbolsContexts::argnum = "Exactly 1 non-optional argument was expected"; (********************************************************************) (********************************************************************) (********** Implementation - Private part ************) (********************************************************************) (********************************************************************) Begin["`Private`"]; globalProperties[]= { DownValues, UpValues, OwnValues, SubValues, FormatValues, NValues, Options, DefaultValues }; End[]; Options[SearchedContexts] = { ExcludeContexts -> (* Rather ad hoc *) {"ResourceLocator`","DocumentationSearch`", "Security`","XMLSchema`","WebServices`","JLink`", "PacletManager`","System`"}, ExcludeSystem -> False, IncludeOnlyContexts -> {} }; Options[DependentSymbols]= { DependencyCheck-> Normal, PropertiesToCheck-> `Private`globalProperties[], SymbolsToCheck-> None, CheckContexts-> True, SymbolsOnly -> True }; Options[SymbolsContexts] = {ContextsOnly-> False}; (****************************************************************) (***************** Option-checking block ********************) (* NOTE: This block requires the "PackageOptionChecks`" package *) (****************************************************************) With[{globalProps = `Private`globalProperties[]}, If[ValueQ[PackageOptionChecksAvailableQ[]], SetOptionsInfo["PackageSymbolsDependencies`",#]]&@ {{ExcludeContexts, {___String}, {SearchedContexts:> $Failed, DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {ExcludeSystem, True|False, {SearchedContexts:> $Failed, DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {IncludeOnlyContexts, {___String}, {SearchedContexts:> $Failed, DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {DependencyCheck, Normal|Full, {DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {PropertiesToCheck, _List?(Complement[#,globalProps]==={}&), {DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {SymbolsToCheck, {___Symbol}|None, {DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {CheckContexts, True|False, {DependentSymbols:> $Failed, DependentSymbolsExistQ:> $Failed}}, {SymbolsOnly, True|False, DependentSymbols:> $Failed}, {ContextsOnly, True|False, SymbolsContexts:> $Failed}};]; Begin["`Private`"]; (********************************************************************) (* Auxilliary functions *) (********************************************************************) (* This function borrowed from Roman Maeder's package *) FilterOptions[ command_Symbol, options___ ] := FilterOptions[ First /@ Options[command], options ] FilterOptions[ opts_List, options___ ] := Sequence @@ Select[ Flatten[{options}], MemberQ[opts, First[#]]& ] JoinHeld[a___Hold]:= Hold@@Replace[Hold[a],Hold[x___]:>Sequence[x],{1}]; UseOption[optname_Symbol,function_,opts___?OptionQ]:= optname/.Flatten[{opts}]/.Options[function]; Attributes[WithCodeAfter]={HoldRest}; WithCodeAfter[expr_,code_]:=(code;expr); (****************************************************************) (************************** Main code **************************) (****************************************************************) keepNameOnly[fullname_String]:= StringReplace[fullname,LongestMatch[__~~"`"]~~x__ :> x ]; (* ** Returns a list of ...Values (DownValues or UpValues or...), ** for a list of symbols (in their String form ). Ignores Locked ** symbols, and temporarily removes ReadProtected attribute for ** those symbols that have it, to read their global properties. ** I use many nested With statements instead of Module since ** I prefer to maintain referential transparency when I can. *) symbolsPropValues[prop:Alternatives@@globalProperties[], symbols:{___String}]:= Module[{lockedQ,specialQ}, lockedQ[_]=False; specialQ[_]=False; With[{specialSymbolsAndMessages = { "$FrontEndSession":> ToExpression::"notstrbox", "DynamicDump`toggleControlMakeBoxes":>Optional::opdef }, newprop = If[!MemberQ[Attributes[prop],HoldAll|HoldFirst|HoldRest], Function[x,prop[Unevaluated[x]],HoldAll], prop] }, With[{ specialSymbols = Part[specialSymbolsAndMessages,All,1], lockedOrReadprotected = Select[symbols, MemberQ[ToExpression[#,InputForm,Attributes], Locked|ReadProtected]&]}, With[{readProtected = Select[lockedOrReadprotected, !MemberQ[ToExpression[#,InputForm,Attributes], Locked]&] }, Map[(specialQ[Evaluate[#]]=True)&,specialSymbols]; With[{locked = Complement[lockedOrReadprotected,readProtected] }, Map[(lockedQ[Evaluate[#]]=True)&,locked]; With[{ accountForLockedAndSpecialF= Which[ lockedQ[#], {}, specialQ[#], WithCodeAfter[ First[Hold[Off[#]]/.specialSymbolsAndMessages]; ToExpression[#,InputForm,newprop], First[Hold[On[#]]/.specialSymbolsAndMessages];], True, ToExpression[#,InputForm,newprop] ]&, heldArgs = JoinHeld[ Replace[ JoinHeld@@Map[ ToExpression[#,InputForm,Hold]&, readProtected], Hold[held___]:>Hold[{held}], {0}], Hold[ReadProtected]] }, ClearAttributes@@heldArgs; With[{result = accountForLockedAndSpecialF/@symbols}, SetAttributes@@heldArgs; Remove[lockedQ,specialQ]; result ]]]]]]]; (* ** Generalizes the "short-circuit" behavior of the standard Or ** to cases when results are obtained by applying some test function ** to a list of (possibly unevaluated) arguments. Note that, in order ** to test unevaluated arguments (if this is desirable), itself ** should also be HoldFirst or HoldAll *) Attributes[FastOr]={HoldRest}; FastOr[test_,args___]:= TrueQ[Scan[ Function[arg,If[test[arg],Return[True]],HoldAll], Hold[args]]]; (* ** Gives true if either coincides with , or is ** a sub-context of *) SubContextQ[cont_String,context_String]:= StringMatchQ[cont,context~~___]; (* ** Tests if a list of contexts contains a context ** or any of its sub-contexts *) ContextMemberQ[contexts:{__String},current_String]:= FastOr[SubContextQ[#,current]&, Evaluate[Sequence@@contexts]]; testingFunction[property:Alternatives@@globalProperties[], context_String,symbols:{___String}]:= If[DependentSymbolsInDefinitions[ property,context,symbols]=!={}, Throw[True], False]; DependentSymbolsAux[context_String,opts___?OptionQ]:= With[{checkLevel = UseOption[DependencyCheck,DependentSymbols,opts], toCheck = UseOption[PropertiesToCheck,DependentSymbols,opts], specificNames = If[#===None,{},#]&@ UseOption[SymbolsToCheck,DependentSymbols,opts], contextsCheckQ = UseOption[CheckContexts,DependentSymbols,opts]}, With[{cnames = If[TrueQ[contextsCheckQ], Switch[checkLevel, Normal, SearchedContexts[ FilterOptions[SearchedContexts,opts]], Full, Contexts[] ], (* else *) {}] }, With[{allSymbolNames = Join[ToString/@specificNames, Flatten[ Map[Names[#<>"*"]&, DeleteCases[cnames, x_/;SubContextQ[x,context]]]]] }, {toCheck,allSymbolNames}]]]; (********************************************************************) (********************************************************************) (***************** Public interface ******************) (********************************************************************) (********************************************************************) (* Note: searches only $Packages *) AllContexts[s_Symbol]:= Cases[ Map[{MemberQ[keepNameOnly/@Names[#<>"*"],ToString[s]], #}&, $Packages], {True,x_}:>x]; (* Error cases *) AllContexts[_]:= "never happens"/;Message[AllContexts::badarg]; AllContexts[x___]/;Length[{x}]=!=1:= "never happens"/;Message[AllContexts::argnum,Length[{x}]]; ShadowedQ[s_Symbol]:=Length[AllContexts[s]]>1; (* Error cases *) ShadowedQ[_]:= "never happens"/;Message[ShadowedQ::badarg]; ShadowedQ[x___]/;Length[{x}]=!=1:= "never happens"/;Message[ShadowedQ::argnum,Length[{x}]]; SymbolsContexts[expr_,opts___?OptionQ]:= With[{conlyQ = UseOption[ContextsOnly,SymbolsContexts,opts]}, Catch[ If[TrueQ[conlyQ],Union[Part[#,All,2]],#]&@ If[#==={},Throw[{}],#]&@ Cases[ expr, (* ** The first test is needed to avoid creating symbols ** at run-time in various contexts, if these symbols ** are present in definitions in the form Symbol[stringname] *) x_Symbol/;!MatchQ[Unevaluated[x],Verbatim[Symbol][_]]&& !MemberQ[Attributes[x],Locked]:> {Hold[x],Context[x]}, Infinity, Heads->True]]]; (* Error cases *) SymbolsContexts[x___]/;Length[{x}]=!=1:= "never happens"/;Message[SymbolsContexts::argnum]; SearchedContexts[opts___?OptionQ]:= With[{include = UseOption[IncludeOnlyContexts,SearchedContexts,opts]}, If[include=!={}, include, With[{exclude = UseOption[ExcludeContexts,SearchedContexts,opts], sysExcludeQ = UseOption[ExcludeSystem,SearchedContexts,opts]}, With[{cpatt= Alternatives@@Union[ Join[Complement[$Packages,exclude],{"Global`"}]] }, If[TrueQ[sysExcludeQ],#,Append[#,"System`"]]&[ Select[Contexts[], StringMatchQ[#,(cpatt)~~___]&]]]]]]; (* Error cases *) SearchedContexts[x___]/;Length[{x}]=!=0:= "never happens"/;Message[SearchedContexts::argnum]; (* ** Switch off option checking in for speed *) DependentSymbolsInDefinitions[property:Alternatives@@globalProperties[], context_String,symbols:{___String}]:= With[{checkQ= ValueQ[PackageOptionChecksAvailableQ[]]&& OptionCheckIsOn[SymbolsContexts] }, If[checkQ, OptionCheckOff[SymbolsContexts]]; With[{listsOfContexts= Map[SymbolsContexts[#,ContextsOnly-> True]&, symbolsPropValues[property,symbols]] }, WithCodeAfter[ Select[ DeleteCases[ Transpose[{symbols,listsOfContexts}], {_String,{}}], ContextMemberQ[#[[2]],context]& ], If[checkQ,OptionCheckOn[SymbolsContexts]] ]]]; (* Error cases *) DependentSymbolsInDefinitions[_,_,_]:= "never happens"/;Message[DependentSymbolsInDefinitions::badarg]; DependentSymbolsInDefinitions[x___]/;Length[{x}]=!=3:= "never happens"/;Message[DependentSymbolsInDefinitions::argnum, Length[{x}]]; DependentSymbols[context_String,opts___?OptionQ]:= With[{propsnames=DependentSymbolsAux[context,opts]}, With[{checkprops = First[propsnames], names = Last[propsnames] }, With[{renderF= If[TrueQ[UseOption[SymbolsOnly,DependentSymbols,opts]], Part[#,All,1], #]& }, renderF@Flatten[#,1]&@ Map[DependentSymbolsInDefinitions[#,context,names]&, checkprops]]]]; (* Error cases *) DependentSymbols[_]:= "never happens"/;Message[DependentSymbols::badarg]; DependentSymbols[x___]/;Length[{x}]=!=1:= "never happens"/;Message[DependentSymbols::argnum]; DependentSymbolsExistQ[context_String,opts___?OptionQ]:= With[{propsnames=DependentSymbolsAux[context,opts]}, With[{checkprops = First[propsnames], names = Last[propsnames] }, Catch[Or@@Map[testingFunction[#,context,names]&, checkprops]]]]; (* Error cases *) DependentSymbolsExistQ[_]:= "never happens"/;Message[DependentSymbolsExistQ::badarg]; DependentSymbolsExistQ[x___]/;Length[{x}]=!=1:= "never happens"/;Message[DependentSymbolsExistQ::argnum]; End[]; (* `Private` *) (****************************************************************) (******* Finalizing option-checking ********************) (****************************************************************) If[ValueQ[PackageOptionChecksAvailableQ[]], TurnOptionChecksOn[], (* else*) Remove[PackageOptionChecksAvailableQ,TurnOptionChecksOn, SetOptionsInfo,PackageSymbolsDependenciesOptionChecks] ]; Protect[AllContexts,DependentSymbols,DependentSymbolsExistQ, DependentSymbolsInDefinitions,SearchedContexts, ShadowedQ,SymbolsContexts,ExcludeContexts,ExcludeSystem, IncludeOnlyContexts, DependencyCheck,PropertiesToCheck, SymbolsToCheck, CheckContexts, SymbolsOnly]; EndPackage[];