Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Listing declared variables programmatically 1

Status
Not open for further replies.

nqramjets

Programmer
Jun 20, 2012
15
US
I'm looking for a way to list or capture the declared variables programmatically, along with their values.

MikeLewis refers to an error handler written entirely in FoxPro which sounds like it does exactly this in this thread: thread184-1687712.

Here is the motivation: We have a wrapper which executes stored scripts and I'm interested in being able to take a snapshot of the variables which are defined when the wrapper begins, and return the environment to this state once the script exits.

Please note:
[ol 1]
[li]I'm aware of SAVE TO/RESTORE FROM, these almost work, but restoring from there (obviously) does not properly restore objects.[/li]
[li]Yes it's possible that variables are declared PUBLIC inside the script which causes them to remain in scope when the script exits. I know that somebody will be screaming inside that this is bad form, but let's keep that on the inside. It's just the way it is. [ponder]
[/li]
[/ol]

The SAVE TO/RESTORE FROM is really great, except that I'd like to just release any variables which have popped up since the snapshot was taken, and parsing that binary is something I'd like to avoid. (Referring to the same thread as above.) Even better would be able to implement something like what Mike refers to in that thread and capture the values of all the declared variables programmatically.

Essentially, if I had the foxcode implementation for generating the list supplied by intellisense when you type "m." I'd be happy.

Thanks ahead of time!
-TRH
 
The error-handler I referred to simply executes:

LIST MEMORY LIKE * TO <filename> NOCONSOLE

It's the LIST MEMORY command that does the work. I only mentioned the error-handler because that's typically the sort of routine that calls it.

So, to enumerate the variables that are declared at a given time, you execute the above command, then parse out the names from the resulting text file.

Regarding the public variables that are defined within the script, you can identify these within the text file, either by looking for their specific names and/or by looking for PUB in the second column. Parse these out, and disregard them when looking for the other variables.

As far as objects are concerned, you can use LIST OBJECTS in much the same way as LIST MEMORY.

Does that help at all?

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
Thanks Mike,

I was trying to avoid the step where we write to disk, but that's okay. I thought maybe you had some kind of slick trick to list them I missed. I've always wanted something like AMEMORY() or AVARIABLES() to just get a list... Oh well.

I wasn't aware of the LIST OBJECT command at all.[sadeyes]

Thanks you much Mike, that's very helpful.
-TRH

For posterity:

Code:
[tt]CLEAR MEMORY[/tt]
CLEAR

*
* Example data
*
PUBLIC goVal, gcVal
gcVal = "Public Variable"
goVal = CREATEOBJECT("Empty")

pcVal = ["Private Variable"]
poVal = CREATEOBJECT("Empty")

LOCAL lcVal as String
LOCAL loVal as Object
LOCAL ARRAY laVal[2,2]

lcVal = "Local Variable"
loVal = CREATEOBJECT("Empty")

*
* Save the snapshot
*
LOCAL lcSnapFile AS String
lcSnapFile = GETENV("TEMP") + SYS(2015) + ".txt"

DISPLAY MEMORY LIKE * TO FILE (lcSnapFile)
ALINES(laLines, FILETOSTR(lcSnapFile), 5)

DELETE FILE (lcSnapFile)

IF TYPE("laLines", 1) == "A"
	FOR EACH lcLine IN laLines
		IF	GETWORDNUM(lcLine,1) <> "LCSNAPFILE" AND ISALPHA(LEFT(lcLine,1))
			*
			* Dimension the array
			*
			* Column 1: Variable Name
			* Column 2: Variable Scope
			* Column 3: Variable Type
			* Column 4: Calling Scope
			*
			IF TYPE("laSnapshot", 1) == "A"
				DIMENSION laSnapshot[ALEN(laSnapshot, 1) + 1, 4]
			ELSE
				DIMENSION laSnapshot[1, 4]
			ENDIF
			
			* Load up the array data
			? "--> " + lcLine
			
			laSnapshot[ALEN(laSnapshot, 1), 1] = GETWORDNUM(lcLine, 1)
			laSnapshot[ALEN(laSnapshot, 1), 2] = GETWORDNUM(lcLine, 2)
			laSnapshot[ALEN(laSnapshot, 1), 3] = GETWORDNUM(lcLine, 3)
			IF GETWORDNUM(lcLine, 2) == "Pub" AND GETWORDNUM(lcLine, 3) == "O"
				laSnapshot[ALEN(laSnapshot, 1), 4] = ""
			ELSE
				laSnapshot[ALEN(laSnapshot, 1), 4] = GETWORDNUM(lcLine, GETWORDCOUNT(lcLine))
			ENDIF
			
		ENDIF
	ENDFOR
