LavishScript:Language and Engine Overview

From Lavish Software Wiki
Jump to: navigation, search

Introduction

LavishScript is designed for a mixed, hostile environment. That is, an environment where objects and types of objects may be forcefully removed without notice by an external source. For example, as part of an addon to a game that was not meant to have addons, with scripts to automate gameplay and such. Safety is ensured by discarding object references after use (as each data sequence is closed).

LavishScript is pre-emptively scheduled and also has a concept of atomic code sequences. Pre-emptive scheduling is necessary to allow a script to run "in the background" iteratively while a game plays, as opposed to a purely event-based paradigm, though it is possible to create fully event-driven scripts as well.

LavishScript's structural syntax is derived from C, with statement syntax derived from command-line interpreters.

LavishScript is object-oriented. Each piece of data is an object, and each type of object (object type, also known as data type) may have various operations or other objects associated with it, by containment or otherwise. Object types can be created via script, or in C++. Object types follow a single-inheritance model (though special handling can be used for object types created in C++ to use any sort of inheritance). Object sub-types can be used to create an object that uses another type (compare to C++ templates), such as a collection of strings.

The Language

Preprocessor

LavishScript includes a built-in preprocessor. The preprocessor's job is to take input (generally a file), and apply given transformations (such as word replacements, macros, pre-selection of portions of code, insertion of additional files, and so on), sending the output to the engine for final processing. See the preprocessor page for a complete reference to the LavishScript preprocessor.

Comments

Comments are a method of introducing plain text into code for human readability, and are completely ignored by the script processor. LavishScript supports two types of comments.

ANSI-C Comments

ANSI-C comments begin with /* and end with */. This style of comment may begin and end anywhere (including in the middle of other lines) without restriction, but may not be nested. For some tips on using ANSI-C comments, see Standards and Style for Coding in ANSI C.

/* This is an ANSI-C Comment */
/********************************
 *                              *
 *          So is this          *
 *                              *
 ********************************/
/*
 * And this
 */

Full-line Comments

Full-line comments begin with a ; as the first non-whitespace character on a line, and end naturally at the end of the line. This style of comment cannot be used in the middle of a line.

; This is a full-line comment
echo hi; This is NOT a full-line comment

Object type definitions

Types of objects can be defined as part of a script. These object types will only be available for object creation or typecasting within their owning script, but objects of these types can be accessed anywhere within scope of the object. Object scopes are discussed in the Variables section below.

Object type definitions begin with the keyword objectdef, followed by the name of the type of object, such as

objectdef character

Inheritance can be specified by using the keyword inherits, followed by the name of the script-defined object type to inherit, such as

objectdef player inherits actor

The lines immediately following the objectdef line describe the contents of the new object type. They may contain variables, functions, methods, members and atoms. The contents of the objectdef begins with a line containing only {, and ends with a line containing only } (referred to as a code block).

The following is an example of a valid, empty object definition:

objectdef player inherits actor
{
}

See Script-Defined Object Types for further information on usage and implementation, such as object initialization and shutdown routines (constructor and deconstructor), and special members and methods.

Functions

A function is a sequence of code to be executed. Function definitions begin with the keyword function, followed by the name of the function, along with a set of parentheses such as

function MoveTo()

The parentheses are actually to contain a list of parameters, separated by commas. Each parameter becomes a local variable when the function is eventually called. Each parameter definition begins with the name of an object type, such as string, followed by the name of the parameter, such as Name. The following is an example of a function with parameters:

function InitializePerson(string Name, uint Age, bool Male)

This example has three parameters, a string called Name, a uint called Age, and a bool called Male. It would presumably keep a record of some personal information entered in a user interface.

To assign a default value to a parameter if not all parameters are given to the function, follow the name of the parameter with = and the default value, like so

function InitializePerson(string Name, uint Age, bool Male=TRUE)

To accept a variable number of parameters to a function, use ... as the object type, as the last parameter, such as:

function HowManyParameters(... Params)

The variable will be an array of strings, with the size equal to the number of parameters actually given to the function.

All functions have the ability to return an object. By default, the return type is string. To return another type of object, append the function keyword with a colon, followed by the object type (this is the same construct as object sub-types), like so

function:bool InitializePerson(string Name, uint Age, bool Male=TRUE)

The returned object is initialized to the object type's default (empty, zero, etc) value when the function is called, and can be directly accessed via an object called Returning. The Return command can be used to end execution of the function at any point -- if the command is used without following parameters, the return object is returned unchanged. If parameters are used, the original object is destroyed, and a new object is given, initialized with the parameters given. The object can be retrieved from the calling function with the Return object.

