TrainPlayer Programming Language v. 8.3

The vocabulary and grammar you use to write scripts is called TrainPlayer Programming Language, or TPL.  It recognizes a given list of commands and keywords -- some related to trains, such as setting speed and direction, and some standard programming constructs, like loops, ifs, and branches.  It allows you to create and substitute variables, and to separate frequently-used code into subroutines and procedures.  It is rudimentary but reasonably complete.

This document gives an overview of the components of the language.  It does not attempt to teach you programming, or even give you much more than a brief overview and a command reference.  The main documentation is built into the program, and can be found on the Reference tab of Script Central -- the same table of commands and variables as shown below, but with more detail and examples under each topic.

Further reading matter can be found on the Scripting page of the TrainPlayer web site. Of special note are two PDF documents giving a wealth of expert-level discussion and details:

Additions and changes for Version 8.1 are noted in the text below.  Version 8.3 (2024) added these features:

For complete information, see the Reference tab in Script Central.

TPL Overview

A script consists of a series of statements.  Some are comments -- text provided by the script author to annotate or explain -- and are skipped over when the script is executed.  Others are either executable statements, which cause actions to occur, or wait conditions, which cause the script to pause for some specified event or time period before proceeding.  The text in the script is sometimes referred to as code, and the act of creating it is called coding.

An executable statement begins with a command -- one of the words or phrases recognized by the language, such as those listed below -- followed by arguments providing data needed by the command. An argument might consist of a single string or numeric value, or an expression formed by combining multiple values using arithmetic operators. In place of a literal string or numeric value, you can use the name of a variable, and it will be replaced by the corresponding value when the statement is executed.

Statements are normally executed one after another as they appear in the script.  However, this behavior can be altered using flow commands.  A loop is a block of statements which are executed repeatedly a given number of times.  A branch is a jump to another place in the script, sometimes based on a decision made in an IF statement. 

A call is a jump to a separate external block of statements; these execute in their entirety, and then control jumps back to the statement after the call.  There are three variations on this.  A subroutine is a block of script code stored in a text file; a call to the subroutine executes the file and then returns.  A proc is a small routine stored between two tags within another script; the first time it is encountered, as its parent script executes, it is extracted and saved in memory under a specified name; subsequently it can be called using this name.  A function is a body of code which carries out a calculation and returns a result to be used by the calling script.

Whenever a train script or junction action is running, it is connected to a train.  Commands in the script which do not specify otherwise are directed to this train.  Train control commands can be used to redirect commands from the script-owning train to another one.

Variables are created and assigned using a Set statement, or the equivalent Let.  This takes the name of a variable and an expression; the expression is evaluated and its value assigned to the variable.   If the name is new, the variable is created, otherwise the existing variable is updated. 

TPL Statements

A script consists of a series of statements, usually one per line in a text file. Lines starting with * are comments, and are not executed.  By default, comments starting with a single asterisk are echoed to the output window as the script runs; those starting with ** are not.  Blank lines are ignored.

8.3 Update: multiple lines can be enclosed in /* ... */ to comment the entire block.

An executable statement consists of a command or wait condition name -- one of the words in the first column of the table below -- followed by any required arguments.

Multiple statements may be concatenated on a single line, using semicolon (;) as delimiter.  As a special case, a wait statement may be followed immediately by a command on the same line.

Built-in Commands

A command is an instruction telling the program to take some action.  There are two types: commands accessible from the program menus vs. those built into the language.  The menu commands are described in the next section.  The built-in commands are in these categories:

Most commands consist of a single verb followed by one or more arguments telling what to act on or what to do.  In the table below, arguments are indicated by angle brackets -- you supply the value, not the brackets.  Optional arguments are enclosed in braces; if omitted, these take on default values.

speed 20 set speed of script owner to 20; start moving if stationary
echo "note to user!" display message in output window
throw J35 1 throw switch at junction 35 to position 1
drive "Broadway Limited" begin block of statements to be directed to another train

A complete list of commands is given below.  A better one is found in the Reference tab of Script Central.

Menu Commands

