Scripting Guide > Programming Methods > Advanced Expressions, Macros, and Lists
Publication date: 07/08/2024

Advanced Expressions, Macros, and Lists

This section describes how to use expressions, macros, and lists in advanced JSL scripts.

Advanced Expressions

Advanced Macros

Manipulate Lists

Manipulate Expressions

Advanced Expressions

This section delves into JSL expressions and introduces several JSL functions that can be used to manipulate JSL expressions. These functions provide JSL programmers with a rich, powerful, meta programming toolkit. One of the benefits of such a toolkit is that it provides JSL programmers with a powerful macro system capability that is similar in functionality to macro systems available in other modern programming languages.

In order to understand JSL expressions, it is useful to keep in mind that everything that happens in JSL is a result of a function call. Function calls come in three varieties:

prefix

The function name comes before its arguments (for example, Foo( m, n ) ). Most function calls in JSL are of this form.

infix

The function name is between its arguments (for example, m + n). This form is common for mathematical operators.

special

Functions like [ ] (the subscript operator) fall into this category. Since there are only a handful of special functions, they are not discussed further in this section.

Although function calls come in three forms, all function calls can be written in prefix form. It turns out that JMP actually uses the prefix form internally and so, in general, it is very useful to think of JSL expressions in this way.

To illustrate this idea, begin by examining the syntactic structure of JSL expressions a bit more closely. All JSL expressions must conform to a very simple syntactic structure. Each expression consists of a head expression and a sequence of zero or more argument expressions. Consider the following JSL expressions, written first in infix form and then in prefix form, to see how this syntactic structure applies:

x = 100.1
m & n
1 <= z <= 20

Using the prefix form, these expressions are written equivalently:

Assign( x, 100.1 )
And( m, n )
Less or Equal( 1, z, 20 )

The head of the first example is Assign and its arguments are x and 100.1. For the second example, the head is And and its arguments are m and n, whereas for the third example, the head is Less or Equal and its arguments are 1, z, and 20. For these examples, the “=”, “&”, and “<=” operators are just human friendly ways of representing Assign, And, and Less or Equal.

Examine a few additional simple examples.

Assign( 100.1, x )
And( )
Less or Equal( z )

These examples also conform to the required syntactic structure, but they differ from the previous examples in that they all produce an error message when they are evaluated. This point leads to an important distinction that you should make when thinking about JSL expressions. That is, you should separate the idea of the syntactic structure of a JSL expression from what happens when the expression is evaluated.

Consider the following examples that present both the human friendly form and the prefix form in each case:

x = 100.1 + Log( z )                   // human friendly form
Assign( x, Add( 100.1, Log( z ) ) )    // prefix form

This example illustrates that the arguments of a head expression can also be expressions.

// human friendly form

x = 0.8;
x ||= Random Uniform( 0.2, 0.6 );
 

// prefix form

Glue(
	Assign( x, 0.8 ),
	Concat To( x, Random Uniform( 0.2, 0.6 ) )
)

This example would typically be referred to as a JSL script, but it is just another case of a JSL expression. Here, the head expression is Glue, which has two arguments: the expressions Assign( x, 0.8 ) and Concat To( x, Random Uniform( 0.2, 0.6 ) ). This example illustrates two important points:

1. The “;” symbol is another instance of a human friendly operator. In this case, it is for the Glue function.

2. There is no distinction between what are typically referred to as a JSL script and a JSL expression.

The remainder of this section covers how to assign an expression to a variable and then how to manipulate, or execute, that expression when needed. For information about how JMP resolves names when evaluating JSL expressions, see the section Rules for Name Resolution.

Quoting and Unquoting Expressions

The operators to control when expressions are evaluated are Expr and Eval, which you should think of as the procrastination and eager operators. Expr just copies its argument as an expression, rather than evaluating it. Eval does the opposite: it evaluates its argument, and then takes that result and evaluates it again.

Expr and Eval can be thought of as quoting and unquoting operators, telling JMP when you mean the expression itself, and when you mean the result of evaluating the expression.

