Edit

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.

Types

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).

Type Example

  • Number 5.35
  • Date '1/1/13' — note that single quotes are for dates, not text strings
  • Text "mwscript" or `also a string`
  • Selection — create via CreateSelection(tableName, search) function
  • Table — create via CreateTable() function
  • Record — created by looping over a selection with foreach

and also

  • 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[0] = "thing"
    let myArray["mykey"] = myArray[0]
    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

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

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.

Message Handlers

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

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.

Assignment

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

Conditionals

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.

Short form:

    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.

While loops

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

For loops

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: account, ledger, general, department, link, transaction, detail, log, taxrate, message, name, payments, product (items), job, build, jobsheet, bankrecs, autosplit, memo, user, user2, offledger, filter, stickies, lists, login, contacts, inventory, assetcat and 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 variants:

    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

Range Example:

    // 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

Array Example:

    // 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

List Example:

    // 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

Selection Example:

    // 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

Running Scripts

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.

Stopping problematic scripts

If you write a script that gets into an endless loop, you should be able to stop it using the Stop button in the script progress window that will appear when the script is busy for more than a few seconds. The Stop button is available for any user who has the Scripting privilege. This will not deactivate the script; it will just terminate execution of the current handler.

If you write a script that gets into an endless loop putting up alert windows, you will not get a progress window Stop button. If a script presents an alert, any user with Scripting privileges can stop the script by holding down the ctrl+shift keys when clicking the alert's button. You will get a further alert asking if you want to disable the script. Note that if you disable (deactivate) a script, be sure to reactivate it before closing the script window because otherwise it will be deactivated for all users.

If you write a script that causes problems in its Load handler, thereby preventing you (or anyone) from logging in, you can suppress the loading of document scripts by holding down Option+Shift (Mac) / Ctrl+Shift (Windows) when logging in. Note that this option is available only for users who have the Scripting privilege.