Choose Language Hide Translation Bar
Highlighted
jcampbell
Level I

Eval (), Parse (), and Recursion, an Adventure in Meta-Programming

A few years ago, I was working on building a script to automagically generate a sampling plan for an attribute-style measurement systems analysis. Everything was going smoothly, until I ran into a big problem: how could I make the script pull up a window to generate the master “Pass/Fail” list of parts being used, if the number of parts was chosen at run-time? Now, you might be already thinking of an easy way to do this. I’m still, however, proud of the method that I came up with, and I’ll share that in this blog post.

Hidden in the Programming portion of the JSL Scripting Index are 2 seemingly innocuous entries: Eval and Parse. These functions allow a crafty scripter to execute a string as if it were a piece of code. Being able to do this has staggering implications. Doing this can also solve my problem from earlier.

Let’s dig in by building a simple script:

//section 1:

//get pass/fail criteria for one part

//initialize some variables

i=1;

listParts = {};

//build modal window to get criteria

New Window("Get Pass/Fail List",

       <<Modal,

              H List Box(

                     V List Box(

                           Lineup Box( 2,

                                  Text Box(char(i)), listParts[i] = Text Edit Box("Pass"),

                                  Button Box("OK", listParts[i] = listParts[i] << Get Text;),       Button Box("Cancel")

                           )                   

                     )

              )

       );

//look at list

show(listParts[i]);

Make sure you have the log showing, then run this script. The script itself shows a window and asks, for one part only, if the part is a “Pass” or a “Fail.” When the OK button is pressed, whatever text is in the window is passed to the ith location in the listParts list, and listParts is shown on the log. Storing the results in a list of just 1 item seems kind of silly for now, but makes sense when we have more than one part to store Pass/Fail criteria for. See the example below, for what the script looks like for 3 parts:

//section 2:

//repeat of above script, but grabbing criteria for 3 parts

i=1;

listParts = {};

New Window("Get Pass/Fail List",

       <<Modal,

              H List Box(

                     V List Box(

                           Lineup Box( 2,

                                  Text Box(char(1)), listParts[1] = Text Edit Box("Pass"),

                                  Text Box(char(2)), listParts[2] = Text Edit Box("Pass"),

                                  Text Box(char(3)), listParts[3] = Text Edit Box("Pass"),

                                  Button Box("OK", for (i = 1, i <=3, i++, listParts[i] = listParts[i] << Get Text;)),     Button Box("Cancel")

                           )                   

                     )

              )

       );

show(listParts);

But, we haven’t solved the problem, yet. How can we make this script show a dynamic number of text boxes, based on some arbitrary number of parts, that isn’t known until after the script starts running, and could be different each time the script is run? Enter Eval and Parse, the aforementioned wunderkinds, to the rescue. Take a look at the following code:

//section 3:

//determine number of parts

New Window ("How many parts?",

       << Modal,

       neb = Number Edit Box (10),

       Button Box ("OK", numParts = neb << Get;)

);

show(numParts);

listParts = {};

//Build first portion of script for modal window

//Escape character sequence for " is \!"

a = "

       New Window(\!"Get Pass/Fail List\!",

            <<Modal,

              H List Box(

                     V List Box(

                           Lineup Box( 2,

";

//for loop creates second portion, which builds however many input boxes as there are parts

// || is the shortcut for concatenate

b="";

for ( i = 1, i <=numParts, i++,

       b1 = char("Text Box(char(" || char(i) || ")), listParts[" || char(i) || "] = Text Edit Box(\!"Pass\!"),");

       b = b||b1;

);

//build last portion of script for modal window

c = "

       Button Box(\!"OK\!", for (i = 1, i <=numParts, i++, listParts = listParts << Get Text;)),     Button Box(\!"Cancel\!")

                           )                   

                     )

              )

       )

";

//combine parts of script, and execute

d = a || b || c;

eval (parse(d));

//look at what was created

show(d);

show(listParts);

First, there’s a section to ask the user how many parts, which is stored in numParts. Next, the script creates three separate strings that look suspiciously like JSL, except they are all in purple. The first string is just an exact copy (with some special code to make the quote characters not end the string) of the script from before, all the way up to the part where text boxes are inserted. The second part is a for loop to build a string consisting of however many parts were entered previously (numParts) repeats of textboxes. The third section is the last bits of the script from before.

Once this string is created, we combine them (d = a || b || c;), then use the eval (parse(d)); command to evaluate what happens when JMP parses the string d (our dynamically created string with an adjustable number of text boxes) as if the string d were JSL script, and not just a string. Look at the log to see both the super long command to show lots of text boxes, as well as the list of Pass/Fail that was generated (shown below if you just accept the defaults):