ELSE
	? "Unable to build snapshot."
ENDIF

?
DISPLAY MEMORY LIKE laSnapshot
 
Oops, there's a bug in that for all globals....

Here we go:

Code:
CLEAR MEMORY
CLEAR

*
* Example data
*
PUBLIC goVal, gcVal
gcVal = "Public Variable"
goVal = CREATEOBJECT("Empty")

pcVal = ["Private Variable"]
poVal = CREATEOBJECT("Empty")

LOCAL lcVal as String
LOCAL loVal as Object
LOCAL ARRAY laVal[2,2]

lcVal = "Local Variable"
loVal = CREATEOBJECT("Empty")

*
* Save the snapshot
*
LOCAL lcSnapFile AS String
lcSnapFile = GETENV("TEMP") + SYS(2015) + ".txt"

DISPLAY MEMORY LIKE * TO FILE (lcSnapFile)
ALINES(laLines, FILETOSTR(lcSnapFile), 5)

DELETE FILE (lcSnapFile)

IF TYPE("laLines", 1) == "A"
	FOR EACH lcLine IN laLines
		IF	GETWORDNUM(lcLine,1) <> "LCSNAPFILE" AND ISALPHA(LEFT(lcLine,1))
			*
			* Dimension the array
			*
			* Column 1: Variable Name
			* Column 2: Variable Scope
			* Column 3: Variable Type
			* Column 4: Calling Scope
			*
			IF TYPE("laSnapshot", 1) == "A"
				DIMENSION laSnapshot[ALEN(laSnapshot, 1) + 1, 4]
			ELSE
				DIMENSION laSnapshot[1, 4]
			ENDIF
			
			* Load up the array data
			? "--> " + lcLine
			
			laSnapshot[ALEN(laSnapshot, 1), 1] = GETWORDNUM(lcLine, 1)
			laSnapshot[ALEN(laSnapshot, 1), 2] = GETWORDNUM(lcLine, 2)
			laSnapshot[ALEN(laSnapshot, 1), 3] = GETWORDNUM(lcLine, 3)
			IF GETWORDNUM(lcLine, 2) == "Pub"
				laSnapshot[ALEN(laSnapshot, 1), 4] = ""
			ELSE
				laSnapshot[ALEN(laSnapshot, 1), 4] = GETWORDNUM(lcLine, GETWORDCOUNT(lcLine))
			ENDIF
			
		ENDIF
	ENDFOR
ELSE
	? "Unable to build snapshot."
ENDIF

?
DISPLAY MEMORY LIKE laSnapshot
 
If you enter a wrapper object method, only PUBLIC and private variables will be visible to that wrapper object method, and only those could be affected by your code. Local variables are safe anyway. So actually there is not much to worry about. You just have to think right about scope and what it means. Your fear about the need to restore a previous state is almost unneeded and based on having a low understanding about scope. If you don't need to access any public or private variables inside your wrapper, you simply start your method with PRIVATE ALL. See what this does:

Code:
On Error ?? " variable not present"
Local lcTest
Public gcTest

pcTest = "private"
lcTest = "local"
gcTest = "public"

wrapperfunction()
? gcTest

Function wrapperfunction()
   List memory like *c*
   
   ? "local:"
   ?? lcTest
   ? "private:"
   ?? pcTest
   ? "public:"
   ?? gcTest
   
   Private All
   List Memory Like *c*   

   ? "local:"
   ?? lcTest
   ? "private:"
   ?? pcTest
   ? "public:"
   ?? gcTest

   Public gcTest
   gcTest ="public wrapper"

EndFunc

I'll keep the discussion about public variables low, because as you'll see after PRIVATE ALL, none of the variables, which were defined outside of the wrapperfunction, are accessible anymore. Not even the public gcTest. So their value and existence is totally not your concern anymore.

Let's single step through the code. In the first place I set an error handler to catch all errors by printing " variable not present". That's just to demonstrate later, how several variables are not accessable and accessing them causes an error I catch this way and continue the demonstration.