When you select any command from a menu, you see a cascading series of names starting with a top-level menu.  Write these names down one after another and you have a command you can use in a script.  Menu names must be separated by spaces, multi-word names must be quoted. 

Version 8.1 update: two special menu commands can now take an additional argument: File Open and File Save As can be followed by a full pathname, or a path relative to the layout owning the script.  If no filename is given, the file dialog comes up as before.

Examples:

file open bring up Open File dialog
file "open layout" bring up Layout Chooser; note quotes required around two-word command
file open mylayout.rrw open named file; simple filename must be in same folder as current layout
file "save as" new.rrw save current layout to specified filename
train "add car" boxcar add car to selected train at insert position; note menu command applies to selected train, not script owner

Wait Conditions

A wait condition tells the script to wait for a certain event or until a certain time before proceeding.  There are only a few wait condition keywords, serving multiple purposes: AT a certain time on the clock, or when the first car of the train reaches a certain junction or track label; AFTER an elapsed time, or when the last car passes the given location; or ON specified events.

at J35 stop when first car of train crosses junction 35, stop train, continue with script
after 0:0:02 speed 20 wait 2 seconds, then set speed to 20

Flow Control

Script statements are normally executed one after another.  Commands in the "flow control" category change this behavior, by jumping to other parts of the code based on some criterion.  The following types of flow control are available:

goto nextPart jump to statement "nextPart:"
call sitandwait 10 call subroutine "sitandwait.txt" and pass one argument

If Statement

An IF statement takes the form

if (val1 comp-op val2)
    code block 1
[else
    code block 2]
endif

where val1 and val2 are string or numeric values, variables, or expressions, and comp-op is a comparison operator, one of:

=  <  >  <>  <=  >=

The two values are compared using the given operator; if the result is TRUE, then code block 1 is executed; if FALSE, code block 1 is skipped, and control passes to the optional code block 2 or, if none, to the statement after the endif.

let ncars = $train(train1, length) create a variable and assign value from a system function
if (ncars < 10)
    echo "short train"
compare value to literal; if true, output this text
else
    echo "long train"
if comparison is false, output alternate text instead
endif  

While statement

A while statement defines a loop.  It takes a form similar to IF:

while (val1 comp-op val2)
    code block
endwhile

where the code block executes once every time the comparison expression evaluates to TRUE; if it is FALSE, the code block is skipped and control passes to the statement after the endwhile.

let i = 1 create variable and initialize
while (i < 10) compare variable to literal; if true, go to next statement
   echo Line @i 
   let i = i + 1
from here, jump back to start of loop
endwhile when comparison is false, jump to here

Use a CONTINUE statement within a loop to go to the next iteration, or a BREAK statement to jump out and terminate the loop.

Let/Set statement

Set/Let statements take the form

set var expr
let var = expr

where var is the name of a new or existing variable, and expr is an expression, one or more components joined by operators:

comp [op comp [...]]

If expr is omitted, then the variable is deleted and removed from further use -- let x=  undefines the variable x if previously defined.

A component is one of:

Op is an arithmetic operator.  Between two numeric components, it can be one of these four:

+  -  /  *

Between strings, only the + operator is used, and means concatenation of strings.

Examples:

let v = (2 + 6) / 12
let v2 = v + 99
set AllowYardMode 0
let s = "speed is " + $x_speed
let myvar =

Variables and Functions

A variable is a named value.  You can make up a name, assign it a value, then use the name in place of the value in a script statement.  This is the key to writing abstract code which can work on different input data.

To create a variable and/or assign it a value, you use a LET or SET statement.  To represent it in a script statement, you normally* precede it with "@" -- a symbol you can read as "contents of," which identifies what follows as a variable name.

 Example:

let CruisingSpeed = 55
...
at j22
   speed @CruisingSpeed

* You don't need to precede a variable name with @ when you refer to it in a LET or SET statement.  If you want to embed a variable's contents into a larger string, use @ on both ends:

let s = "def"
echo The Alphabet: abc@s@ghi...

As of Version 6.3.1, you can edit a variable's value in the Reference tab of Script Central; see Editing Variables.