The following examples all assume these two assignments:

x = 1; y = 20;

If you assign the expression x+y to a, quoting it as an expression with Expr, then whenever a is evaluated, it evaluates the expression using the current values of x and y and returns the result. (Exceptions are the utilities Show, Write, and Print, which do not evaluate expressions for pure name arguments.)

x = 1; y = 20;
a = Expr( x + y );
a;

21

If you want the expression that is stored in the name, rather than the result of evaluating the expression, use the NameExpr function. See Retrieve a Stored Expression Rather than its Result.

x = 1; y = 20;
Show( Name Expr( a ) );

NameExpr(a) = x + y

If you assign an extra level of expression-quoting, then when a is evaluated, it unpacks one layer and the result is the expression x+y.

x = 1; y = 20;
a = Expr( Expr( x + y ) );
Show( a );

a = Expr(x + y)

If you want the value of the expression, then use Eval to unpack all layers:

x = 1; y = 20;
Show( Eval( a ) );

Eval(a) = 21

You can do this to any level, for example:

x = 1; y = 20;
a = Expr( Expr( Expr( Expr( x + y ) ) ) );
b = a;

Expr( Expr( x + y ) )

c = Eval( a );

Expr( x + y )

d = Eval( Eval( a ) );

x+y

e = Eval( Eval( Eval( a ) ) );

21

For more information about expression handling, see Morgan (2010).

Quote an expression as a string

The JSL Quote() function returns the contents of an expression as a quoted string. Comments and white space in the string are preserved.

The following expression is an example:

x = JSL Quote( /* Begin quote. */
For (i = 1, i <= 5, i++,

// Print the value of i.

Print( i );
);

// End expression.

);
Show( x );

In the output, the contents of the JSL Quote() function are enclosed in quotes.

x = " /* Begin quote. */

For (i = 1, i <= 5, i++,

// Print the value of i.

Print(i);

);

// End expression.

";

Quoting Expressions

A common use of the Expr() function is to store an expression in a JSL variable. This could be considered as a macro. Consider the following script:

dist = Expr( Distribution( Column( height ) ) );

To execute the above expression, just use the following:

dist;

You can use expressions in a loop to execute it:

For( i = 0, i < 3, i = i + 1, dist );

This loop results in three Distribution platform reports for height.

Use Eval() to evaluate an expression explicitly:

Eval( dist );

Note, however, that in column formulas, Eval() only works if it is outermost in the formula. So, for example,

Formula( Log( Eval( Column Name( i ) ) ) );

would generate an error. Instead, use:

Formula( Eval( Substitute( Expr( Log( xxx ) ), Expr( xxx ), Column Name( i ) ) ) );

As another example,

Formula( Eval( Column Name( i ) ) + 10 );

generates an error, since Eval() is actually under the Add function. Instead, use:

Formula(Eval(Substitute(Expr(xxx+10), Expr(xxx), column name(i))))

Retrieve a Stored Expression Rather than its Result

What if you wanted the symbolic value of a variable (such as the expression Distribution(Column(height)) stored in dist above), rather than the evaluation of it (the actual launched platform)? The Name Expr function does this. Name Expr retrieves its argument as an expression without evaluating it.

Expr returns its argument exactly, whereas Name Expr looks up the expression stored in its argument. Name Expr “unpacks” just one layer to get the expression, but does not keep unpacking to get the result.

For example, you would need to use this if you had an expression stored in a name and you wanted to edit the expression:

popVar = Expr( Summation( i = 1, N Row(), (y[i] - Col Mean( y )) ^ 2 / N Row() ) );

Summation( i = 1, N Row(), (y[i] - Col Mean( y )) ^ 2 / N Row() )

 
unbiasedPopVar = Substitute( Name Expr( popVar ), Expr( Wild()/N Row() ), Expr( (y[i] - Col Mean( y )) ^ 2 / ( N Row() - 1 ) ) );

