Writing a script
A script is a plain text file of commands, read from its first line to its last or to an end. This chapter gives the rules of the language itself, apart from any one command: how a line is cut into tokens, what a comment is, how parameters, arrays and functions are defined and used, what an expression may contain, the two families of control statements and why they never mix, and how one script includes another.
The command pages say what each command does. This chapter says what holds for all of them. Read it once, then use the command reference for the rest.
Lines and tokens
James reads a script as a stream of tokens. A token is a word, a number, a name, a bracketed expression, a punctuation mark or a comment marker.
- Whitespace. Spaces, tabs and line breaks separate words. A command may run over many lines, and many commands may share one line.
- Self-delimiting characters. The characters
{,},(,),,and=are tokens by themselves, with or without space around them. So is==, which James matches ahead of=. This is whyif(%a==1)thenanddef sq(x)=x*xneed no spaces. The six equation commandsx=,y=,z=,t1=,t2=andt3=are the one exception: each keeps its=as part of its name. James does not run them in this version and reports E0105. Compatibility says which milestone builds them. - Line breaks. James ignores them, with two exceptions. A comment ends at its line break, and so does the text of a raw-text command such as
title. Inside a[ ]expression, a line that ends in&continues on the next line. - The semicolon. A
;closes an argument list, and a list that is empty still needs its;. A;typed after a list is already closed is ignored. James never reports one, so closing every list with;is always safe. - Numbers. A number may be written with a sign, a decimal point and an exponent, as in
-2.5e-3. James reads a real number up to the limit of a double precision value, and refuses one that is not finite with E0213. An integer wanted by a command, such as an index, may be at most 2147483647 in size. A larger one is E0212. - Names. A parameter name may hold any printable character except
+ , % * / ^ ( ) [ ] = & $. A name with one of those is E0216. Names are compared in full and without regard to case, soWidthandwidthare one parameter. A-and a.are legal inside a name. - Case. Keywords match without regard to case.
BLOCK,Blockandblockare one command, and so are.EQ.and.eq.. - Aliases. A few commands have a second spelling.
parameterispara,cyliiscylinder, and insidesdthe wordscyliandsphestand forcyandsp. - No colon shorthand. A list such as
1:4is not read as a range. James reports it as a malformed entry (E0104). Write the numbers out. - A word James does not know. A word in the place of a command that is not one is E0105.
Comments
James drops a comment before it reads anything else.
cas a word of its own starts a comment that runs to the end of the line. The word must stand alone:cylinderis a command, andcinsideabcis part ofabc.$starts a comment to the end of the line, alone or at the start of a word.#does the same. It is James’s own marker, and#noteis a comment with no space after the#.{and}enclose a block comment that may run over many lines. Blocks nest, so a block may hold a commented-out block of its own. A{that is never closed is E0210, reported at the{with the nesting depth.
A parameter cannot be called c, because the word starts a comment.
Some commands take raw text, which James never reads as script. The text of title, echo, becho, comment and the text after caption cap (caption) runs to the end of the line exactly as written. A $, a #, a c, a {, a %name or a [expr] in that text is only text. A verbatim block does the same for every line up to endverbatim. After caption on or caption off there is no raw text, so a trailing comment is a comment.
Parameters, arrays and functions
A parameter is a number with a name. You define it once and use it anywhere a command wants a number.
Defining and reading a parameter
para gives names values: para width 4 height 2.5;. The ; after the last pair is required. A name may be redefined, and the new value replaces the old one. James reads the script from top to bottom, so a name must have a value before a line reads it.
%name reads a parameter, and [expr] computes a value in place. Both may stand wherever a command wants a number. A [expr] may read parameters, call functions and use an expression as an array subscript, written without brackets, as in %xs(%i+1,2).
A %name reference takes every character that may be in a name. It ends at a space, at one of the self-delimiting characters, or where a dotted operator such as .eq. begins. It does not end at a -. So %a-1 is the one name a-1, and %a.eq.1 is the name a followed by .eq. and 1. To subtract, put spaces around the sign: [%a - 1]. If only a is defined, para b [%a-1]; is E0203, because a-1 has no value.
James turns every %name and [expr] into a plain number before the command that holds it is read. --dump-prep shows the result.
A value that has to be an integer, such as an index, is cut toward zero: 2.9 becomes 2 and -2.9 becomes -2.
Arrays
array declares a parameter with several values: array xs(2,2) 0 1 2 3;. The subscripts count from 1, and the data fill the array in Fortran order, the first subscript varying fastest. One value is given to every element, and no value leaves every element unset. You read an element as %xs(1,2) and assign it with para xs(1,2) 7;. A subscript may itself be an expression, written without brackets: %xs(%i+1,2). A subscript outside its dimension is E0206, and the wrong number of subscripts is E0207.
Functions
def defines a function: def area(w,h) = w*h. The arguments of the body are written without a %, and a parameter is written with one, so the two cannot be confused. An argument may have the same spelling as a built-in such as max. A call must give as many values as the function takes, or James reports E0208. You call it inside brackets, as in [area(2,3)].
Listing what is defined
painfo prints a note for every parameter, array element and function in the order each was first declared, and then every automatic parameter that has a value.
Automatic parameters
James supplies some parameters itself. You read them and never assign them: a para, an array or a for that names one is E0205.
| Name | Meaning | When it has a value |
|---|---|---|
pi | 3.14159 and more digits | always |
nextprt | the next unused part number | from the start, and updated when a part opens |
nextsrf | the next unused surface number | from the start, and updated by each sd |
nextln, nextld | the next unused 2D curve number (two names for one value) | from the start, and updated by each ld |
nextcrv | the next unused 3D curve number | from the start, and updated by each curd |
maxrudi, maxrudj, maxrudk | the largest reduced index in each direction | when a part opens |
mxiridx, mxjridx, mxkridx | the largest full index in each direction | when a part opens |
idxlist, jdxlist, kdxlist | arrays that hold the part’s full index lists | when a part opens |
The last three rows belong to a part, so read them from inside one. After endpart they keep the last part’s values until the next part opens. Before the first part opens they have no value, and a reference is E0203.
James knows a second set of names and never gives them a value in this version. They are nextnode, nextlbrick, nextbb, nextlshell, nextlbeam, nextqbeam, nextqbrick, nextqshell, nextlc, nextmat, distance, inprod, subang, xprj, yprj, zprj, xnrm, ynrm, znrm, xcrprod, ycrprod and zcrprod. Reading one is E0203, the same as a name nobody defined.
James can never supply node and xboxmin to zboxmax, because they belong to a screen and a mouse that James does not have. Reading one is E0204. Any other name nobody defined is E0203.
Expressions
An expression is a Fortran-like formula. It appears inside [ ], in the body of a def and in the test of an if, elseif, when, elsewhen or while. A test may not hold a [ ] of its own, and one that does is E0221.
All arithmetic is in floating point, even when every number looks like an integer, so [10/4] is 2.5.
Operators
Most operators have two spellings. The table runs from the operator that binds tightest to the one that binds loosest.
| Level | Operators | Notes |
|---|---|---|
| 1 | ** or ^ | Right to left, so 2**3**2 is 512. |
| 2 | unary + and - | -2**2 is -4 and -2*3 is -6. |
| 3 | * and / | |
| 4 | binary + and - | |
| 5 | .gt. or >, .ge. or >=, .lt. or <, .le. or <=, .eq. or ==, .ne. or != | A true comparison is 1 and a false one is 0. |
| 6 | .not. or ! | |
| 7 | .and. or & or && | |
| 8 | .or. | No symbol. |
| 9 | .eqv. and .neqv. | No symbol. |
Parentheses override the table. The symbol | is not an operator, and James reports it as an expression error (E0221).
Two numbers that differ only by roundoff are equal. James calls a and b equal when their difference is within a relative tolerance of 1e-12 of the larger of the two, or within 1e-30 of each other. So [1/3*3 == 1] and [1e-31 == 0] are both true. The other five comparisons agree with it: a pair that is equal is never also greater or less than itself.
Functions
| Function | Meaning | Where it is not defined |
|---|---|---|
int(x) | cut toward zero | |
nint(x) | round to the nearest integer | |
abs(x) | absolute value | |
mod(a,b) | remainder of a/b | b is 0 |
sign(a,b) | the size of a with the sign of b | |
max(x1,...), min(x1,...) | the largest, the smallest | |
sqrt(x) | square root | x is negative |
exp(x) | e to the power x | x is above 85.19 |
log(x), log10(x) | natural and base 10 logarithm | x is 0 or negative |
sin(x), cos(x) | sine and cosine | |
tan(x) | tangent | x is 90 plus a multiple of 180 |
asin(x), acos(x) | inverse sine and cosine | x is outside -1 to 1 |
atan(x) | inverse tangent | |
atan2(y,x) | inverse tangent of y/x | both are 0 |
sinh(x), cosh(x) | hyperbolic sine and cosine | |
rand, norm | random numbers, below | norm with a sig of 0 or less |
Every angle is in degrees, in and out. [sin(90)] is 1, and [atan2(1,1)] is 45.
An illegal operation does not stop the script. James prints a warning, E0211, and the result is NaN. Division by zero is illegal in the same way. A NaN that reaches a command that wants a number is then an error there.
rand draws a value from a spread of one unit around a mean that is 0 unless you give one: rand, rand(seed) and rand(seed,mean). norm draws from a normal distribution: norm, norm(seed), norm(seed,mean) and norm(seed,mean,sig), where sig is the standard deviation and is 1 unless you give one. A call with a seed different from the one in force starts the sequence again from that seed. Any other call goes on with it. Run twice, a script draws the same numbers twice. They are James’s own sequence, and they do not match the numbers any other program draws from the same seed.
Control statements
James has two families of control statements. They look alike and are read by different stages.
| Family | Statements | Read by | Where it may stand |
|---|---|---|---|
The if family | if, elseif, else, endif | the command parser, as it reaches each one | between commands |
| The preprocessed family | when, elsewhen, else, endwhen, for, endfor, while, endwhile, break | the preprocessor, before the command parser sees any of the text | between commands, and for when and for inside one command’s arguments |
The word else belongs to both. James takes it for whichever construct is open.
The two families never nest in each other. A family if inside a when, for or while, or one of those inside an if, is E0306, in either direction. The reason is the order of work. The preprocessor settles a when, for or while first and hands its text on. The command parser settles an if later, as it reaches it. Inside a loop, use a when where you would have used an if. The rule also holds when the if arrives through an include, as the third example below shows.
Within a family, constructs nest freely, and two may never cross. One scope sits wholly inside another or wholly apart from it. A crossing is E0307.
Every statement of both families stands alone on its line, and a statement that shares its line with anything else is E0309. The if, elseif, when and elsewhen tests need a then (E0317). A while takes none (E0318).
The if family
James tests an if when it reaches it, in script order. It reads the branch of the first true test, or the else branch, and skips the rest. A skipped branch is not read at all. A para in it never runs, an include in it never opens its file, and a name it uses never has to exist. A guard that comes after a branch has been taken is not tested.
An if is closed by exactly one endif in the file that opened it. An endif with no if is E0300, and an if that its file ends without closing is E0319. An endif inside a when, for or while that has no if of its own is also E0300.
An if cannot be a part of one command’s arguments. It is a command, and it stands between commands.
The preprocessed family
The preprocessor tests each when guard in order and passes on the text of the first branch that is true. A for repeats its body once for each value of its counter, and a while repeats its body while its test is true. All three are gone from the stream by the time the command parser reads it. --dump-prep shows the unrolled text.
A when or a for may sit inside one command’s argument list, to choose or repeat a piece of it. A while may not, and James reports one that does as E0321.
A for runs MAX(FLOOR((end - start) / step) + 1, 0) times, worked out once when the loop opens, so a real step is as legal as an integer one. A step of 0 is E0311. break leaves the innermost for or while. It is E0308 outside both.
The limits are these.
- Constructs of the preprocessed family nest to 64 levels in all. The 65th is E0310.
- The
iffamily nests to 64 levels of its own. The 65th is E0320. - A loop may run 1,000,000 passes. A
forthat would run more runs none, and awhilethat reaches the limit stops there. Both are E0312, and the count is per loop.
A for, when or while must close in the file that opened it (E0319). A closer with nothing to close is E0301 for endfor, E0302 for endwhen and E0303 for endwhile. A closer that names the wrong kind of construct is E0316.
Including a file
include reads another script at that point: include inner.inc. The file name is the run of tokens written right after the word with no space between them. A %name or [expr] in it is replaced by its value, cut to an integer, so include part[%n].inc reads part2.inc when n is 2.
- A relative name resolves against the directory James was started in, at the moment the
includeis read, at every depth. It is not found from the including file’s own directory. A file that cannot be read is E0200. - Includes nest to 32 files.
- A file that is already being included is refused, so two files that include each other stop at the second
include(E0202). The depth limit is E0201. - A parameter, array or function defined in an included file belongs to the whole script. It is still defined when the file ends.
- An
if,for,whenorwhileopened in an included file closes in that file. Aparaor anincludein a branch that is not taken does nothing. - Every message names the file and line that holds the text, so an error in an included file points into it.
The session commands
These commands act on the run as a whole and not on a part. Each is legal in any phase.
| Command | What it does | Page |
|---|---|---|
echo | Prints its text on standard output as James reads the script. | echo |
becho | The same as echo. James sends no bell. | becho |
title | Sets the title the mesh file stores. A second title replaces the first. | title |
caption | Accepted and ignored, because it labels a picture. | caption |
comment | Accepted and ignored. | comment |
verbatim | Opens a block of raw text that James discards, up to endverbatim. | verbatim |
painfo | Prints every parameter, array and function. | painfo |
include | Reads another script file in place. | include |
errmod | Chooses whether James stops at the first error and whether it prints warnings. | errmod |
mxp | Sets the most passes James makes when it meshes a part. | mxp |
intyp | Chooses the interpolation James uses where the script names none. | intyp |
interrupt | Accepted and ignored. | interrupt |
resume | Accepted and ignored. | resume |
end | Stops reading the script. | end |
echo prints in script order, as James reads, and before any part is meshed, because the engine meshes only after the whole script has been read. A script that echoes “meshing part 2” shows it before any part exists.
Every command that only draws a picture is accepted, its line is discarded and nothing is printed. Compatibility lists them all. A script written for an interactive session therefore runs to its end.
Examples
Each script below is a case that make test runs. The text after each one says what James printed or stored when it ran.
Raw text protects its characters
The script tests/corpus/127-caption-cap-raw-text-protected/input.tg:
caption cap A rod c with $ signs
caption on $ trailing comment
The $ signs and the c after caption cap are text, so the prep dump keeps the line as caption cap A rod c with $ signs. The $ after caption on starts a comment, and the dump shows caption on alone. James prints no message.
A block comment that never closes
The script tests/corpus/126-brace-comment-unterminated-rejected/input.tg:
merge
{ a comment that is never closed
write
James prints this, which tests/corpus/126-brace-comment-unterminated-rejected/expected.diag.txt holds:
input.tg:2:1: error [E0210]: unterminated comment block, opened here (nesting depth 1)
james: 1 error, 0 warnings
The { on line 2 is never closed, so everything after it is a comment. James reports E0210 at the {. The write on line 3 is part of the comment and never runs.
Where a name ends
The script tests/corpus/008-hyphenated-name-not-subtraction/input.tg:
para a-1 5;
para result [%a-1];
painfo
James prints this, which tests/corpus/008-hyphenated-name-not-subtraction/expected.diag.txt holds:
input.tg:3:1: note: painfo: a-1 = 5
input.tg:3:1: note: painfo: result = 5
input.tg:3:1: note: painfo: pi = 3.141592653589793 (automatic)
input.tg:3:1: note: painfo: nextprt = 1 (automatic)
input.tg:3:1: note: painfo: nextsrf = 1 (automatic)
input.tg:3:1: note: painfo: nextln = 1 (automatic)
input.tg:3:1: note: painfo: nextld = 1 (automatic)
input.tg:3:1: note: painfo: nextcrv = 1 (automatic)
The - is part of the name, so %a-1 reads the parameter a-1, and result is 5 and not 4. painfo lists the two parameters in the order they were declared, then the six automatic parameters that have a value before any part opens.
An illegal operation
The script tests/corpus/014-illegal-expression-nan-warning/input.tg:
para y [sqrt(-1)];
painfo
James prints this, which tests/corpus/014-illegal-expression-nan-warning/expected.diag.txt holds:
input.tg:1:9: warning [E0211]: SQRT(-1) is not defined for these arguments; result is NaN
input.tg:2:1: note: painfo: y = NaN
input.tg:2:1: note: painfo: pi = 3.141592653589793 (automatic)
input.tg:2:1: note: painfo: nextprt = 1 (automatic)
input.tg:2:1: note: painfo: nextsrf = 1 (automatic)
input.tg:2:1: note: painfo: nextln = 1 (automatic)
input.tg:2:1: note: painfo: nextld = 1 (automatic)
input.tg:2:1: note: painfo: nextcrv = 1 (automatic)
james: 0 errors, 1 warning
The square root of -1 is not defined. James prints the warning E0211 at the [, stores NaN in y, and goes on. The run ends with 0 errors and 1 warning.
An array in a coordinate list
The script tests/corpus/159-array-elements-in-coordinates/input.tg:
c a 2 by 2 array whose four elements, read in Fortran order, give one coordinate list
array xs(2,2) 0 1 2 3;
block 1 2 3 4;1 2;1 2;
%xs(1,1) %xs(2,1) %xs(1,2) %xs(2,2);
0 1;0 1;
endpart
The data list 0 1 2 3 fills the 2 by 2 array in Fortran order. The prep dump shows the four %xs(i,j) reads replaced by 0 1 2 3;, the block’s i coordinate list.
A loop with a real step
The script tests/corpus/022-for-noninteger-step/input.tg:
for x 0 1 0.3
para y [%x*10];
endfor
The step is 0.3 and the end is 1, so the trip count is FLOOR((1 - 0) / 0.3) + 1, which is 4. The prep dump shows para y 0;, para y 3;, para y 6; and para y 9;, and no for or endfor.
A when inside a command
The script tests/corpus/113-elsewhen-selects-branch/input.tg:
para x1 2;
para x2 5;
block
when(%x1.gt.%x2) then
1 3;1 3;1 3;
elsewhen(%x1.lt.%x2) then
1 2;1 2;1 2;
else
1 4;1 4;1 4;
endwhen
1 2 1 2 1 2
endpart
The when stands between the block word and its last index list, so it chooses a piece of the command. x1 is 2 and x2 is 5, so the first guard is false and the elsewhen guard is true. The prep dump shows block with 1 2;1 2;1 2;, and the other two branches are gone.
A while cannot do the same
The script tests/corpus/136-while-embedded-rejected/input.tg:
para n 0;
block
while(%n.lt.3)
para n [%n+1];
%n
endwhile
;1 2;1 2;0 1 2;0 1;0 1;
endpart
James prints this, which tests/corpus/136-while-embedded-rejected/expected.diag.txt holds:
input.tg:3:1: error [E0321]: while cannot be embedded in another command
james: 1 error, 0 warnings
The while sits inside the argument lists of block. James expands its three passes and then reports E0321 at the while, on line 3.
An if reached through an include
The script tests/corpus/157-if-in-loop-through-include/input.tg:
for i 1 2 1
include cond.inc
endfor
block 1 2;1 2;1 2;0 1;0 1;0 1;
endpart
It includes cond.inc, which holds a whole if construct:
if (1 .gt. 0) then
endif
James prints this, which tests/corpus/157-if-in-loop-through-include/expected.diag.txt holds:
cond.inc:1:1: error [E0306]: an if scope must be disjoint from every when, for, and while scope
input.tg:1:1: note [E0306]: 'for' opened here is still open
cond.inc:1:1: error [E0306]: an if scope must be disjoint from every when, for, and while scope
input.tg:1:1: note [E0306]: 'for' opened here is still open
james: 2 errors, 0 warnings
The if is in another file, but it is still inside the for. James reports E0306 once per pass, with a note that names the for that is still open. The loop ran twice, so the message appears twice.
Two files that include each other
The script tests/corpus/130-include-cycle-rejected/input.tg:
include a.inc
a.inc holds merge, neutral and include b.inc. b.inc holds include a.inc and write. James prints this, which tests/corpus/130-include-cycle-rejected/expected.diag.txt holds:
b.inc:1:9: error [E0202]: include cycle detected: 'a.inc' is already being included
james: 1 error, 0 warnings
James reads a.inc, then b.inc, and refuses the include a.inc inside it, because a.inc is already being read. The prep dump holds merge, neutral and write: the lines before the refusal and the write after it.
See also
- Commands: every command, with the page for each statement named above.
- Diagnostics: every message, by code.
- Concepts: the ideas the script language serves.