A system variable or function is a value supplied by the system.  You call for a system value by giving a function name, often followed by a list of arguments in parentheses.  An argument might be the id of an object, a keyword requesting a specific action or value, or an input value for a calculation or operation.  Sometimes you don't get a value back, you just call the function to do some work, using a CALL statement. 

System function names begin with "$".  A list of them is given below, and also in the Reference tab of Script Central.

 Examples:

echo Today's date is $DATE
echo Name of active layout is $LAYOUT(Name)
let r = $RAND(1,100)
echo A random percentage -> @r

let storedData = $READ("C:\myfile.txt")
let outputData = storedData + "additional info"
call $WRITE("C:\myfile_new.txt", @outputData)

A function family is a set of related functions with the same name, and a list of arguments which usually includes the identifier of an object and/or a keyword representing a specific value.  Several function families provide access to objects on the layout -- cars, trains, tracks, junctions, the layout itself -- while others work with general objects, like strings and files. 

Some functions are "writable," meaning their values can be changed in LET or SET statements.  Writable functions are indicated by "r/w" in the Reference listing in Script Central.

Example:

echo The load in box 44 is $CAR(x44, Loadname)
let newLoad = "Toy Tractors"
echo Changing load to @newLoad!
set $CAR(x44, Loadname) = newLoad

A local variable is one which is inaccessible outside of a certain limited region of code (or "scope").  Normally, when a variable is defined and assigned a value, it is "global" -- it can be referenced by any scripts or actions anywhere.  But if it is named in a preceding LOCAL statement, then it is known only to the script (or subroutine or proc) where it is defined.  Example:

* train script 1
let gv = "a global value"
local lv
let lv = "a local value"

* train script 2
echo global value was defined as: @gv
echo value local to script 1 is undefined here: @lv

An excellent discussion with detailed descriptions of each system function can be found on the web in TrainPlayer 6.1 - Extensions to the Trainplayer Programming Language (TPL).

Subroutines

A subroutine is a named body of code stored in a file outside a script.  The code can be executed within the script by inserting a CALL statement including the name of the file. 

To pass data into and out of the subroutine, one or more arguments may be included in the call.  Arguments are numbered from left to right in the call, and are represented within the subroutine as placeholders starting with "%".  For example, wherever the subroutine contains "%1", it will be replaced by the first argument in the call.

Here is a simple example of a subroutine which takes a single argument.  It doesn't do anything particularly useful, but the point of making it into a subroutine is that it could be made fancier if needed.

* subroutine sitandwait <secs> comment at first line of subroutine tells how to call
echo sitting for %1 secs... using passed arg in a string
after 0:0:%1 wait number of seconds given by arg 1, then return

These lines become a subroutine if stored as a text file sitandwait.txt in the TP Scripts folder.

A proc is a type of subroutine which is not defined in an external file, but in a section of another script.  Using proc and endproc statements within a script, you define a callable block of code and give it a name.

proc sitandwait defines proc and gives it a name
    echo ... content as above
    after 0:0:%1  
endproc ends definition

This becomes callable once the script containing it is run.  Including it within the master script guarantees this.

If either of the above is available, a script can then say

call sitandwait 5

to cause 5 seconds of idleness.

Display Strings

The term "display string" (DS) means a lengthy string for conveying information to the user or to a file. Each of the following commands takes a ds as argument, and all process it in exactly the same way:

Display strings:

Special $MSGBOX feature introduced in Version 8.3: this box is now "pseudo-modal," which means you no longer need to dismiss it in order to continue interacting with the program.  The msgbox stays up while you work, until you click OK.  This happens only when you do not supply a type argument (and are thus not waiting for a result).

Quotes in TPL

Our general philosophy is: quotes are not required (except in certain situations listed below), but may be used if desired. Programmers used to formal languages tend to prefer quoted strings. Thus either of these is valid:

echo this is a string to be output
echo "this is a string to be output"

In a display string, variables are substituted whether or not the string is quoted. Examples:

echo the speed of the crossing train is $train($x_train, speed)
echo "today is $date and it is already $time"