Next is the declaration of three variables by a LOCAL, PUBLIC statement and by a value assignement (this generates a PRIVATE variable). Notice PRIVATE is not like LOCAL or PUBLIC declaring a variable, this is the basis of PRIVATE ALL, I come back to that difference, when we're at that step.

Next is the call of wrapperfunction(). Local variables are local and so will not be seen by anything else called, but LIST MEMORY still lists lcTest. That's because it's an exception of the rule, LIST MEMORY is there to list memory variables, and should not be limited to variables of the current scope, of course. But as you see in the next line, the local variable is not present, the line ?? lcTest triggers the error handler to print "variable is not present", only the private and the public are accessible. You also can try to assign a values to lcTest, this also triggers errors, you also cannot RELEASE lcTest, that will not lead to an error, but that variable is not in scope. That's why you define most variable LOCAL, if you don't want any routine you call to change it and be sure after you return from whatever number of levels of calls on the stack, your LOCAL variable still has the value it had before you made the call, it's also still existing.

Now the PRIVATE ALL line comes into play. After that, not only the local lcTest, but also the private and the public pcTest and gcTest are not present anymore. But they didn't vanish, they still exist, as LIST MEMORY shows. If you look closely you'll see they are listed as hidden (hid) instead of Local, Private or Public.

The next few lines now all trigger an error and "variable not present" is printed for all of them. After that I recreate a public variable gcTest and set it to a value, then return from the wrapperfunction. After that gcTest does not have the new value "public wrapper", but still the initial "public".

So PRIVATE ALL simply solves all that problems. If your wrapper needs access to goApp or any other public variable you can make that an exception to hiding variables by PRIVATE ALL EXCEPT goApp, for example.

Just have a read on the topic of PRIVATE and you'll understand that.

Bye, Olaf.




 
Hi Olaf,

Thanks for the post. I had forgotten that PRIVATE can hide PUBLIC variables.

I'm actually not getting the desired behavior. You're code works correctly only when the public variable declared has a name which matches the public variable in the outside scope. Notice that [tt]gcTest2[/tt] persists after [tt]wrapperfunction()[/tt] exits.

See this version below:

Code:
On Error ?? " <<variable not present>>"
Local lcTest
Public gcTest

pcTest = "private"
lcTest = "local"
gcTest = "public"

? gcTest2

wrapperfunction()
? gcTest
* Notice that this exists now!
? gcTest2

Function wrapperfunction()
   List memory like *c*
   
   ? "local:"
   ?? lcTest
   ? "private:"
   ?? pcTest
   ? "public (same name):"
   ?? gcTest
   ? "public (new name):"
   ?? gcTest2
      
   Private All
   List Memory Like *c*   

   Public gcTest2
   gcTest2 ="public wrapper"

   ? "local:"
   ?? lcTest
   ? "private:"
   ?? pcTest
   ? "public:"
   ?? gcTest
   
   ? "public (new name):"
   ?? gcTest2

EndFunc

Thank you again.
-TRH
 
Of course if you don't have a name overlap, your new public variable persists and is visible outside the wrapperfunction.

So what?

If the outside code doesn't generate that variable, will it use it?

You know yourself what you generate and you can tidy up in your own code, you can release your new public variable before you return, can't you?

Bye, Olaf.
 
What you should be more concerned about is influences on the current datasession and objects. No matter if you pass objects in or you address _screen.activeform or _screeen.forms(n) that of course has an influence on the outside. There is no means to have a snapshot you can revert to, you are responsible for your part of the code and what it does by intention or not.

If you break something nonintentional it's error, errors happen.

If you code a line PUBLIC myvar then at the same moment write RELEASE myvar and write your code using that public variable between those lines, then you'll not forget it. Or rather create a local or private variable anyway. But if you create a public variable, and agani let's not talk about how bad that is, the idea is you WANT that variable public, so it should also stay defined outside your code, shouldn't it? That's the definition of public. If the intention was that of a rpivate variable, visiable at the stack level generated and in called code, then change that to private variables. Find the declaraiotn of the public variables you don't want and change them. There's no big hudrdle, you have Code References to search the whole project.

Bye, Olaf.
 
Hi,

