The MWScript Language
The MWScript expression syntax is compatible with the existing MoneyWorks expressions taht you use in custom forms and reports. MWScript simply adds handlers and statements. Flow control is closely analogous to that provided by the report writer.
Newlines are syntactically significant. One statement per line.
MWScript supports the same types as are available in the report writer. As in the report writer, the language is very weakly typed (values are freely type-promoted as necessary).
'1/1/13'— note that single quotes are for dates, not text strings
`also a string`
- Selection — create via CreateSelection(tableName, search) function
- Table — create via CreateTable() function
- Record — created by looping over a selection with foreach
- Associative Array — create with the CreateArray() function
Associative arrays use the syntax arrayname[key], where key can be any text up to 31 characters, an integer, or a date (internally, integers and dates are represented as text formatted so that lexical sort order matches numeric/date sort order). Data values can be any other type.
let myArray = CreateArray() let myArray = "thing" let myArray["mykey"] = myArray let myArray['31/1/12'] = 500
In the current implementation, insertion into an associative array is O(N). Retrieval is O(log N).
Arrays, like Tables, are always passed by reference, so if you assign an array to another variable, or pass it as a parameter to a function, you are not copying the array. If you want to copy an array, you must allocate a new array with CreateArray and explictly copy all of its members.
Properties are variables that last for the lifetime of the script. They are instantiated each time the script is loaded and discarded on unload.
Properties are defined outside of message handlers.
property mytextglobal = "qwertyuiopasdfghjklzxcvbnm"
If you need a property to be persistent across sessions, you can store the value in the database (the User2 table is appropriate for this, see the SetPersistent and GetPersistent functions). Load the value in the script's Load handler, and store it in the Unload handler. Keep in mind that every user will be executing the script with their own copy of the properties, so you may need to take steps to stop one user from clobbering values stored by another user.
In MoneyWorks 8 you can declare a simple-type property to be persistent. If you do this, MoneyWorks will automatically save and load its value (locally, on each client) when your script is Loaded and Unloaded.
persistent property MyPrefValue = "foo"
Note that these persistent values are stored per-client and all scripts for all documents the client might open or connect to are in the same namespace. Once the script has been run once on a particular computer, keep in mind that the value of the property at load can be different from the value that it appears to be initialised to in the script. To store persistent values per-document, use the SetPersistent and GetPersistent functions.
Constants are named values for use within the script.
Constants are defined outside of message handlers.
IMPORTANT: Every script must declare a constant with the name meta.
constant meta = "Script for Something by Your Name and your URL"
The meta contant will be used by MoneyWorks to identify your script to the user. It must be a string and it must not be empty.
A message handler defines a callable function with optional named parameters and an optional return value. A message handler is a peer of the built-in intrinsic functions and can be used in expressions in the same way.
A message handler is defined by the keyword on, followed by the handler name and an optional list of parameter names. The handler body ends with the keyword end on a line by itself.
If a parameter required by your handler is not supplied by the caller, then a runtime error will result. Parameters are untyped, so if callers don't provide a value of the correct type, you should convert the type yourself to the required type (usually using TextToNum or NumToText)
Comments use the C++ form. // for a to-end-of-line comment; /* and */ to begin and end a block comment that can span lines or within a line.
The let statement assigns the result of an expression to a new or existing variable. If the variable has not previously been seen in the handler and is not a property, then it is implicitly declared as a local variable in the handler.
let myVar = Today() + 7
Conditional flow control is done with if, elseif, else, and endif. Zero or more elseif clauses are allowed for an if, followed by zero or one else clause, followed by an endif.
if condition // do something endif
And the long form:
if condition // do something elseif anothercondition // can have any number of these // do something else else // up to one of these // do other thing endif
The long form effectively provides switch/case functionality.
General looping can do done with a while loop. Loops can make use of the break and continue statements for early exit or short circuit (just as in C-like languages, and the report writer).
while condition if special condition break // exit the loop endif if anothercondition continue // go back to top of loop, skipping do stuff endif // do stuff endwhile
There are several kinds of for loops that operate on different kinds of collections or ranges. Each kind starts with the keyword foreach and ends with endfor.
The general form is
foreach loop-control-var in type-keyword expression
Foreach declares a local loop control variable whose scope is limited to the loop body. Type keywords can be:
- database table names (defining a loop over a selection of records). Available table names are:
asset. The expression must yield a selection variable for the specified table. The loop control variable is a record, whose fields can be accessed by suffixing the variable name by a field name (e.g. rec.ourref). The variable used on its own will yield a 1-based index.
- the keyword text which will iterate over words or lines in comma or newline delimited text. The delimiter is determined automatically—if there is at least one newline, then iteration is by line, otherwise iteration is by comma. Every line of multiline text should terminate with a newline (including the last one!).
- the keyword textfile that will iterate over lines in a textfile loaded from the local filesystem using the "file://" scheme, or from an HTTP server using the "http://" or "https://" scheme. You can use the http scheme to access data from remote data sources (e.g. REST servers. It retrieves data using the GET operation only). If the path is supplied, it must be in the temp directory, the custom plugins directory, MoneyWorks app support directory, or have a file extension of .txt or .csv. If the path is specified by the user, it can be anywhere or have any extension (but obviously should be a text file). Anywhere outside these locations will need to be specified in a file open dialog box by the user (which will be automatically invoked for any invalid or empty path).
- the keyword array which will iterate over key values in an associative array variable;
- and finally no keyword but rather a parenthesis-enclosed pair of values defines a simple numeric range (with an optional step value). The values can be full expressions but must be numeric. If the finish value is less than the start value, no iterations will occur (unless the step is negative).
foreach rec in transaction CreateSelection("transaction", "status=`P`") foreach rec in account someSelectionICreatedEarlier foreach word in text "foo, bar, baz" foreach line in textfile "file://" foreach line in textfile "http://" foreach key in array myArrayVar foreach i in (0, 10) // integers foreach i in (100, 0, -10) // integers with optional step
// log the numbers 1...100 foreach i in (1, 100) syslog(i) endfor // note that (100, 1) will iterate 0 times, unless you supply a negative step // log the numbers 100, 90, 80, ... 0 foreach i in (100, 0, -10) syslog(i) end for
// foreach in array iterates over keys; get the value by subscripting with the key foreach key in array anArray syslog(key + " = " + anArray[key]) // key is always text end for
// comma-delimited text foreach w in text "foo, bar, baz" syslog(w) endfor // newline-delimited text foreach line in text "firsttlinensecondtlinenlasttlinen" syslog(line) end for
// loop control is of record type; use dot-notation to access fields // the naked loop contol variable is a 1-based index foreach a in account CreateSelection("account", "code=`1@`") syslog("record #" + a + ": " + a.code + " " + a.description) end for
You can install multiple scripts in a document and enable or disable them individually.
Use the Show Scripts command to show and edit the scripts installed in a document. Note that only one user at a time can make changes to scripts.
You can add, delete and rename scripts (use the icons at the bottom of the sidebar).
Active scripts are loaded at login and unloaded at logout, and a script is also unloaded/reloaded when you click the Activate button in the script editor toolbar (the old version of the script is first unloaded if it was loaded, then the modified script is compiled and loaded).
You can keep inactive scripts in a document. They won't do anything until you activate them.
You can use an inactive script to store boilerplate text for other purposes (such as a mail template, html, etc). See GetScriptText.