Quotes are optional around most function arguments and keywords. Thus both of these work:

echo the speed is $train(train66, speed)
echo the speed is $train("train66", "speed")

Where quotes are required:

1. Around a multi-word command, registry setting name, or filename -- that is, any of these which contains a space. In a menu, this applies only to a single level of a cascading menu. Examples:

file open "c:\my file.txt"   <== filename with space
file "revert to saved"       <== menu command
set "Default Car Length" 400 <== reg setting name

3. Around a string containing a parsable character, where the meaning of that depends on the context. In a function call, an unquoted string must not contain the character ")", because that would terminate the call. In a comma-delimited list, a string must be quoted if it contains comma. Examples:

call $write(c:\myfile.txt, a string with ) paren)   <== WRONG
call $write(c:\myfile.txt, "a string with ) paren") <== OK

4. Around a multi-line text block. This is the only way to define such a block, by enclosing it in double quotes and including carriage returns:

echo "this is a string
  with multiple lines

   and a blank line"

Note that if you want the string itself to include a quote, you must double it:

echo "string with embedded "" quote"


Table 1.  TPL Command Reference

This table shows the command vocabulary with brief descriptions.  For a more complete and up-to-date listing, consult the Reference tab in Script Central.  Contents of that tab can be exported for printing if desired.