Immediately following the function line, a code block must be given, beginning with a { line and ending with a } line. The following is a complete example of a function:

function:bool InitializePerson(string Name, uint Age, bool Male=TRUE)
{
  echo ${Name} is ${Age} years old and is ${If[${Male},male,female]}
  return TRUE
}

Function code blocks may contain variables, control structures and statements (commands or object methods) to execute.

To call a function, use the Call command, followed by the function name, and then any parameters, like so:

call InitializePerson Jimmy 12 TRUE
echo ${If[${Return},"InitializePerson successful","InitializePerson failed"]}

Special types of functions

There are 4 types of functions in LavishScript, each defined in the same way, but with their own keyword: function, atom, member, and method. Each type of function is also executed in its own way. Here's a little tree to show you how these types of functions are related:

  • function
    • atom
      • member
      • method

As the tree indicates, an atom is a function, and members and methods are atoms.

Here's the differences.

  • A function is simply a segment of code. A function is not guaranteed to execute to completion before anything else happens -- it may be broken up by the scheduler to run on subsequent frames, and functions are also allowed to use certain commands that force a delay. Because of this, a function cannot be used directly in a data sequence (which begin with ${ and end with }), because data sequences are atomic. When something is atomic, it cannot be broken up (in this case, meaning that it cannot wait until a subsequent frame).
  • An atom therefore is an atomic function. The difference is simply that it is guaranteed to execute to completion immediately, cannot use delays, will not be broken up by the scheduler, and so on, and therefore an atom can be used in a data sequence. Atoms (but not members or methods) can also be used as commands within their scope.
  • Members and methods are specialized atoms for use with objects, which have the advantage of natively being used in data sequences. These can only be created within the context of an object type definition. Member and method definitions can also be created without parameters or a code block, but instead a data sequence to redirect to, like so:
member Location=${Me.Location}

Any parameters given to members or methods defined in such a way will be given as indices to the final object in the given data sequence (e.g. ${Me.Location[1,2,3]}).

Atoms

Atoms are defined just like functions, but with the atom keyword. Atoms cannot be part of an object definition.

atom MyAtom()
{
}

Similar to variables, atoms can be placed into one of three scopes: script, global, or globalkeep. To apply scope to an atom, append parentheses containing the name of the scope to the atom keyword, before the sub-type, if any.

  • script
The script scope is the default for an atom if no scope is given. In this scope, the script context is the current script, and the atom will be destroyed upon the script ending.
  • global
The global scope may be specified to create an atom that is accessible globally, but retain the context of its defining script. In this scope, the script context is the current script, and the atom will be destroyed upon the script ending.
  • globalkeep
The globalkeep scope may be specified to create an atom that is effectively detached from the script, i.e. a persistent script-defined command. In this scope, the script context is the global script -- you cannot directly access in-script data or functions -- and the atom will be kept after the script ends. Defining the same global or globalkeep atom twice will replace the existing atom.
atom(script) MyAtom()
{
}
atom(global) MyAtom2()
{
}
atom(globalkeep) MyAtom3(... Params)
{
   if !${Params.Size}
   {
     echo "Syntax: MyAtom3 <your name>"
     return
   }
   echo Hi ${Params[1]}!
   return "${Params[1]}"
}
atom(script):bool MyAtom4()
{
  return FALSE
}

Atoms can be executed in a few different ways. The easiest way within the atom's scope is to use the atom as a command (note that this is case insensitive):

/* will output "Syntax: MyAtom3 <your name>" */
myatom3
/* will output "Hi Bob!" */
myatom3 Bob

The other way is to use one of the available ExecuteAtom members or methods:

Script:ExecuteAtom[MyAtom]
LavishScript:ExecuteAtom[MyAtom3,Jimmy]
echo ${LavishScript.ExecuteAtom[MyAtom3,Bob]}
echo ${Script.ExecuteAtom[MyAtom4]}

Variables

A variable is an object created of a given type for use in LavishScript. This differs from built-in objects (referred to as Top-Level Objects), which are simply there, or added by a LavishScript module.

Variables can be created as part of script structure, or dynamically while a script is running with the DeclareVariable command. This section will detail creating variables as part of script structure.

Variables are placed into one of five "scopes", which describe where the variable exists, and where it does not. When the scope of the variable is destroyed, the variable is destroyed too. The five variable scopes are as follows:

  • local