Summation( i = 1, N Row(), (y[i] - Col Mean( y )) ^ 2 / (N Row() - 1) )

Compare x, Expr(x), NameExpr(x), and Eval(x) after submitting this script:

a = 1; b = 2; c = 3;
x = Expr( a + b + c );

Table 8.1 Compare Eval, Name Expr, and Expr

Command and result

Explanation

x;

6

Evaluates x to the expression a+b+c, and then evaluates the expression, returning the result, 6 (unpacks all layers).

Eval( x );

6

Equivalent to simply calling x.

Evaluates x to the expression a+b+c, and then evaluates the expression, returning the result, 6 (unpacks all layers).

NameExpr( x );

a+b+c

Returns the expression that was stored in x, which is a+b+c (unpacks the outside layer).

Expr( x );

x

Returns the expression x (packs one layer).

JSL also supports functions to access and traverse expressions, all of them either a name or a literal expression as an argument. In the following, expressionArg is either a single name or a compound expression to be taken literally.

NArg(expressionArg) finds the number of arguments in expressionArg.

The expressionArg can be a name holding an expression, an expression evaluated to an expression, or a literal expression quoted by Expr().

NArg(name) obtains the expression held in name (it is not evaluated) and returns the number of arguments

NArg(expression) evaluates expression and returns the number of arguments

NArg(Expr(expression)) returns the number of arguments to literal expression.

For example, if aExpr = {a+b,c,d,e+f+g};

NArg(aExpr) results in 4.

NArg(Arg(aExpr,4)) results in 3.

NArg(Expr({1,2,3,4})) results in 4.

Head(expressionArg) returns the head of the expression without any arguments. If the expression is an infix, prefix, or postfix special character operator, then it is returned as the functional equivalent.

The expressionArg can be a name holding an expression, an expression evaluated to an expression, or a literal expression quoted by Expr().

For example, if aExpr = expr(a+b);

r = Head(aExpr) results in Add().

r = Head (Expr(sqrt(r))) results in Sqrt().

r = Head({1,2,3}) results in {} .

Arg(expressionArg,indexArg) extracts the specified argument of the symbolic expression, resulting in an expression.

For example, Arg(expressionArg,i) extracts the ith argument of expressionArg.

The expressionArg can be a name holding an expression, an expression evaluated to an expression, or a literal expression quoted by Expr().

Arg(name,i) obtains the expression held in name (it is not evaluated) and finds the ith argument.

Arg(expression,i) evaluates expression and finds the ith argument.

Arg(Expr(expression),i) finds the ith argument of expression.

As another example, if aExpr = Expr(12+13*sqrt(14-15));

Arg(aExpr,1) yields 12.

Arg(aExpr,2) yields 13*sqrt(14-15).

Arg(Expr(12+13*sqrt(14-15)),2) yields 13*sqrt(14-15).

To extract an argument of an argument inside an expression, you can nest Arg commands:

Arg(Arg(aExpr,2),1) yields the first argument within the second argument of aExpr, or 13.

Arg(Arg(aExpr,2),2) yields Sqrt(14-15).

Arg(Arg(Arg(aExpr,2),2),1) yields 14-15.

Arg(Arg(Arg(aExpr,2),2),3) yields Empty()

Here is a description of how the last example line unwraps itself:

1. The inner Arg statement is evaluated.

Arg(aExpr,2)

13 * Sqrt( 14 - 15 )

2. Then the next one is evaluated.

Arg(Arg(aExpr,2),2)
// this is equivalent to Arg(Expr (13 * Sqrt( 14 - 15 ) ), 2)

Sqrt( 14 - 15 )

3. Finally, the outer Arg is evaluated.

Arg(Arg(Arg(aExpr,2),2),3)
// this is equivalent to Arg (Expr (Sqrt( 14 - 15 ) ), 3)

Empty()

There is only one element to the Sqrt expression, so a request for the third argument yields Empty(). To access the two arguments inside the Sqrt expression, try this:

Arg(Arg(Arg(Arg(aExpr,2),2),1),2);

