QuadooScript

Quadoo was the best cat I ever had.  He is missed!

Download QuadooScript for Windows!

QuadooScript is a dynamically typed, high-level scripting language that tightly integrates into Windows applications and processes. It has an easy-to-use syntax that will immediately be understood by anyone familiar with VBScript or JavaScript, or even anyone with a background with C.

Getting Started

QuadooScript can be used in five different ways:

Ultimately, no matter how QuadooScript is used, both QuadooParser.dll and QuadooVM.dll will be involved, although scripts can be compiled separately so that an embedding application can omit QuadooParser.dll.

QVM.exe is the stand-alone console application, and it supports several command line parameters:

When not in CGI mode, the first command line argument specifies the script, which can also be a pre-compiled script if the the file has the .QBC extension. All other script files are sent through the script parser, regardless of file extension (the preferred extension is .QUADOO). When using CGI mode, the script file (either pre-compiled or script text) is obtained from the PATH_TRANSLATED environment variable. Also, when using CGI mode, the content type, query string, and cookies are read from environment variables while the form data is read from the standard input handle.

When using WQVM.exe, only the -args parameter is understood.  WQVM.exe attempts to run a script's WinMain() function.  A script can define this function either as having no parameters or as having two parameters.  In the latter case, these are the strCmdLine and nCmdShow parameters, which are propagated to the script from the host's own WinMain() function.

When using ActiveQuadoo.dll in an ASP environment, the .ASP extension should still be used for server pages using QuadooScript.

Syntax

QuadooScript's syntax is designed to be familiar to people coming from C-like languages while being a bit simpler and more streamlined.

Scripts can be stored in either plain text files with a .QUADOO extension or pre-compiled with a .QBC extension. The syntax is easily parsed using a hand-written look-ahead Recursive Descent Parser (RDP). Expressions are parsed using Pratt parser techniques.

Comments

Comments work in QuadooScript the same as in C and C++. /* and */ omit the enclosed text from being parsed, and // causes all remaining text on the line to be ignored.

Reserved Words

The following are reserved keywords in QuadooScript:

array bool break case catch
class continue default do double
else enum false float for
function get goto if int
long map money namespace new
null partial property ref return
set static string super switch
syscall this throw true try
var virtual while

Identifiers

Identifiers are used to name variables, functions, classes, namespaces, enums, and labels. Identifiers are case sensitive and may not begin with numbers. Underscores are permitted.

Line Endings

Statements in QuadooScript end with a semicolon. Blocks of code do not need trailing semicolons, but class and enum definitions do require a semicolon at the end, while namespace definitions do not.

Blocks

Like C and C++, QuadooScript uses curly braces to define blocks of code. A block of code can be used anywhere a statement is allowed, like in control flow statements. Function bodies are also blocks but can also be a single statement without curly braces.

function FunctionWithBraces (x) { return x * 2 + 3; } function FunctionWithoutBraces (x) return x * 2 + 3;

Namespaces

Namespaces provide a simple way to separate groups of functions, classes, and other namespaces by adding the namespace's name as part of the path required to access anything within that namespace by anything outside of it. A dot is used to navigate between the components of a namespace path.

namespace MyGroup { function MyFunction () { return 123; } } function MyOtherFunction () { return 456 + MyGroup.MyFunction(); }

Enumerations

Enumeration values work similarly in QuadooScript as they do in C and C++, except that the name of the enumeration must be used to reference the values.

enum Stuff { a, b, c }; function GetValueOfB () { return Stuff.b; }

Values

Values are the built-in atomic object types that all other objects are composed of. They can be created through literal values in script and expressions that evaluate to a value. All simple values are immutable: once created, they do not change. The number 3 is always the number 3. The string "frozen" can never have its character array modified in place. Objects, arrays, maps, JSON objects, and JSON arrays can all have their contents changed.

Booleans

A Boolean value in QuadooScript is represented by one of two values, true or false. The literal keywords true and false are used to create a Boolean value.

Numbers

QuadooScript can manage numeric values using five different types:

At compile-time, QuadooScript automatically determines whether an integral literal is 32-bit or 64-bit.

var x = 1234567890; var y = 12345678900;

In the example above, x is assigned a 32-bit value, while y is assigned a 64-bit value. 32-bit floating point values can be created with literals that are followed by an 'f' character.

var x = 3.14; var y = 3.14f;

In the example above, x is assigned a 64-bit double value, while y is assigned a 32-bit floating point value.

Currency values are 64-bit integers with four lossless decimal places.

var x = $123.4567;

Strings

A string in QuadooScript is internally represented by a wide-string RSTRING value. String literals are represented in script as zero or more characters enclosed in quotations.

"This is my string!"

QuadooScript supports escape sequences just like in C and C++.

"This string ends with a line break!\r\n"

Strings in QuadooScript cannot be modified, but array subscript syntax can be used to read from them just like in C.

var strName = "Quadoo"; var nFirstChar = strName[0];

In this example, nFirstChar would be set equal to the ASCII value of 'Q'.

QuadooScript accepts escaped character values in strings using the \uhhhh and \Uhhhhhhhh sequences where h denotes a hexadecimal digit.