Exists only within a function. This scope is default (and the only scope available) when declaring a variable within a function.
  • object
Exists only within a script-defined object. This scope is default (and the only scope available) when declaring a variable within an object type definition (not within one of its functions)
  • script
Exists only within a script. This scope is only available outside of functions or object type definitions, and is default in that case.
  • global
Exists globally, but destroyed with the owning script. This scope is only available outside of functions or object type definitions.
  • globalkeep
Exists globally, not destroyed with the owning script. This scope is only available outside of functions or object type definitions.

Variables are defined with the variable keyword, followed by the type, and finally the name of the variable, like so:

variable type name

Where type is the object type to use, and name is the name to use. Variables may be initialized to given values on creation by adding an equals sign (=) and the default value. For object types that allow variable initialization with multiple parameters, these must be separated by spaces (follow basic LavishScript Command Syntax for parameter grouping). To specify a different scope for the variable, the scope is placed in parentheses and attached to the end of the variable keyword, like so:

variable(globalkeep) string MyString="Initialize to value"

In the above statement, a global variable that will be detached from the script is created, called MyString, of the string object type, and initialized to Initialize to value.

As the variable keyword is part of script structure, all variable statements in a function are processed BEFORE all other statements in that function. In other words, the following code...

function main()
{
 echo "Hello World! ${MySecondString}"
 variable string MyString="One"
 MyString:Set[Two]
 variable string MySecondString=${MyString}
}

... results in the output...

Hello World! One

This is because variable keywords are processed in order at the beginning of function execution, before the function begins to execute. Both variables are initialized with the given values before the echo command executes, making MySecondString contain the value One because the MyString:Set[Two] command has not changed the value of MyString, and therefore resulting in the given output rather than Hello World! NULL, which would happen if MySecondString had not initialized.

Control Structures

Some keywords can change the flow of execution of the script. A control structure is generally made up of one or more keywords, such as "if", followed by a condition or other expression, along with one command or a code block. These structures may cause a section of code to repeat, or only be executed under a certain condition. These keywords along with the commands or code blocks they affect make up control structures. See Mathematical Formulae for insights on how "conditions" are processed (they are simply math!).

Conditional

Note: There is also Inline branching (not a control structure)

Repetitive

Selective

Statements

Commands

Commands are simply LavishScript commands, as may be entered in a console or command file. See Command_Syntax for LavishScript command syntax rules.

Object methods

Object methods can be used as commands by leaving off the ${}, which would cause the data sequence to be reduced to text, in a valid data sequence ending in a method. Here is an example of a method used as a command

MyObject:MyMethod

Special functions

Main

All scripts must have a main function. This function is called when the script begins, and any parameters passed to the script are passed thusly to the main function. When the main function ends, the script ends. Any return value from the main function is discarded.

AtExit

The atexit atom is executed when the script ends, either naturally, forced, or because of an error. This can be used to perform cleanup operations for the script, such as unloading user interfaces, modules, and so on.

Script Errors

Preprocessing

Preprocessor errors are simply displayed, and the script fails to launch. Preprocessor errors only happen as a direct result of preprocessor directives, or the source file not existing to process in the first place.

Post-processing

Post-processing errors are those found while the script structure is being cached and optimized. This type of error is identified by the source file, line number, and the text on the line, as well as a description of the problem. A common error is a missing } causing errors like "Local function definitions not allowed".

Runtime

Runtime errors are those found while the script is in the process of running. These errors generally display a line containing the error message, followed by a stack dump of the script's current state. This shows each file, line number and line in the call stack, with the current line at the top.

Example Scripts

Example 1: Hello World

function main()
{
   echo Hello World
}

Example 2

A fairly pointless script that demonstrates looping, functions, variables, data methods

function MyFunction(int Count)
{
  return -${Count}
}

function main()
{
  variable int Count=1
  do
  {
     call MyFunction ${Count}
     echo Count: ${Count} ${Return}
  }
  while (${Count:Inc}<=10)
}

Example 3

The same pointless script as above, but with a parameter passed to main to determine the size of the loop.

function MyFunction(int Count)
{
  return -${Count}
}

function main(int Max)
{
  variable int Count=1
  if (${Max}<1)
    return
  do
  {
     call MyFunction ${Count}
     echo Count: ${Count} ${Return}
  }
  while (${Count:Inc}<=${Max})
}

The script may be run like so:

runscript myscript 10