A good starter for your request to show / make a list of all variables would be Maurice de Beyer's "Parselocals" you will have to study and modify as per default parselocals only lists the non-declared variables, a changement which should not be to difficult. Parselocals works with all variables, regardless if they are prefixed with a 'l' in contrary to the Create_locals.prg which is part of Thor and could also be used in this respect with the by you required behavior.

Regards,

Jockey(2)
 
Jockey, I'm a great fan of ParseLocals, but I don't think it will meet Nqramjet's needs. The point about ParseLocals is that it scans a source file. What's required here is to examine the variables that are actually in memory at run time.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
Hi All,

Olaf: Of course the code outside does not use it, but the next script which I run inside the wrapper might. This is exactly why I'm trying to hide these when they're produced. There are common parameter variable names like glUpdate or gcFile which I am interested in removing just to be sure there's no chance that the 2nd script does not accidentally make use of a value which was assigned in a previous script. And yes, I can tidy up my own code, but it's not all mine. The question was simply whether or not this is possible, not how scope works.

Jockey2: Thanks for that link to ParseLocals, I've never seen that one before. It sounds like that would be an answer to the question "What is the code to supply the list of variables when you type 'm.'?" I do have access to the code of the script when it's about to be run (obviously) so I wonder if I can use some hybrid of MikeLewis' (MikeLewis's? MikeLewises??) and your suggestion to do this.

MikeLewis: Thanks you for sticking with the original question. Dynamically at runtime is ideal, but sounds like it may be too difficult, or too kludgy to really be clean.

As a side note: The parameter variables are assigned within the script like this so that they're accessible outside the scope of fetch_settings(). It's also important (especially when the script is 6000+ lines) that the very beginning of the script is the skeleton of execution, with the first call being to fetch_settings(), to make it readable.

If there was a way to set these variables dynamically inside the method like this without having to scope them publicly then I'm all for it, but nothing comes to mind.

Code:
* Beginning of script...
* preliminary stuff goes here...

* Fetch user settings
fetch_settings()

* use these settings to do stuff.
* do more work, blah blah blah
RETURN

PROCEDURE fetch_settings

  create cursor csrVariables (var_name v(64), var_value v(254), ...)
  brow last fields var_value, var_name :r, ... ;
    title "Enter settings"

  scan
    lcVarName = csrVariables.var_name
    public (lcVarName)
    &lcVarName. = fancy_cast_method(var_value, ...)
  endscan

ENDPROC
 
If this is so crucial to you to start over a number of scripts without unwanted remains influencing the next one, then what comes to mind is making these seperate EXEs and calling them. Each script has it's own process then, nothing to overlap.

And if this all must work together in one process then any public variable that is an error from some scritp is not your fault, you're not responsible to correct it, are you? It's more likely these variables are wanted to stay in scope, for example as a public counter or anything I really don't want to reset as the developer creating such public vars.

Anything you do from your perspective to tidy up and restore a ground zero can be good or bad. If you don't have an insight in the code you call, such a concept of cleaning seems just a mere guess it would be good.

You have CLEAR ALL, RELEASE ALL, well, or RUN/ShellExecute/CreateProcess if reset is so crucial and each script you call is indepedant on a state anyway, then that's as clean as you can get.

Bye, Olaf.

 
The first thing that crossed my mind at the top of this thread was Olaf's suggestion of an independent process. It doesn't have to be separate exes, it could just be a "script handler" exe that you call with the identity of the script to be executed. Or it could be a COM object that you call similarly.

If a script really shouldn't upset the current environment, then it should really not run in the current environment. Or it should be written such that it doesn't upset the current environment.

Don't spin your wheels trying to make things into something they're not.
 
Olaf/DanFreeman: Thanks guys, perhaps that's actually the best solution. I'm afraid this is just one of those things that I couldn't quite do in a nice way so it's probably not worth hacking together.

The only other thing I could think of would be to have a settings object with all of the user input dangling as properties off of it. This way there is exactly one variable which would be used for all the settings, and I can explicitly release it after the scripts are completed in the wrapper method. After all, RELEASE goSettings still works if there is no goSettings.

Actually, I already have a object which automatically adds properties to the object when they're used the first time, so actually this should be quite easy to do.
 
Well, there is ParallelFox as part of the codeplex VFPX project to help with starting parallel foxpro processes.

Bye, Olaf.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top