4
$\begingroup$

I'm using ReadList to pull the declarations off of a package like so:

PackagePullDeclarationsAction // Clear
PackagePullDeclarationsAction[
   Hold[
    _Begin | _BeginPackage |
     CompoundExpression[_Begin | _BeginPackage, ___]
    ]
   ] :=
  Throw[Begin];
PackagePullDeclarationsAction[e : Except[Hold[Expression]]] :=
  Sow@e;

PackagePullHeader[pkgFile_] :=
 Reap[
   Catch@
    ReadList[pkgFile,
     PackagePullDeclarationsAction@Hold[Expression]
     ]
   ][[2, 1]]

The PackagePullDeclarationsAction either uses Sow to sow an expression or Throw to abort the ReadList call. And this works as intended:

$pkgFile =
  URLDownload[
   "https://raw.githubusercontent.com/b3m2a1/mathematica-BTools/\
master/Packages/AppBuilder.m",
   FileNameJoin@{$TemporaryDirectory, "AppBuilder.m"}
   ];

Begin["test`"];
Take[
 PackagePullHeader[$pkgFile] // DeleteCases[Hold[Null]],
 5
 ]
End[];

{Hold[$AppDirectoryRoot::usage = 
    "The directory root for finding apps";], 
 Hold[$AppDirectoryName::usage = 
    "The basic extension to a directory for locating apps";], 
 Hold[$AppDirectory::usage = "Joins the root and name";], 
 Hold[AppPath::usage = "A path parser for a given app name";], 
 Hold[AppDirectory::usage = 
    "Used by AppPath find appropriate directories";]}

The problem is that it also loaded all of my "`Private`" symbols into the primary Context:

Names["test`*"]~Take~5

{"test`a", "test`add", "test`app", "test`AppAddContent", \
"test`AppAddDocPage"}

What's going on here? ReadList clearly didn't ignore the Throw so did it load everything first then apply PackagePullDeclarationsAction?

Is there a good work-around for this? Am I just missing something trivial?

$\endgroup$
  • $\begingroup$ @Kuba I am indeed. I don't want anything to evaluate. I just want the stuff "above" the Begin. Hence the context issues. My old version used a loop until it hit that. I wanted something faster. $\endgroup$ – b3m2a1 Aug 18 '17 at 7:03
  • $\begingroup$ Ok, I see the point now. $\endgroup$ – Kuba Aug 18 '17 at 7:04
  • $\begingroup$ Looks like you will have to stick to a loop as ReadList reads all first. $\endgroup$ – Kuba Aug 18 '17 at 7:31
  • $\begingroup$ @Kuba that was my guess. It's too bad that type of idiom (abort when pattern is found) isn't supported. I also tried a version that loads it all like this but in a hidden context and moves the necessary symbols to the appropriate context via Context[s]=... but that was slower than the loop. $\endgroup$ – b3m2a1 Aug 18 '17 at 7:32
6
$\begingroup$

Simple order of operations.

ReadList[expr, f["Expression"]]

constructs the list {f[e1], f[e2], ...} for each expression in expr. Then that will evaluate, or not, depending on what f is. However, every expression in expr has already been created. And since the BeginPackage never evaluated, all symbols without a known context will end up in the current context.

This is a general feature of reading package files. You'd encounter a similar issue with Import["foo.m", "HeldExpressions"]. Because the context can change dynamically, each expression must be read and evaluated sequentially to guarantee the expression. Your only alternative would be to write packages which fully qualify every single symbol in them so there's never a context issue.

$\endgroup$
  • 1
    $\begingroup$ I just reverted to an old loop-base method. It's actually faster than loading in a private context and exposing via Context[...]=.... Any chance a sequentially evaluating ReadList gets supported ever? (basically one that can be aborted midway through with a Throw) $\endgroup$ – b3m2a1 Aug 18 '17 at 21:08
2
$\begingroup$

Maybe you can do something like:

Begin["test`"];
Block[{BeginPackage=Identity,Begin=Throw},
    Catch@Get[package]
];
End[]

For your example:

$pkgFile = URLDownload[
    "https://raw.githubusercontent.com/b3m2a1/mathematica-BTools/master/Packages/AppBuilder.m",
    FileNameJoin@{$TemporaryDirectory, "AppBuilder.m"}
];

Begin["test`"];
Block[{BeginPackage=Identity, Begin=Throw},
    Catch @ Get[$pkgFile]
];
End[];

#->MessageName[#, "usage"]& /@ Symbol /@ Names["test`*"][[;;5]]

{test`AppAddContent -> "Adds a file to the app", test`AppAddDocPage -> "Adds a doc page for a symbol to the app", test`AppAddGuidePage -> "Adds a guide to the app", test`AppAddPackage -> "Adds a package", test`AppAddPalette -> "Adds a palette to the app"}

$\endgroup$
  • $\begingroup$ that's a clever idea. Autoloading symbols would kill it, of course (if they called a Begin as mine tend to), but that might not be an issue. I may use just that for Version 2.0 of the pre-processor. $\endgroup$ – b3m2a1 Aug 19 '17 at 9:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.