15

HeadName(expressionArg) returns the name of the head of the expression as a string. If the expression is an infix, prefix, postfix, or other special character operator, then it is returned as the functional equivalent.

The expressionArg can be a name holding an expression, an expression evaluated to an expression, or a literal expression quoted by Expr().

For example, if aExpr = expr(a+b);

r = HeadName (aExpr) results in "Add".

r = HeadName (Expr(sqrt(r))) results in "Sqrt".

r = HeadName ({1,2,3}) results in "List".

In previous versions of JMP, other versions of Arg, Narg, Head, and HeadName were implemented, called ArgExpr, NArgExpr, HeadExpr, and HeadNameExpr, respectively. These did the same thing, but did not evaluate their argument. These forms are now deprecated and will not be documented in future versions.

Making Substitutions

Eval Insert is for the situation where you want to make substitutions, by evaluating expressions inside a character string.

With Eval Insert, you specify characters that delimit the start and end of an expression, and everything in between is evaluated and expanded.

There are two functions, one to return the result, the other to do it in-place.

resultString = EvalInsert( string with embedded expressions,startDelimiter,endDelimiter )
EvalInsertInto( string l-value with embedded expressions,startDelimiter,endDelimiter )

The delimiter is optional. The default start delimiter is "^". The default end delimiter is the start delimiter.

xstring = "def";
r = Eval Insert( "abc^xstring^ghi" );  // returns "abcdefghi";
 
r = "abc^xstring^ghi"; // in-place evaluation
Eval Insert Into( r ); // r now has "abcdefghi";
 

// with specified delimiter

r = Eval Insert( "abc%xstring%ghi","%" );  // returns "abcdefghi";
 

// with different start and end delimiters

r = Eval Insert( "abc[xstring]ghi","[","]" );  // returns "abcdefghi";

When a numeric value contains locale-specific formatting, include the <<Use Locale(1) option. The following example substitutes a comma for the decimal point based on the computer’s locale setting.

Eval Insert( "^1.2^", <<Use Locale( 1 ) );

1,2

Evaluate expressions inside lists

Eval List evaluates expressions inside a list and returns a list with the results:

x = { 1 + 2, 3 + 4 };
y = Eval List( x );   // returns y is {3,7}

Eval List is useful for loading the list of user choices returned by Column Dialog or New Window with the Modal argument.

Evaluate expressions inside expressions

Eval Expr() evaluates only the inner expressions and returns an expression that contains the results of the evaluation. By comparison, Eval evaluates the inner expressions, takes the results, and then evaluates it again.

Suppose that a data table contains a column named X3. Here is an example of using Eval Expr() to evaluate the inner expression first:

x = Expr( Distribution( Column( Expr("X"||Char( i ) ) ) ) );
i = 3;
y = Eval Expr( x );   // returns Distribution( Column( "X3" ) )

To evaluate further, you need to either call the result in a subsequent step, or else put Eval() around the Eval Expr(). The following examples create a Distribution report.

// two-step method

x = Expr( Distribution( Column( Expr( "X" || Char( i ) ) ) ) );
i = 3;
y = Eval Expr( x );
y;
 

// one-step method

x = Expr( Distribution( Column( Expr( "X" || Char( i ) ) ) ) );
i = 3;
Eval( Eval Expr( x ) );

See Table 8.3 to learn what would happen if you tried to use Eval directly on x without first doing Eval Expr.

Parsing Strings into Expressions, and Vice Versa

Parsing is the syntactic scanning of character strings into language expressions. Suppose that you have read in a valid JSL expression into a character string, and now want to evaluate it. The Parse function returns the expression. To evaluate it, use the Eval function.

x = Parse( "a=1" ) ;  // x now has the expression a=1
Eval( Parse( "a=1" ) ); // a now has the value 1

To go in the reverse, use the Char function, which converts an expression into a character string. Usually the argument to a Char function is an Expr function (or a NameExpr of a variable), since Char evaluates its argument before deparsing it.