Flow Commands  
Goto <label> jump to statement after given label 
Autopause <secs> turn on pauses of given duration during train movements
If (<expr> <comp-op> <expr>) compare two expressions, branch based on result
Else begin alternate block after IF
Elseif  (<expr> <comp-op> <expr>) evaluate alternate after IF
Endif end IF block
While (<expr> <comp-op> <expr>) compare two expressions, execute or skip next block based on result
Endwhile end WHILE block
Break break out of WHILE loop
Continue continue to next iteration of WHILE loop
Exit terminate running script
Layout Commands  
Throw <jxn> [<pos>] throw switch to given position or next available
Rotate <ttbl> <jxn> [CCW] rotate given turntable to junction, clockwise unless CCW added
Tablestop stop turntable rotation 
Sound [loop|stop] <spath> play or stop sound in named wav file.  If LOOP omitted play once.
Set Commands  
Set <var> <expr> set variable to value; equivalent to LET <var>=<expr>
Reset <var> reset registry variable to stored value
Let <var>=<expr> set given variable to value of expression; create variable if needed
Subroutine Commands  
Call <routine> <arglist> call given subroutine or proc and pass any required arguments
Proc <routine> begin definition of procedure of given name
Endproc end procedure definition
Local <var[,var...]> declare local variables
Return return from subroutine or proc to calling script
Train Commands  
Forward set direction to forward
Reverse set direction to reverse
Speed <mph> set speed to given mph; starts train moving if stopped
Stop decelerate to a stop
Uncouple <slot> OR <car> OR <car><car>  uncouple at given position, at given car, or between given cars
Horn [<msec>] sound train horn for 3 sec or given time in millisec
Train Control Commands  
Train <train> select given train 
Load [Toggle] [Car/Train/Cut] <ids> [<loadname>] load given car(s) with given load or default
Unload [Toggle] [Car/Train/Cut] <ids> unload given car(s) 
Drive <train> transfer control temporarily to given train 
Enddrive transfer control back to original train
Start <train> [<mph>] start train script, or start train moving at 5 mph or given speed
User Interface Commands  
Echo <string> display given string in schedule window 
Input <var> [<prompt>] set user variable from input dialog, optionally using given prompt
Note [<string>] display given string in popup window; hide window if no string
Notebox [<string>] display string in window dismissable with ESC or click
Car Functions  
$CAR(label, <any>) custom property; set blank to remove
$CAR(label, AAR) car aar code
$CAR(label, Car_ID) id of car
$CAR(label, Class) general car class
$CAR(label, Consist) one-word description of train consist
$CAR(label, Dest) user- or ops-applied destination tag on car
$CAR(label, DistTo, label2) distance in rru from center of this car to car "label2"
$CAR(label, ExcludeOps) X => exclude car from switchlists
$CAR(label, ID) numeric car id
$CAR(label, IsSpotted) 1 if car is at assigned dest, otherwise 0
$CAR(label, Label) car label
$CAR(label, Layout) layout name where car is located
$CAR(label, LayoutID) layout ID where car is located
$CAR(label, Load) current load or 'unloadable' or 'empty'
$CAR(label, Loaded) 1 if car is loaded, otherwise 0
$CAR(label, Loadname) name of load assigned to car, if any
$CAR(label, Location) track label or track number underneath car
$CAR(label, Note) user-applied note on car
$CAR(label, Position) trackno, dist to S end, 1=>headed S
$CAR(label, PropsStr) prop string used by scripts
$CAR(label, ShowLabel) 1 to show, 0 to hide
$CAR(label, SortOrder) user-applied integer value
$CAR(label, Track) track number where car is currently located
$CAR(label, Train) name of owning train
$CAR(label, Train_ID) id of owning train
$CAR(label, Type) name of car type within collection
Train Functions  
$TRAIN(name, <any>) custom property; set blank to remove
$TRAIN(name, Car) label of car i, i between 0 and NCars-1
$TRAIN(name, CarIDs) set of car id's in train
$TRAIN(name, CarLabels) set of car labels in train
$TRAIN(name, Direction) F = forward, R = reverse, T = toggle
$TRAIN(name, Flip) invert train on track
$TRAIN(name, ID) numeric train id
$TRAIN(name, Layout) layout name where train is located
$TRAIN(name, LayoutID) layout ID where train is located
$TRAIN(name, Length) length in cars; returns same value as NCars
$TRAIN(name, Name) train name, either user-assigned or Train<id>
$TRAIN(name, NCars) number of cars in the train
$TRAIN(name, Speed) current speed, same as returned by $SPEED
Layout Functions  
$LAYOUT(Active) 1 if layout is active document window
$LAYOUT(Description) text notes as shown in layout props dialog
$LAYOUT(IDSet, type) set of id's of given type
$LAYOUT(JxnLabels) space-delimited list of all junction labels
$LAYOUT(LayoutID) layout id
$LAYOUT(Name) layout name
$LAYOUT(NTrains) number of trains on the layout
$LAYOUT(Path) rrw file pathname
$LAYOUT(ReadOnly) does rrw file open for read-only
$LAYOUT(RoadStyleFromGlobals) copy global settings to layout roadstyle
$LAYOUT(SaveVars) does saving to rrw file save variables
$LAYOUT(TrackLabels) space-delimited list of all track labels
$LAYOUT(Train) name of train i, i between 0 and NTrains
Track Functions  
$CAPAC(loc, mode) number of car spaces at location; mode=all/free/occupied
$JXN(id, Label) user-applied label
$SCENERY(id, Hide) hide scenery object
$SCENERY(id, IsVisible) 1 if scenery object is showing, else 0
$SCENERY(id, Label) user-applied label
$SCENERY(id, Show) show scenery object
$SCENERY(id, Toggle) toggle visibility of scenery object
$TRACK(id, Label)
user-applied label
$TRACK(id, Length)
track length in rru
$TRACK(id, OccupiedBy) return labels of cars on track
$TRACK(J1, FindFromJxns, J2) returns id of track between jxns J1 and J2, blank if none
$TURNTABLE(id, AlignedTo) id(s) of 1 or 2 locked external tracks
$TURNTABLE(id, Label) user-applied label
$TURNTABLE(id, OccupiedBy) return labels of cars on turntable
$TURNTABLE(id, Track) id of bridge track
String Functions  
$STRING(string, Contains, argument) returns 1 if string contains argument, 0 if not
$STRING(string, EndsWith, argument) returns 1 if string ends with argument
$STRING(string, Length) length of string in characters
$STRING(string, NextToken[, delims]) returns next substring, removes from string
$STRING(string, StartsWith, argument) returns 1 if string starts with argument
File Functions  
$FILE(id, Close) close file
$FILE(id, ReadAll) read contents and return as string
$FILE(id, ReadLine) read next line; return 'EOF' when done
$FILE(name, Delete) 1 if file successfully deleted, else 0
$FILE(name, Exists) 1 if file exists, else 0
$FILE(name, GetDir) return directory path, no filename
$FILE(name, GetNameExt) return filename+ext only, no path
$FILE(name, ReadVars) read all user variables from named file
$FILE(name, Open) open named text file, return id or 0 if fail
$FILE(name, SaveVars) write all user variables to named file
Set Functions  
$SET(set, Add, value) add new value at end; return new set
$SET(set, Contains, query) 1 if set has member matching query, 0 if not
$SET(set, ContainsSet, set2) 1 if all mems of s2 are in this set, 0 if not
$SET(set, Count) number of members in set
$SET(set, Dedupe) return set without duplicates
$SET(set, Difference, set2) return set minus set2
$SET(set, EqualSets, set2) 1 if sets are equal to each other, 0 if not
$SET(set, Find, query) index of member matching query, -1 if none
$SET(set, Flatten)
return set with subsets merged in
$SET(set, Get, i) member i (first i = 0)
$SET(set, Intersection, set2) return intersection of set and set2
$SET(set, Randomize) return set sorted in random order
$SET(set, Remove, i) remove member i; return new set
$SET(set, Sort) return set sorted alphanumerically
$SET(set, SortRev) return set sorted in reverse order
$SET(set, Union, set2) return union of set and set2
   