d = "

       New Window(\!"Get Pass/Fail List\!",

       <<Modal,

              H List Box(

                     V List Box(

                           Lineup Box( 2,

Text Box(char(1)), listParts[1] = Text Edit Box(\!"Pass\!"),Text Box(char(2)), listParts[2] = Text Edit Box(\!"Pass\!"),Text Box(char(3)), listParts[3] = Text Edit Box(\!"Pass\!"),Text Box(char(4)), listParts[4] = Text Edit Box(\!"Pass\!"),Text Box(char(5)), listParts[5] = Text Edit Box(\!"Pass\!"),Text Box(char(6)), listParts[6] = Text Edit Box(\!"Pass\!"),Text Box(char(7)), listParts[7] = Text Edit Box(\!"Pass\!"),Text Box(char(8)), listParts[8] = Text Edit Box(\!"Pass\!"),Text Box(char(9)), listParts[9] = Text Edit Box(\!"Pass\!"),Text Box(char(10)), listParts[10] = Text Edit Box(\!"Pass\!"),

       Button Box(\!"OK\!", for (i = 1, i <=numParts, i++, listParts = listParts << Get Text;)),     Button Box(\!"Cancel\!")

                           )                   

                     )

              )

       )

";

listParts = {"Pass", "Pass", "Pass", "Pass", "Pass", "Pass", "Pass", "Pass", "Pass", "Pass"};

Take a moment to think of the implications, here. With these concepts, you can create code that adjusts itself, and then executes itself. Dynamic, self-referential, recursive, and crazy scripts could be just the beginning. Given enough time and patience, you could probably use this concept to create Skynet. On second thought, maybe that’s not such a good idea…

4 REPLIES 4
Highlighted
SamKing
Level III

Re: Eval (), Parse (), and Recursion, an Adventure in Meta-Programming

Great example, thank you @jcampbell .  I am having one issue that I can't seem to resolve.  I get an error:  Send Expects Scriptable Object in access or evaluation of 'List' , {/*###*/"Pass", "Fail"}

I think it is this send command causing the issue.  I only get the error if I choose more than 1 number.  Any thoughts? 

listParts = listParts << Get Text;
Highlighted
erin_vang
Level II

Re: Eval (), Parse (), and Recursion, an Adventure in Meta-Programming

The problem here is that <<Get Text is not a message you can send to a list. I think it's an error in the example code that you don't see listparts[i] or something that amounts to that, as you do elsewhere in the script where <<GetText is used. 

 

You can't send messages to things that aren't objects, and lists are not objects in the same sense that, say, data tables are. I recently fixed a longstanding bug in one of my clients' addins by realizing I had tried to do

list<<insert item(item)

 instead of 

insert item(list, item)

 

I think where many of us get confused is that Associative Arrays ARE objects that can take <<messages, and so you see different syntax for commands having the same name, such as:  

 

map=associative array(:Name, :Weight);
map<<insert item("Tom", 68); 
list = map<<get keys;
insert item(list, "Tom"); 

 

Back to the question at hand, I'm not really sure why the example uses listparts[i]<<get text in the first place, though, as simply calling the list with a subscript [i] returns the string in the ith position of the list. Unless I'm missing something subtle in the script (which I've only read and not stepped through in JMP), each of those places in the script could be simplified as just listparts[i] rather than listparts[i]<<gettext. 

Erin Vang
Principal Pragmatist, Global Pragmatica LLC® - Custom JMP Scripting
Highlighted
jcampbell
Level I

Re: Eval (), Parse (), and Recursion, an Adventure in Meta-Programming

In addition to what Erin has to say, I can also trace this back to a bit of stupidity on my part, in that I'm not using names properly all the time in this example. What I think is going on is that, when the window is created, a bunch of Text Boxes are created. Text Boxes are objects, which means it should be possible to send them the << Get Text() message. Where the stupidity (luck?) comes in is that each Text Box is assigned the name ListParts, so if I get really lucky with when I send ListParts[x] the Get Text () message, I actually get some text. In retrospect, I should have named the listParts[x] Text Boxes something else (i.e., in the example below, substituting ListParts[1] with tb[1]).

Text Box(char(1)), listParts[1] = Text Edit Box("Pass"),


I think this is an example of one of those rare instances when a script does something the author wanted, despite being actually incorrect, which is kind of exciting!

Highlighted
erin_vang
Level II

Re: Eval (), Parse (), and Recursion, an Adventure in Meta-Programming

Behold the delightful mysteries of untyped variables! It's shockingly easy to get into situations like that with JSL. I, too, have accidentally gotten away with code that shouldn't have worked, and it was a real knot to untangle when a later version of JMP or different operating conditions caused my sloppiness to fail at long last. 

 

I would confess details, but I can't remember any (perhaps I'm repressing?), and first rule of JSL Club is you don't talk about what happens at JSL Club. 

 

Suffice it to say I got a good belly laugh from your followup. 

Erin Vang
Principal Pragmatist, Global Pragmatica LLC® - Custom JMP Scripting
Article Labels

    There are no labels assigned to this post.