y = Char( Expr( a = 1 ) ); // results in y having the character value "a=1"
z = Char( 42 ); // returns "42"

The Char function allows arguments for field width and decimal places if the argument is a number. The default is 18 for width and 99 for decimal (Best format).

Char( 42, 5, 2 ); // returns the character value "42.00"

To preserve locale-specific formatting in the numeric value, include the <<Use Locale(1) option as shown in the following example:

Char( 42, 5, 2, <<Use Locale(1) ); // returns the character value "42,00" in the French locale

The reverse of Char is not quite as simple. To convert a character string into an expression, you use Parse, but to convert a character string into a number value, you use Num.

Parse( y );
Num( z );

Table 8.2 Functions to Store or Evaluate Expressions

Function

Syntax

Explanation

Char

Char(Expr(expression))

Char(name)

Converts an expression into a character string. The expression must be quoted with Expr; otherwise its evaluation is converted to a string.

string = char(number, width, decimal)

Converts a number into its character representation. Width and decimal are optional arguments to specify formatting; the default is 18 for width and 99 for decimal.

Eval

Eval(x)

Evaluates x, and then evaluates the result of x (unquoting).

Eval Expr

Eval Expr(x)

Returns an expression with all the expressions inside x evaluated.

Eval List

Eval List(list)

Returns a list of the evaluated expressions inside list.

Expr

Expr(x)

Returns the argument unevaluated (expression-quoting).

NameExpr

NameExpr(x)

Returns the unevaluated expression of x rather than the evaluation of x. NameExpr is like Expr except that if x is a name, NameExpr returns the unevaluated expression stored in the name rather than the unevaluated name x.

Num

Num("string")

Converts a character string into a number.

Parse

Parse("string")

Converts a character string into a JSL expression.

Summary

Table 8.3 compares various ways that you can use the evaluation-control functions with x. Assume that a data table contains a column named X3, and x and i have been assigned:

x = Expr( Distribution(Column( Expr("X"||Char( i ) ) ) ) );
i = 3;

Table 8.3 Functions for Controlling Evaluation

Commands and results

Explanation

x; // or

Eval(x);

Not Found in access or evaluation of 'distribution' , Bad Argument( {"X" || Char( i )} )

Eval(x) and simply calling x are equivalent.

Evaluates the expression distribution( column( expr( "X" || Char( i ) ) ) ). This results in errors. The column name is recognized as "X"||Char(i) because it is packed by the Expr() function.

Expr(x);

x

Returns the expression x (packs an additional layer).

Name Expr(x);

Distribution(Column(Expr("X" || Char(i))))

Returns the expression stored in x exactly as is: Distribution(Column(Expr("X" || Char(i)))).

y=Eval Expr(x);

Distribution(Column("X3"))

Evaluates the inner expression but leaves the outer expression unevaluated, so that y is Distribution(Column("X3")).

y; //or

Eval(Eval Expr(x));

Distribution[]

Eval(eval expr(x)) and simply calling y are equivalent.

Evaluates Distribution(Column("X3")) to launch the platform.

z = Char(nameexpr(x));

"Distribution(Column(Expr (\!"X\!" || Char(i))))"

Quotes the entire expression as a text string, adding \!" escape characters as needed.

Note that Char(x) would first attempt to evaluate x, producing an error and ultimately returning a quoted missing value: "."

Parse(z);

Distribution(Column(Expr("X" || Char(i))))

Unquotes the text string and returns an expression.

a = Parse(Char( NameExpr(x)));

Eval(EvalExpr(a));

Distribution[]

Evaluation control taken to its logical extreme.

Note that you must break this into at least two steps as shown. Combining it into one giant step produces different results because the Eval Expr layer causes the Parse layer to be copied literally, not executed.

Eval(
	EvalExpr(
		Parse(
			Char(
				NameExpr(x)))));

Distribution(Column(Expr("X" || Char(i))))

Want more information? Have questions? Get answers in the JMP User Community (community.jmp.com).