System Functions  
$AO_DEV(mode,arg) getset<name>,getvar<name>,agent,y7,upgrade,checkXO,mon1,reset,ccs,gnsl
$EXPIMAGE(p [,s,o]) export layout image to path p, size=1-10, opts=bit sum
$FINDSTR(sub,s) return zero-based position of sub within s, or -1 if not found
$INT(x) return integer portion of number
$MSGBOX([type,]s) show message s with ok/cancel or yes/no buttons; return 1=ok/yes 0=no
$NOTE(id) get/set text of given note
$OPS(mode,g.col,qry) get/find/set value or show/hide Ops Central grid g
$OUTPUT [(ALL)] return last line or all lines of output window
$RAND(i1,i2) random integer between i1 and i2; defaults false,100 if not specified
$READ(f) return contents of text file f as string
$RRCLOCK(arg) rrclock functions: get, set t, add t
$SETTING(name) return named registry setting
$STRLEN(s) return length of s
$SUBSTR(i1,n,s) substring of s starting at i1, n chars long; if n = -1, go to end
$SWITCH(j) return switch pos at j (0 or 1 standard), -1 if j not a switch
$SYSTEM(cmd) execute system command in command box
$VIEW(op,name) get/set visibility of named window or feature; op=IsVisible,Show,Hide
$WRITE(f,[a,]s) write string s to file f, optionally append; 0 if fail
System Variables  
$CAR name of selected car
$CRASHES count of crashes/bounces since layout open or last reset
$DATADIR TP application data directory path
$DATE today's date (mm/dd/yyyy)
$KEY numeric code of last key hit on keyboard
$LAYOUT layout name
$SPEED speed of train in MPH (KPH if metric settings in effect)
$TIME current time of day (h:m:s in 24-hour format)
$TRAIN name of selected train
$X_CAR label of crossing car
$X_SPEED speed of crossing train
$X_TRAIN name of crossing train -- train owning calling script
Wait Conditions  
AFTER <(t j d)> last car of train crosses exact spot
AFTER <h:m:s> specified actual time has elapsed on wall clock
AFTER <jxn> last car of train crosses junction
AT <(t j d)> lead car of train crosses exact spot (dist d from jxn j on track t)
AT <h:m[:s]> specified time is shown on layout clock; h:m required, secs optional
AT <jxn> lead car of train crosses junction
ON COUPLE train couples with another car
ON KEY [<key>] user presses any key or specified key
ON NOTECLOSE user closes notebox with key or click
ON STOP train comes to a complete stop
ON TABLESTOP turntable finishes rotating
ON THROW <jxn> specified switch is thrown by any means