if(Host.CodePage != 65001) Host.CodePage = 65001; println("Smile Emoji: \U0001F600");

When running QVM.exe under the Windows Terminal application, this is the output:

Smile Emoji: 😀

When working with large Unicode characters, there are differences between indexing a string directly as compared to using the asc and chr intrinsics.  For example:

var strText = "\U0001F600"; println("First element: " + strText[0]); println("First character: " + asc(strText));

Reading the first element directly returns exactly that element.  In this case, the value printed would be 55357.  The asc intrinsic understands the large Unicode values and will look for the rest of the value in the next element.  In this example, asc would return 128512.  That same value can be turned back into a string of two elements with chr(128512).

Null

QuadooScript has a special null value that serves as the default value of any variable that is otherwise uninitialized. If you call a method that doesn't return anything and get its returned value, you get null back. The null value evaluates to false when tested by an equality operator.

Arrays

Arrays in QuadooScript work similarly to arrays in other scripting languages, although all arrays in QuadooScript are dynamic array objects on the heap. There are five ways to create an array in script syntax:

var a0[]; var a1[10]; var a2 = new array; var a3 = new array[23]; var a4[] = { 1, 2, 3, 47, 90, 234 };

a0 is defined as a dynamic array without any slots initially allocated.  a1 has 10 slots initially allocated.  a2 is assigned a new array without any slots, while a3 is assigned a new array with 23 slots available. a4 is assigned a new array sized to the data in the C-style data set.

Array Length

An array's length is retrieved using the len function. Using the variables from above, len(a4) would return 6.

Getting and Setting Values

Array values are indexed using square brackets.

a3[0] = 123; var n = a3[0];

Inline Arrays

Array initialization lists can be defined inline and used as expressions.

var n = 3; var v = { 1, 2, 3, 4, 5, 6 }[n];

In this example, 4 is assigned to v.

Array Methods

Maps

A map in QuadooScript is an associative array. It holds a set of entries, each of which maps a key to a value.

QuadooScript supports maps with these key types:

A map is dynamically created using this syntax:

var m0 = new map<string>; var m1 = new map<int>; var m2 = new map<long>;

All three maps can associate keys with any type of value, but m0 only stores string keys, m1 only stores 32-bit integers for keys, and m2 only stores 64-bit integers for keys.

Map Length

A map's length is retrieved using the len() function.

var cItems = len(m0);

Getting and Setting Values

Map keys and values are retrieved using this syntax:

m0["stuff"] = 123; var n = m0["stuff"];

Maps using string keys also support syntax like this:

var n = m0.stuff;

Map Methods

Functions

Functions in QuadooScript begin with the "function" keyword, followed by the function's name and finally a list of function parameters. A function can either explicitly return a value, or the compiler will insert a "return null" instruction at the end of the function.

Execution in a script always begins with the main function.

function main () { }

QuadooScript allows functions to be called directly by name or by using indirect references (like a function pointer). For example:

function TestFunction (n) { return 890 + n; } function main () { var oFunction = TestFunction; var n = oFunction(123); }

Function references can only be created on static functions.

Most functions in QuadooScript will only return one value using the return keyword, but function arguments that are local variables can be passed by reference to functions, allowing the called function to pass data back to the caller through the function parameters.

function Test (x) { x = 1; } function main () { var n Test(ref n); }

Partial Functions

QuadooScript allows functions to be constructed incrementally throughout the source files that are compiled together.

partial MyFunction (a, b, c) { // Do something } <Other code...> partial MyFunction (a, b, c) { // Do something else }

Internally, at the parsing level, the partial functions are stitiched together into a single function. The first partial function defines the parameters, if any. Later extensions to the partial function must either redefine the same parameter list or omit the list. For example:

partial MyFunction { // Parameters a, b, and c were defined previously and didn't need to be redefined. }

Lambda Functions

QuadooScript supports lambda functions using the new keyword.

function main () { var oTest = new function (x) { Host.WriteLn("x: " + x); var oInner = new function (y) { Host.WriteLn("y: " + y); return x * y; }; return oInner(x * 2); }; Host.WriteLn("Test: " + oTest(3)); }

NOTE: The Host object is part of QVM.exe and ActiveQuadoo.dll, rather than part of the language itself.

In this example, a lambda function is created and assigned to oTest. When it runs, it also creates another lambda function and assigns it to oInner. Also, when the second lambda is created, it inherits the value of x from the first lambda.

Internally, a lambda function is an object, and inherited values are stored as member variables on that object.

QuadooScript also allows lambda functions to name the function.

var oLambda = new function MyNamedLambda () { }; oLambda.MyNamedLambda();

While this syntax does create a lambda function that can be called anonymously, since the function is named, it can also be called by name.

Tail Call Optimization

QuadooScript can optimize tail calls for functions and non-virtual class methods. The optimization replaces the calling function's stack frame with the called function's stack frame. This means the stack will not grow recursively. This optimization is only considered when a return statement's expression is an eligible function call (global function, static method, or non-virtual class method from the same class).

Control Flow

Control flow is used to determine which chunks of code are executed and how many times.  Branching statements and expressions decide whether or not to execute some code and looping ones execute something more than once.

Truth

All control flow is based on deciding whether or not to do something.  This decision depends on some expression's value.

To test whether strings, arrays, and maps are empty, you can use the isempty() intrinsic function.  isempty() returns true for null and empty strings, arrays, and maps.  It returns false for strings, arrays, and maps that have at least one element.  For all other values, an exception is thrown.

If Statements

The simplest branching statement, if lets you conditionally skip a chunk of code.  It looks like this:

if (ready) Host.WriteLn("go!");

That evaluates the parenthesized expression after if. If it's true, then the statement after the condition is evaluated. Otherwise it is skipped. Instead of a statement, a block may be used:

if(ready) { Host.WriteLn("getSet"); Host.WriteLn("go!"); }

An else branch can also be included. It will be executed if the condition is false:

if(ready) Host.WriteLn("go!"); else Host.WriteLn("not ready!");

And, of course, it can take a block too:

if(ready) { Host.WriteLn("go!"); } else { Host.WriteLn("not ready!") ; }

Logical Operators

The && operator evaluates the left-side expression first. If it is true, only then is the right-side expression evaluated. Otherwise, the right-side expression is never evaluated.

The || operator evaluates the left-side expression first. If it is true, the right-side expression is not evaluated. The right-side expression is only evaluated when the left-side expression is false.

var a = true; var b = false; if(a && b) Host.WriteLn("true!"); if(a || b) Host.WriteLn("true!");

The Conditional Operator ? :

The conditional operator works just like in C and C++.

Host.WriteLn(a ? "a was true!" : "a was false!");

Do/While Statements

Loops can be constructed in QuadooScript following either the do {} while(expression) format or using the while(expression) {} format. do { Host.WriteLn("Still looping!"); } while(keep_looping); while(keep_looping) { Host.WriteLn("Still looping!"); }

Like C and C++, the distinction is when the expression is evaluated. A do loop always executes the loop code at least once, but a while loop may not execute its loop code at all.

For Statements

The for loop works similarly in QuadooScript as it does in C. Three code fragments, separated by semicolons, are expected. The first fragment is an initializer, which can either be an assignment or a variable declaration. The second fragment is the expression for continuing the loop. The third segment is an expression to be executed at the end of each loop iteration.

for(var n = 0; n < 10; n++) { Host.WriteLn("n: " + n); }

Any or all of the three code segments may be empty. For example, infinite loops may be expressed with this syntax:

for(;;) { }

Break Statements

The break statement works the same as in C and C++. Put a break statement in a loop to immediately jump from that point to just outside the loop.

goto

The goto keyword can be used to jump to a label that is in the same scope or in a parent scope. The script cannot jump into child scopes using the goto keyword.

Variables

Variables are named slots for storing values. New variables are declared using this syntax:

var x; var y = 1;

Like JavaScript, but unlike VBScript, variables can be declared and initialized on the same line.

In the example above, variable x's value is null.

QuadooScript also allows variables to be declared and initialized using the value types. For example:

int a, b, c; long l; float r; double d; money m; string s;

Since QuadooScript is a dynamically typed language, these "types" are meaningless if other values are later assigned to these variables. However, without explicitly initializing them in script, the default values of each type are automatically assigned at compile-time since the types are specified. Therefore, values a, b, and c are automatically initialized to 0, instead of null. Variable d is initialized to a 64-bit value of 0, and so on.

Type Casting

The value types can also be used to cast a variable of one type into another type. For example:

var n = 0; var q = (double)n; var s = (string)q;

Scope

A local variable in QuadooScript always exists until the end of the block in which it was defined. Static variables can be referenced from other scopes of code. For either, QuadooScript searches up the scope stack to find a variable.

namespace Stuff { var MyValue; namespace OtherStuff { var MyOtherValue; } } function main () { Host.WriteLn("Value: " + Stuff.MyValue); Host.WriteLn("Value: " + Stuff.OtherStuff.MyOtherValue); }

Assignment

After a variable has been declared, you can assign to it using =:

var a = 123; a = 234;

An assignment walks up the scope stack to find where the named variable is declared.

When used in a larger expression, an assignment expression evaluates to the assigned value.

var a = "before"; Host.WriteLn(a = "after");

External Variables

The extern keyword can be used to create a variable (at the scope where it's used) that loads an external object having the same name. For example:

extern Host;

After this line of code, a variable called Host can be used that is loaded with an external object called Host. It's always more performant to load from a variable than to look up a named object.

Variable Types

A variable's type can be retrieved using the typeof intrinsic.

var abc = 123; var type = typeof(abc);

The type can be checked against a predefined set of enumeration values:

Classes

Classes work in QuadooScript about the same as they do in VBScript, except they use syntax that is more like C++.

Classes are defined using the class keyword.

class Nothing { };

Classes in QuadooScript can optionally have a constructor and a destructor.

class Nothing { Nothing () { } ~Nothing () { } };

Member variables

Any variable declared in a class but outside any class method becomes a member variable. Class member variables can be made static using the static keyword.

Static class member variables can be accessed from outside the class, but non-static class member variables are only accessible to non-static class methods unless they're marked with the "property" keyword (see below).

Class Methods

All class methods are publicly available.

Class methods can also be static. Static class methods can be accessed by treating the name of the class as a namespace.

class Nothing { Nothing () { } ~Nothing () { } static function MyStaticMethod () { Host.WriteLn("Hello!"); } }; function main () { Nothing.MyStaticMethod(); }

Class Lifetime Management

Class instances are created by using the new keyword.

var o = new Nothing;

Class instances are reference counted. When a variable holding a class instance goes out of scope, the class instance's reference count is decremented. When a class's reference count reaches zero, the class's destructor (if any) runs, and then the instance is removed from memory.

Properties

QuadooScript supports special syntax for exposing properties on objects.

class Nothing { var m_value; Nothing () { m_value = 0; } ~Nothing () { } property Nothing { get { return m_value; } set (v) { m_value = v; } } }; function main () { var o = new Nothing; Host.WriteLn(o.Nothing); o.Nothing = 37; Host.WriteLn(o.Nothing); }

QuadooScript also supports indexed properties for classes.

property Nothing { get (n) { return m_value[n]; } set (n, v) { m_value[n] = v; } } ... var o = new Nothing; Host.WriteLn(o.Nothing[0]); o.Nothing[0] = 37; Host.WriteLn(o.Nothing[0]);

A property can simultaneously support both regular and indexed getters and setters.

Properties can also be accessed dynamically using the get and set keywords in expressions. Using the example class from above, then the properties can also be accessed as follows:

var x = get(o, "Nothing"); var y = get(o, "Nothing", 0); set(o, "Nothing", 123); set(o, "Nothing", 0, 123);

Properties can be created automatically from member variables by marking them with the "property" keyword.

property var MyProperty;

When this is done, the proprty can be accessed externally by the same name as the member variable.

this

A class method can reference its own class member variables using the this keyword. A class instance can also pass an instance to itself to another function by passing this as an expression.

Inheritance

QuadooScript classes support single class inheritance. This means that a class can inherit members and methods from another class. However, a class cannot simultaneously inherit from two base classes.

class CBaseClass { var m_nValue; function GetValue () { return m_nValue; } }; class CSuperClass : CBaseClass { CSuperClass () { m_nValue = 10; } }; ... var oClass = new CSuperClass; return oClass.GetValue();

If a base class defines a constructor that has parameters, then a super class must also provide a constructor that calls the base class constructor. However, the super class constructor's signature does not have to match the base class constructor.

CBaseClass (nValue) : m_nValue(nValue) { } CSuperClass () : CBaseClass(123) { }

Virtual Methods

Methods on classes can be marked virtual or pure virtual. Methods marked with the "virtual" keyword are called by name, instead of by ordinal, so that super classes can override base class behaviors. Pure virtual methods are like virtual methods but are unimplemented in base classes and must be implemented in super classes.

class CAnimal { virtual function Feed () { Host.WriteLn("The " + GetType() + " has been fed."); } virtual function GetType () = 0; }; class CGiraffe : CAnimal { virtual function GetType () { return "giraffe"; } }; function main () { var oAnimal = new CGiraffe; oAnimal.Feed(); }

In the example above, the CAnimal base class defines a GetType() method but leaves the implementation to the super class CGiraffe.

Method Delegates

Just as global functions can be wrapped into objects, non-static class methods can also be assigned as objects. In this case, they are called method delegates.

class MyBaseEvents { function OnClick (x, y) { Host.WriteLn("x: " + x + ", y: " + y); } }; class MyEvents : MyBaseEvents { MyEvents () { Host.WriteLn("Test"); } function GetOnClick () { return MyBaseEvents.OnClick; } function GetOnStuff () { return OnStuff; } function OnStuff () { Host.WriteLn("Stuff!"); } }; function main () { var oMyEvents = new MyEvents; var dlgOnStuff = oMyEvents.GetOnStuff(); var dlgOnClick = oMyEvents.GetOnClick(); dlgOnStuff(); dlgOnClick(100, 200); }

In the example above, delegates are created for two non-static class methods. Once created, they're callable just like global function references. Internally, they maintain an object reference and the code pointer for the class method. When invoked, the code pointer is updated directly without doing a name lookup.

Virtual class methods may be assigned as delegates, but pure virtual methods may not. This is because a specific function is picked at compile-time. In the above example, a path to a method in a base class picks the base class method, even if it had an override in the subclass.

Class method delegates may only be assigned from non-static class methods of the class for which the delegates are being created. This allows both the class's this pointer to be available as well as type information for ensuring valid delegates are being created.

Interfaces

QuadooScript supports interfaces, similar to those in C++, for defining abstract coding interfaces. Classes inherit from interfaces and must implement the interface methods before they can be instantiated. Interfaces can also inherit from other interfaces, and classes and interfaces can inherit from multiple interfaces.

To define an interface, the interface keyword is used.

interface IMyInterface { virtual function DoSomething () = 0; }; class CMyClass : IMyInterface { virtual function DoSomething () { println("DoSomething!"); } };

The methods of an interface must be defined as pure virtual, like they would be in C++.

The real benefit to using QuadooScript interfaces is derived when embedding the VM into a larger application. The IQuadooObject interface has a GetInterface() method that queries the object for the specified interface. The name of the interface is the same name that is used in the script. If a requested interface is found, it is returned to the caller as an IQuadooInterface object.

The native IQuadooInterface interface has two methods:

Method ordinals begin from zero. Interfaces that have base interfaces always include the base interface methods first, in the order in which they're defined in script. A code generator could simultaneously define interface definitions for QuadooScript and constants for native code. At run-time, a larger application embedding the QuadooScript VM could retrieve interfaces from objects and call methods using code-generated constants. In this way, no name lookups would ever be necessary.

Fibers

Fibers are like coroutines, which can be thought of as cooperatively scheduled threads. Fibers wrap either a global function or a lambda, and they have separate call stacks from the caller. Code that is running within a fiber can yield control back to the caller by suspending itself. A fiber remembers its state (call stack and instruction pointer) until the fiber is called again. At that point, the fiber resumes running immediately after the original yield call. When the last function running within a fiber exits, the fiber's call stack is deallocated.

function main () { var oFiber = new fiber(new function () { for(var i = 0; i < 5; i++) { var o = JSONCreateObject(); o.i = i; yield(o); } return null; }); var oValue; do { oValue = oFiber(); if(null == oValue) break; Host.WriteLn((string)oValue); } while(oFiber.Active); }

In this example, a fiber is created using a lambda. In the do loop, the fiber is started on the first iteration of the loop. Subsequent iterations resume the fiber. With each call to the fiber, the fiber runs until it calls the yield() intrinsic. The fiber returns a JSON object through the yield() intrinsic, and the caller retrieves the value as the return value from the fiber.

Fibers support these properties:

When a fiber's last running function exits, the fiber's call stack is deallocated. If the fiber is called again, it is restarted, and a new call stack is created.

Yielding and Resuming

The yield() intrinsic can be used with or without a single argument. If an argument is passed, then the caller receives the value. When calling the fiber to resume it, either no arguments can be passed, or a single argument can be passed. If one argument is passed, then the yield() call in the fiber returns the passed value.

Intrinsics

QuadooScript has a number of built-in instructions that appear as functions in script but are actually their own double byte-code instructions. One byte-code instruction specifies the INTRINSIC (value 0xFE) instruction, while the second byte-code instruction specifies which intrinsic to execute.

len sqrt log log10 exp
asc chr trim substring strchr
strrchr hex abs lcase ucase
instr instri instrrev instrrevi now
nowutc strcmpi replace timer rand
srand yield split space sin
cos tan hyp left right
stringbuilder gc mutex extract extracti
eventsource modf sigmoid sinh cosh
tanh rad deg event wait
waitall asin acos atan strcmp
scan sleep print println utoa
replacei isempty nsn inf ramp
newasync async await propbag strcmpn
strcmpni base64 base64url strins strtok
reduce dice typeof

JSON

QuadooScript supports JSON natively. JSON objects and JSON arrays are primitives in QuadooScript. Strings, Booleans, and integers are converted directly in and out of JSON objects and JSON arrays.

The following JSON functions are supported:

JSON objects support the following methods:

JSON arrays support the following methods:

More information on using the path parsing APIs is on the JSON page.

Error Handling

QuadooScript exposes runtime errors to the script as exceptions. QuadooScript supports exceptions using syntax that is similar to C++.

try { Host.ThisMethodDoesNotExist(); } catch { Host.WriteLn("We didn't have that method!"); }

Scripts can also catch the error code from the exception.

try { Host.ThisMethodDoesNotExist(); } catch(e) { Host.WriteLn("Error code: " + hex(e.Value)); }

The script can also use the throw keyword to generate an exception.

try { throw 123; } catch(e) { Host.WriteLn("Caught exception: " + e.Value); }

The exception itself is an object that exposes information about the exception. The Value property returns the value that was thrown. The following properties are available:

Exception objects also support a ToString() method that returns a human-readable string containing all the information about the exception.

Internal Objects

The QuadooScript VM has several internal objects.

Array

Arrays are a native variable type that have the following methods:

Binary

The binary object is used to manage a blob of binary data. Internally, the binary object implements the ILockableStream interface, so the data isn't meant to be extended. The data could come from a file or from an external object, for example. Several methods are available to support data extraction:

The binary object has two read/write properties:

The binary object has a read-only property called Size that returns the length, in bytes, of the managed binary data. The binary object also has one indexed property called Data. This property also allows the binary data to be modified in-place.

Date

The date object is used to manage a date/time value. It has the following methods:

The following properties are available:

The date object supports the default value mechanism. Invoking the default value is the same as calling ToString().

Event

The event object is a wrapper around the OS event. It is created using the event() intrinsic function, which requires two arguments: name (null is allowed) and a manual reset boolean. The following methos are supported:

An event instance can be passed to the wait() and waitall() intrinsic functions.

Event Source

The event source object manages event subscriptions. It has the following methods:

The arguments passed to Fire() are passed to the subscribed sinks. Zero or more arguments may be passed. If any other method name is called, that method name is used as the method to call into the subscribed objects. Normal objects, global functions, and lambdas may be used as sinks.

Fiber

Fibers are objects that maintain their own call stacks and can be suspended and resumed. They have two properties:

Map

Maps are a native variable type that have the following methods:

Mutex

Mutex objects have the following methods:

Mutex objects also have one read/write property: Owned.

StringBuilder

StringBuilder objects have the following methods:

The following properties are available:

Host Methods

While not technically part of the QuadooScript language itself, QVM.exe adds a Host object into the global namespace to let scripts interact with the external environment and file system. These are the methods available on the Host object:

The Host object also supports these properties:

Find File Object

These methods are available:

These properties are available:

Folder Object

These methods are available:

The folder object also supports a read/write Path property.  When setting the property, the path must be an existing folder.

Windowed Environment

Whereas QVM.exe operates in console mode, WQVM.exe runs in a windowed environment.  When WQVM.exe runs, Windows internally calls its WinMain() function.  In turn, a script's WinMain function is also called.  However, if WinMain() does not exist, then a script's main() function is called instead.

A script can define WinMain() in one of two ways:

function WinMain () { // No parameters were defined } function WinMain (strCmdLine, nCmdShow) { // The command line and command show values have been provided }

Host Object

Like in the console version, WQVM.exe also provides a Host object.  However, not all methods are available, and some methods are unique to the windowed environment.

Under WQVM.exe, the following Host methods are unavailable: OpenStdInPipe(), OpenStdOutPipe(), and OpenStdErrorPipe().  The ConsoleTitle and CodePage properties are also unavailable.

Two methods are unique to the WQVM.exe Host object:

One property is unique to the WQVM.exe Host object:

The Windows property is the interface to the windowed environment of Windows.  The object returned has these methods:

WQVM.exe does not provide a message pump, window creation, or GUI elements beyond the message box.  QuadooScript plug-ins should be used to provide additional graphical and windowing functionality.

Embedding QuadooScript in Other Applications

It is very easy to include QuadooScript in another application. The first step is to decide whether the application should include pre-compiled byte-code or compile the script on startup. QuadooParser.dll includes a single API for parsing and compiling script text (or script files) into a binary byte-code stream.

HRESULT WINAPI QuadooParseToStream (PCWSTR pcwzFile, __out ISequentialStream* pstmBinaryScript, IQuadooCompilerStatus* pStatus) HRESULT WINAPI QuadooParseTextToStream (PCWSTR pcwzText, INT cchText, __out ISequentialStream* pstmBinaryScript, IQuadooCompilerStatus* pStatus)

If you do not have an implementation of ISequentialStream available, then you can use QuadooScript's default implementation:

HRESULT WINAPI QuadooAllocStream (__deref_out ISequentialStream** ppStream);

Once byte-code is ready to be executed, an application calls the QVMCreateLoader() method from QuadooVM.dll.

HRESULT WINAPI QVMCreateLoader (__deref_out IQuadooInstanceLoader** ppLoader)

Now the application calls methods from the IQuadooInstanceLoader interface.

interface __declspec(uuid("8C32C545-0802-4e32-A830-83EA42BA2870")) IQuadooInstanceLoader : IUnknown { virtual HRESULT FindInstance (RSTRING rstrProgramName, __deref_out IUnknown** ppunkCustomData) = 0; virtual HRESULT AddInstance (RSTRING rstrProgramName, IUnknown* punkCustomData, ISequentialStream* pstmProgram, DWORD cbProgram) = 0; virtual HRESULT LoadVM (RSTRING rstrProgramName, ULONG cbAllocStack, __deref_out IQuadooVM** ppVM) = 0; virtual HRESULT RemoveInstance (RSTRING rstrProgramName) = 0; };

The application now calls AddInstance() with the script's file path and the byte-code in a stream.

Once the AddInstance() method returns, the byte-code has been registered and can be loaded into a new VM. To create a new VM instance, the application calls LoadVM() and passes the name of the script and the stack size. LoadVM() returns a new VM instance that is ready to execute byte-code.

interface __declspec(uuid("35FE6D03-4D05-4b49-A7A3-CD9DC2C944C1")) IQuadooVM : IUnknown { virtual HRESULT AddGlobal (RSTRING rstrName, IQuadooObject* pObject) = 0; virtual HRESULT FindGlobal (RSTRING rstrName, __deref_out IQuadooObject** ppObject) = 0; virtual HRESULT RemoveGlobal (RSTRING rstrName, __deref_opt_out IQuadooObject** ppObject) = 0; virtual HRESULT RunConstructor (__deref_out IQuadooObject** ppException) = 0; virtual HRESULT RegisterDestructor (IQuadooObject* pObject, DWORD idxDestructor) = 0; virtual HRESULT PushValue (QuadooVM::QVARIANT* pqvValue) = 0; virtual HRESULT FindFunction (PCWSTR pcwzFunction, __out ULONG* pidxFunction, __out DWORD* pcParams) = 0; virtual HRESULT RunFunction (ULONG idxFunction, __out QuadooVM::QVARIANT* pqvResult) = 0; virtual HRESULT Throw (QuadooVM::QVARIANT* pqvValue) = 0; virtual HRESULT Resume (__in_opt QuadooVM::QVARIANT* pqvValue, __out_opt QuadooVM::QVARIANT* pqvResult) = 0; virtual HRESULT Unload (VOID) = 0; virtual QuadooVM::State GetState (VOID) = 0; virtual VOID SetExternalScriptSite (__in_opt IExternalScriptSite* pSite) = 0; virtual HRESULT AddGlobalFunction (RSTRING rstrMethod, IQuadooFunction* pFunction) = 0; virtual HRESULT RemoveGlobalFunction (RSTRING rstrMethod) = 0; virtual HRESULT SetSysCallTarget (__in_opt IQuadooSysCallTarget* pTarget) = 0; virtual HRESULT SetPrintTarget (__in_opt IQuadooPrintTarget* pTarget) = 0; virtual HRESULT End (VOID) = 0; virtual HRESULT ThrowAndResume (QuadooVM::QVARIANT* pqvValue, __out_opt QuadooVM::QVARIANT* pqvResult) = 0; virtual VOID EnableCodeStepping (bool fStepping) = 0; };

(See QuadooVM.h for the full set of interface definitions.)

Before running byte-code functions, the application can expose functionality in any of three ways:

The first option will be familiar to anyone who's worked with the IDispatch interface. The application creates its global objects (derived from the IQuadooObject interface) and adds them to the VM's global namespace using the AddGlobal() function.

The second option involves implementing the IQuadooFunction interface and adding objects of this type using the AddGlobalFunction() method. These objects have only a single method, Invoke(), which also receives the name of the function being called, so it is possible to use one instance to support multiple exposed functions. These functions are called at the global scope since they have no associated object, from the script's perspective.

The third option involves implementing the IQuadooSysCallTarget interface and attaching an instance using the SetSysCallTarget() method. This is the lightest weight and most performant way to expose external functionality to scripts. Like IQuadooFunction, IQuadooSysCallTarget also just has a single Invoke() method. Instead of a method name, Invoke() receives the system call number, which was either compiled directly into the bytecode through the SYSCALL_STATIC instruction or acquired at runtime from a variable passed to the SYSCALL_DYNAMIC instruction. The instruction emitted depends on whether a literal is used with the "syscall" function keyword. If the external system call returns E_PENDING, then the VM immediately exits and sets the script's running state to suspended.

Another way to expose external data to scripts is through the IExternalScriptSite interface. Names exposed through that interface appear as properties of the global scope. For example, from the ASP environment, the Response and Session objects are exposed using the IExternalScriptSite interface.

Once the globals are loaded, the application should call RunConstructor() to run the script's global construction code. This initializes the script's global variables.

After the global constructor completes, the application is now free to call any other function it wishes to call from the byte-code. An application uses FindFunction() to look up the function index for a function and RunFunction() to call that function. When RunFunction() returns, the byte-code function has finished running.

Before unloading the VM, the application should call the Unload() method on the VM. This ensures that everything is properly freed from the VM's byte-code stack.

ActiveScript and ASP

Included in the QuadooScript package is a file called ActiveQuadoo.dll.  This is a version of QVM.exe that can be loaded by an ActiveScript host, such as CScript.exe, WScript.exe, or by ASP.dll (using IIS).

When loaded by an ActiveScript host, the output routines (Host.Write(), Host.WriteLn(), Host.BinaryWrite(), print(), and println()) are redirected through the host.  In the case of CScript.exe, output will appear on the console, but with WScript.exe output will appear in popup message boxes.  When used from the ASP environment of IIS, output is sent to the web browser!

For a web page to use QuadooScript, it must tell the ASP environment that it is written in QuadooScript by placing the following declaration at the top of the web page:

<%@ language="QuadooScript" %>

Alternatively, the ASP environment can be configured to use QuadooScript by default.

When using QuadooScript outside the ASP environment, the #include "" directive can be used to include other QuadooScript files.  In the ASP environment, files are included using special ASP syntax:

<!-- #include file="..\inc\core.asp" -->

That still inserts the specified file's text into the page at that location, but ASP will automatically remap line numbers for error logs when exceptions occur.

Unlike other scripting languages, QuadooScript does not allow executable statements (other than variable declarations) at the global scope.  Therefore, all text blocks must be typed within functions.  After declaring QuadooScript as the page's language, the remainder of the page will be wrapped within <% and %> markers.  Free text would then be placed within %> and <% markers.  Just like with a script running under QVM.exe, execution begins with the main() function.

QuadooScript never sees free text blocks or the text that they contain.  The ASP environment converts those blocks into Response.WriteBlock() calls.  The Response object is provided by the ASP environment at runtime.

The ASP environment is somewhat language agnostic and doesn't know what kind of language syntax will ultimately be used, but it assumes that Response.WriteBlock() will be valid syntax.  Fortunately, that is valid QuadooScript syntax, with one issue.  The ASP environment does not emit a semicolon at the end of the statement.  Normally, this would be a syntax error for QuadooScript.  However, when used within the ActiveScript framework, parsing is done slightly differently, and semicolons at the end of statements become optional.

Since so much of an ASP page will involve calling methods on the host's Response object, it would be a good idea to declare the Response object upfront using extern.

extern Response; extern Session;

If you intend to use ASP's Session object, then it could be declared as well.

While you could also declare the Request object, you might want to use a special version of the Request object that is built into ActiveQuadoo.dll.

var Request = Host.ParseFiles(Session); var Browser = Request.Browser;

This works only if form variables have not been accessed yet.  The returned Request object will now parse the form variables instead of ASP's object.  The benefit is that ActiveQuadoo's object can handle uploaded file data.  There are also a few convenience methods available that make it easy to route the page request to handlers based on form variables.

The following is an example of a web page that uses QuadooScript:

<%@ language="QuadooScript" %> <% extern Response; extern Session; var Request = Host.ParseFiles(Session); var Browser = Request.Browser; function main () { %> This is my page text!&nbsp; Your browser is: <% =Browser.Browser %>! <% } %>

Request Properties

There are three collections (map objects) on the Request object that can be retrieved directly as exposed properties:

Since those properties return maps, all methods available to maps can be used with those objects.

When files are uploaded, each file's name is accessible using Request.Form["file_upload_field"].  The file data is exposed as a binary data object from Request.Files["file_upload_field"].  The binary data object also exposes the file's name.

Uploaded files can be enumerated using the following code:

var mapFiles = Request.Files; for(int i = 0; i < len(mapFiles); i++) { var oData = mapFiles.GetValue(i); var strName = oData.FileName; }

Normal form variables can also be retrieved using this syntax: Request["form_variable_name"].

Server variables are retrieved using the indexed ServerVariables property.

If there is a query string that's different from the form, its variables can be retrieved using the indexed QueryString property.  The query string, if available, can also be returned as a string using the QueryString property.  The cookies can also be returned as text using the CookieText property.

The Request object also exposes ContentType, UserAgent, and Browser properties.

Request Methods

The Request object supports several methods for simplifying common operations like checking for form variables and making decisions based on form variables.

The Request.Select() method allows easy routing based on the form.  Form buttons with names can be used to select a different code path into an object managing the web page, for example.  If nothing matches the contents of the array, then a default code path can be taken.

The following example would select a different code path based on the form:

class CPage { function Main () { } function DoEdit (strValue) { } function DoNew (strValue) { } function DoDelete (strValue) { return false; // Returning false causes the Select() call to return false. } } function main () { var oPage = new CPage; if(!oPage.Select(oPage, { "Edit", "New", "Delete" }, "Do")) oPage.Main(); }

The third parameter is an optional prefix.  If specified, the method name expected to be called has the prefix value added.  The form variables do not contain the prefix in their names.

Page Transfers

When using ActiveQuadoo's Request object, the Host.Transfer(Session, Server, strPage [, strForm]) method can be used to transfer control to a different page.  The Session and Server objects must be passed to the method because, internally, Server.Transfer() is ultimately used to transfer, and all the rules and conditions of that method apply.

The benefit to using Host.Transfer() is that this method will also collect data and pass it through the Session object to be retrieved by the target page.

  1. The form data will be collected and attached to session variable TransferForm.
  2. The server variable SCRIPT_NAME will be set as session variable ReturnPage.
  3. Session variable Transferred will be set to true.

If the target page wants to continue using the transferred form, then it should use Host.ParseFiles(Session) to collect the form data.  If the target page wants to preserve the form data, then it should exclude the Session data from the call.

// Do not read the page transfer var Request = Host.ParseFiles();

This model can be used to transfer control to a login page whenever credentials expire.  If credentials are successfully entered, then control can be passed back to the original page (using Server.Transfer()) without any loss of data or extra coding.  The original page might not even realize that a page transfer occurred.

In the login model that has been described, it is important to note that the login page would use ASP's Server.Transfer() method to return control to the original page, instead of using Host.Transfer().  This is because once the user has entered credentials on the login page, control needs to be returned to the original page without overriding the transferred page data that's already in the Session object.  ASP's Server.Transfer() method merely transfers control to the new page without changing any other session data.

WinHttp Plug-in

Since so much of what developers need to do involves making web calls, it makes sense to include a WinHttp plug-in in the QuadooScript package.  The QSWinHttp.dll plug-in makes it easy to send and receive data from web endpoints.

#define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 function main () { var oHttp = Host.LoadQuadoo("QSWinHttp.dll"); var oSession = oHttp.Open("WinHttp Web Agent", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, null, null); var oServer = oSession.Connect("www.quadooscript.com", 0); var oRequest = oServer.OpenRequest("GET", "/", null, null, 0, null, null); wait(oRequest, -1); println(oRequest.Status); println(oRequest.ToText()); }

Server objects may also be opened with a user name and password.

var oServer = oSession.Connect("www.quadooscript.com", 0, "user", "password");

The server object's OpenRequest() method expects seven arguments:

  1. strVerb
  2. strResource
  3. strReferrer
  4. vAcceptTypes - Can be null, a string, or an array of strings
  5. nFlags
  6. strHeaders - Can be null or a string
  7. vBody - Can be null, a string, or a binary object

If the response is binary data, then the ToBinary() method should be used.