Click here to Skip to main content
14,927,843 members
Articles / Desktop Programming / System
Article
Posted 18 Dec 2018

Stats

127.8K views
23 bookmarked

VRCalc++ Object Oriented Scripting Language :: Vincent Radio {Adrix.NT}

Rate me:
Please Sign up or sign in to vote.
3.98/5 (27 votes)
14 Jun 2021CPOL55 min read
VRCalc++ OOSL Engine is embeddable in any Delphi Application using Dynamic Packages
VRCalc++ is a Structured General Purpose Object Oriented Scripting Programming Language which interpreter Engine is Embeddable in any Delphi Application using dynamic Delphi Packages.

Image 1

Introduction

VRCalc++ is a Structured General Purpose Object Oriented Scripting Programming Language which Interpreter Engine is written using Delphi Object Pascal and it is embeddable in any Delphi or C++ Builder Application that uses packages.

Born as a simple calculator, VRCalc++ features the same operators as C++ and Java and extra objects related operators implemented using Delphi object interfaces as well as a complete set of procedural flow control structures including:

  • selection statements
  • loops control statements
  • labelled statements
  • exceptions control statements
  • thread synchronization statements
  • class modules
  • methods and properties
  • lambda functions

and so on ...

VRCalc++ includes support for the following:

  • access to the Delphi RTL package data, funcs and procs
  • Dynamic Arrays
  • run-time exceptions handling
  • multi threaded functions and objects
  • text and binary files access
  • files and directories system access
  • Binary Serialization
  • DLL & Delphi Packages (BPL) access (for Windows as well as the others supported OS)
  • the VRCalc++ Standard Scripted RunTime Library provided by Vincent Radio {Adrix.NT}

and more ...

the VRCalc++ Engine is embeddable in any application using Delphi packages because its scripting run-time code and other environment support exported functions are also contained in Delphi packages.

VRCalc++ is extensible by object oriented scripted class modules or by external packages implementing and exporting the required functions and interfaces.

VRCalc++ Scripted Class Modules usually define and export functions as well as properties

the VRCalc++ base independent script Engine code can also be recompiled and run under Linux and MacOS.

VRCalc++ Console is a Delphi VCL Windows Application using the VRCalc++ Scripting Engine Package. It can also be used as a simple, immediate and fast calculator by executing simple scripts or scratch code.

Image 2

VRCalcSX is a Console Program that only executes VRCalc++ scripts

VRCalc++ FMX Script Executor is a Multi-Platform Delphi Application that Executes VRCalc++ Scripts

these Applications are extensible by pluggable modules as any other VRCalc++ Enabled Application.

VRCalc++ Plug in Modules are *.BPL Files (Delphi Packages) responsible for registering the required external functions into some namespace starting from or in the Global Names List Root which is accessible using the At Prefixed Operator ("@") so they can be addressed by scripted code.

Notes

in order to run the sample scripts contained in this article then you need to ...

  • set up your system PATH variable and the other required system Environment Variables to find the main required *.BPL Modules (Embarcadero, Vincent Radio) and the VRCalc++ Std Scripted RunTime Library
  • use the VRCalc++Console, VRCalcSX or some other application that can execute VRCalc++ main Scripts
  • correctly Load in this application the required VRCalc++ Binary Application Plug-in Modules (*.BPL) provided by Vincent Radio
  • correctly Load in this application the VRCalc++ Std Scripted RunTime Library provided by Vincent Radio

VRCalc++ Links

You can find:

you can also find ...

Background

to develop VRCalc++ OOSL I inspired myself to the FRED Programming Language of Framework III by Bob Carr (Forefront Corporation, first developed in 1983 and distributed by Ashton-Tate).

Image 3

 

index

Language Overview

About VRCalc++ Reference Counted Objects

VRCalc++ Variables

VRCalc++ Scripted Class Modules

VRCalc++ Operators

VRCalc++ Built in Functions

VRCalc++ Special Prefixed Operators

VRCalc++ Objects Names

VRCalc++ Variable Declaration

VRCalc++ Function Definition

VRCalc++ Property Definition

Using VRCalc++ Functions & Properties

Understanding (Function) Object Instances

About VRCalc++ (Identificated) Value Holders

VRCalc++ Scripted Class Registration Example

Defining a Function Example

Using a Lambda Function Samples

Creating and Using a Scripted Object Example

About VRCalc++ Binary Serialization

About the VRCalc++ Standard System Scripted Run-Time Library

VRCalc++ Language Code Samples

Extending VRCalc++

Using the VRCalc++ Engine in Delphi Applications

Notes about Files Dependency List Files

 

 

Language Overview

The VRCalc++ Engine interpreter is mainly used to Evaluate VRCalc++ Scripts.

The VRCalc++ Engine can also be used in a Delphi Application to invoke Script Defined Functions & Properties.

VRCalc++ is a Case-Sensitive Scripting Language (so "aname" and "aName" are two distinct identifiers).

A VRCalc++ Script is usually contained in a text file.

Common file extension types for VRCalc++ Scripts are "vrc" (for a main entry point stand alone script file) and "vrcm" (for scripted class modules).

A VRCalc++ Script is usually Executed by the Engine as soon as it is Loaded from its text file.

Expressions

In a VRCalc++ Script, everything is evaluated as an expression that returns a value or raises an exception, even when you declare a function, a property or a variable. However, the expression value is discarded unless you assign it to a variable or other Value Holder using the assignment ("=") operator.

In VRCalc++, Expressions Evaluation Order is always from Left to Right.

Expressions are separated by the comma symbol (",") and can be enclosed in normal rounded "(...)" or curly "{...}" blocks to change the normal evaluation precedence of operators. the result is the last evaluated one.

There are three (3) types of code blocks:

  • (...) : rounded normal used in expressions and function body blocks
  • [...] : square used for indexing, names and other special cases
  • {...} : curly used in expressions and function body blocks

Basic Syntactic Elements

Comments are parsed and ignored by the interpreter.
Comments can be on Multiple text lines or can be placed at End of a Single text line.
Multiple Text Lines Comments start with "/*" and end with "*/".
Single Line Comments start with "//" and continue until the end of the text line.
Nested multi-lines Comments are Not Allowed.

Identificators can start with a letter char (A..Z, a..z) or an underscore char ("_") followed by any sequence of letter chars (A..Z, a..z), number chars (0..9), underscore chars ("_")

Integer number literal constants can be specified using a sequence of the digits (0 .. 9).
You may also specify the type of the integral (signed | unsigned (the value is signed by default)) along its value (by default the value is the biggest int delphi type (acually it is int64 (signed) or uint64 (unsigned) delphi types)).
You may also specify if it is a signed or unsigned integer by writing "i" or "u" or "ui" after the digits value ("u" or "ui" denote an unsigned value). the type can be also specified in uppercase ("I", "U").
Support for hex integral unsigned constants is also provided.

floating point number literal constants can be specified using a sequence of the digits (0 .. 9) followed by a dot (.) and a sequence of digits for decimals (0 .. 9). Then, you may also specify the exponent using (E | e) an optional sign (+|-) and an int number for the exponent. Then, you may also specify the (F | f) to indicate that it is a floating point number.

string literal constants can be specified by any sequence of chars enclosed by double quotes "...", like this: "this is a string constant".
string literals can be split by Spaces, TABs, EOL Chars, like this: ( "hello " "adrix" " you are an awesome programmer" ), the result is always concatenated into a Single String.
 
char literal constants can be specified using single quotes to surround them, like this '#' or this '@'.
Both string & char literals include support for escape sequences. The Escape Char is ("\"). The Escape Char can also be used in String Literals. For example ...
  • '\'' // the ' char
  • '\"' // the " char
  • '\t' // the TAB char
  • '\r' // CR
  • '\n' // LF
  • '\x' // prefix to specify hex digits for a char, for example: '\xFA'
  • '\d' // prefix to specify decimal digits for a char, for example: '\d13'

Operators Symbols like [+, -, *, /, \, %, (, ), @, $, #, ^, ...] including block start & block end chars (* see Operators below)

Notes

Note: You may also concatenate strings and other values using the ("+") operator because the VRCalc++ Engine tries to convert the value to a string, the first operand must be a string value, like this ...
"hello " + "adrix" + '.' + 5

In VRCalc++ values are Delphi Reference Counted Objects.

In VRCalc++ variables are Objects that implement the ValueHolder Delphi interface.

References to Value Holders are also supported.

All computations involving integral and floating point objects are performed at the max precision, that is:

  • 64 bits for integers
  • Extended for floating point

Then, you may convert the result using the provided functions.

 

About VRCalc++ Reference Counted Objects

VRCalc++ Objects are (Delphi Object Pascal) Reference Counted Objects.

A "strong" reference is a reference that increments the number of references an object have.

When an object references count reaches (0) then the object is destroyed.

During the destroy process, an object releases all the references to other objects it owns (for exmple a container implicitly releases all the references to its contained objects).

For example, a container object releases all the refs to any contained object when it is destroyed.

Because of this, you cannot have mutual objects references, that is objects that strongly reference (add a reference) each other directly or indirectly. For example, a contained object cannot strongly reference its container.

in other words the diagram of object references must be a Direct Acyclic Graph (DAG) starting from the Global Environment or the System Stack and ending to the last referenced object.
you cannot have mutual or backward references.

For this purpose, you may declare a Variable to be "weak" that is it does not add a new reference to a given object (for example a container object).

 

VRCalc++ Expression Blocks

VRCalc++ Expressions can be grouped in Blocks to change the normal evaluation operators precedence.

The expression blocks can be ...

  • (...) : rounded blocks
  • {...} : curly blocks

these kind of blocks are also used to enclose the body of a function

another type of expression block is ...

  • [...] : square blocks

that are used to specify names and other kind of values or are used in some other special circumstances

Code Blocks Evaluation Order of inner Expressions is always from Left to Right.

Note: An empty code block is always evaluated as null

 

VRCalc++ Variables

A VRCalc++ Variable can contain any type of object from time to time. The type of a Var is the type of value it contains in that moment. A VRCalc++ Variable is created when it is first assigned in its default context. However, you can explicitly declare a variable in a specified context.
You cannot explicitly declare a Var twice (it raises an exception).

The Name of a Variable follows the same rules for VRCalc++ Object Names (* see below ...)

The main contexts for a variable are:

  • global
  • thread (global access)
  • class (scripted module)
  • instance (scripted module instance)
  • local (inside a function or a local env)

global and thread vars declared by a module should have a distinctive name and should be explicitly declared to avoid conflicts with variables with the same name declared by other modules.

class and instance vars are not explicitly exported to other modules and should be accessed using properties. However, there are some RTL functions to access these inner variables.

You may create a Var in a local environment, then a variable name in the local env hides the variable with the same name in the outer env.

For example ...
{
   a = 123,
   @localenv {
      @var a = 2.5 // this is a new var the above "a" is hidden here
   },
   @VRStd.Out << a
},
""

The above code will print "123" on the std out channel.

When you write something like "5 + 2" in a script, then an int object is created for the number "5", another int object is created for the number "2" and the result is an integer number object holding the value "7".

Note that if you write ...
a = 5,
b = a

in a script, then a and b refer (hold a reference) to the same object value.

To copy only its value and create a new number object, you have to write something like ...
a = 5,
b = a + 0 // this forces the creation of a new int object
or you may write ...
a = 5,
b = @integer (a) // this forces the creation of a new int object

or ...

a = 123,
b = + a // this forces the creation of a new int object

So be aware when dealing with reference counted objects whose semantic is copy!

By default, a Var is a "strong" ref Var type that is it adds a ref to the contained object value.

A Variable can be declared to be "weak" that is it does not add a new reference to its object value.
This is especially useful, for example, when a contained object has to weakly reference its container.
 
you may also create Lists of Variables as needed in a hierarchical way.
 

VRCalc++ Scripted Class Modules

VRCalc++ Scripted Class Modules are stored in Text Files (usually with the "*.vrcm" file ext).

A Scripted Module is executed as soon as it is loaded.

A Scripted Class Module should have a class name and should register itself into the Global Named Values List (starting from the Global Root of Registered Names) when it is first executed by the Engine.

WARNING: if a scripted module does not register its name starting from the Global Root (or in some other namespace starting from the Global Root) then its contents are lost after execution.

VRCalc++ Scripted Classes usually define:

  • functions
  • properties

These objects stored into the Scripted Class Module can be:

  • static
  • instance

static functions and properties can be invoked on the Scripted Class, while instance ones need an instance of the given class.

a Scripted Class Module (and its instances) define its interface using named functions and properties.

A function can also be private and with an empty name private functions are not registered into the scripted class module but they can be passed as arguments to other functions or their function definition value can be stored into variables for later invocation.

note: The VRCalc++ Engine was updated so now Lambda Functions are supported in this version of the engine.

Lambda functions can avoid the use of small scripted classes instances with instance data implementing a given interface.

A scripted function takes a variable number of arguments. Arguments may have a name in a function definition.

Named arguments became local variables on the stack when the function is later invoked if the function call has fewer args then an exception is raised. There is NO Explicit public interface declaration in VRCalc++.

A scripted class module defines its interface implicitly by defining:

  • functions names (with their related named arguments)
  • properties names

The name of a function or a property can be any string. If this name contains spaces or other special characters, it should be defined as follows: ["#this name contains spaces and special chars !!"]. Also, formal arguments names can contain spaces and other special chars in the same format. A name can also be an expression enclosed in square braces as follows: [ expr ]. The given expression should return a string object.

Accessing Variables of a Scripted Class Module

note that class vars and instance vars are not automatically visible outside the class module or class instance where they are defined.
to access them you have to define related properties and functions.
however, there are special engine functions to access them but their use should be for inspection only.
a class module and its instances should be accessed only using the exported functions and properties.

Comments in a Script

VRCalc++ Scripts can contain comments:

  • multiple line comments start with "/*" and end with "*/" symbols (nested comments are not allowed)
  • single line comments start with the "//" symbol and extend to the end of line

All comments are skipped by the VRCalc++ interpreter.

comments samples ...

/*

   this is a multi line comment
   author: Adrix
   version: 2.3.7

*/
aVar = 123.456, // this is an end of line comment

WARNING: a comment is always parsed even in loops so its better to place comments before a function definition for performance reasons.

Class Module Registration

Every Scripted Class Module should register itself into the Global Named Values List, starting from the Global Root in some given other Name Space, otherwise it cannot be accessible by user code. This Global Root List is a Hierarchical List of name spaces made of Named Objects. That is Objects with a Name implementing the given Named Object interface.

A Scripted Class Module is also a Named Object.

A Name Space is a List of named objects and that is also a Named Object.

To specify the name of the class module, use ...
@classmodule.name MyClassModuleName // to give a name to a scripted class module

Class Inheritance

A Scripted Class can inherit defined names, values and implementation from one or more Ancestor Classes.

To specify an inherited class module name, use ...
@classmodule.extends (@VRSystem.Exceptions.RuntimeException) // for example

To invoke an inherited method of an ancestor class, you have to specify a reference to its class (usually its pathname from the Root).

For example ...
@proc MyMethod()
{
   @MyNamespace.MyInheritedClass.MyMethod(), // a ref to the current object instance is used here
   // ...
}

The reference to the current object instance (@this) is used automatically in the above call.

 

VRCalc++ Plug In Modules

VRCalc++ Plug in Modules have to register functions and properties names into the Global Named Values List, directly into the Global Root or in some other Name Space starting from the Global Root.

 

VRCalc++ Operators

 

The Dot (".") Operator

In VRCalc++, the dot operator (".") is used to separate and access:

  • Name Space members
  • Members of a Class
  • Members of an Instance of a Class

 

In VRCalc++, the at prefixed operator ("@") is used to access the Global Root of registered names (see above).

The VRCalc++ Engine defines some binary and unary operators.

 

the Assignment Operator ("=")

 

The most important of these is the Assignment Operator ("=") ...

ValueHolder = ValueExpr

the returned value is ValueExpr so for example you may write ...

ValueHolderB = ValueHolderA = ValueExpr

a ValueHolder is any object that implements the Identificated ValueHolder interface such as a Variable or an Array Item

note: if an Identificated Value Holder does not exists yet at the moment of the assignment then it is usually created with that Expression Value

 

Binary & Unary Operators

 

Other binary operators are (listed in order of evaluation precedence) ...

  • / : divide
  • * : mult
  • div, \ : int divide
  • mod, % : modulus (remainder of an integral division)
  • + : addition
  • - : subtraction
  • <<, >> : shift left, shift right (overridden and also used for insertions & extractions)
  • <, <=, >, >= : relational order operators
  • ?==, is : compares an object value against a given class type
  • ==, != : equality test operators
  • ===, !== : equality reference test operators
  • & : bitwise and
  • ^ : bitwise xor
  • | : bitwise or
  • && : boolean and
  • || : boolean or

Some unary prefixed operators are:

  • -, + : negation, clone value
  • ! : bool negation
  • ~ : bitwise negation
  • & : get a reference to a value holder
  • * : get a value holder access from its reference
  • ++, -- : auto incr, auto decr prefix operators (post fix are NOT supported)
  • $ : to access a built in script parser (seldom used, use "@" instead)

Note: the "+" unary operator should be used to copy the value of its operand, it acts almost like a Clone operator.

Note: Almost all binary and unary operators can also be redefined by scripted class objects by implementing the required interface functions (* see source code and docs for details).

note: relational and equality test operators cannot be redefined singularly by scripted class objects. you have to define and implement the provided object comparison interface (* see source code and docs for details).
 
also the "is", "?==" operator cannot be redefined by scripted class objects.
also bool not "!", bool and "&&", bool or "||" operators cannot be redefined by scripted class objects.
 
also the "&", "*", "$", "@" prefixed operators cannot be redefined by scripted class objects.
 
VRCalc++ Operators are by default evaluated in a hierachical way from the lowest precedence to the higher one
 
Some op-assignment operators are supported such as ...
  • +=, -= : add/sub & assign
  • *=, /=, \=, %= : mult/div/intdiv/mod & assign
  • &=, |=, ^= : bitwise & assign
  • <<=, >>= : shift & assign

and so on ...

Note: op-assign operators cannot be individually redefined by scripted class modules. you have to redefine the binary ones.

 

Changing the VRCalc++ Default Operators Evaluation Precedence

the evaluation order of operators is hierarchical by default to mantain operators precedence from the lowest to the higher prcedence order

to avoid wasting system stack space and computation time in operators evaluation there is a special modality of the VRCalc++ Engine to make flat (left to right) operators parsing and direct factor evaluation along with a set of prefixed operators to make this work on an expression basis.

you may use the "#" prefix operator to directly evaluate a factor expression. a factor expression includes also a function call. for example ...

// direct factor evauluation sample
a = #123.7,
# @Math.Sin (#a)

or for example ...

// direct factor evauluation sample
a = #1,
aref = # &a,
*aref = #3

you may use the "# |" prefix operator to make a flat (simple left to right) expression evaluation. in this case you may use code blocks ( (...), {...} ) to force operators precedence

you may use the "# \" prefix operator to return to the normal hierarchical expression evaluation inside a single value expression

also there are block functions to change the default VRCalc++ Engine behaviour inside a whole code block (* see source code and docs for details)

note: actually the VRCalc++ Std Scripted Library seldom uses these feautures and it mostly performs standard operators evaluation

 

VRCalc++ Built in Functions

The VRCalc++ Engine defines and includes some basic no args system functions (also called Script Parsers) that can be accessed using the "$" prefix. Some of them are ...

Function, Property and Variables definition

  • var : used to define a variable inside a context
  • property | prop : used to define a property
  • on : used to define a function or procedure
  • to : same as above
  • function : used to define a function
  • func : same as above
  • procedure : used to define a procedure (same as "function")
  • proc : same as above

Scripted Class Object Instances Creation

  • new : used to create new object instances of a given scripted class

Flow Control

  • if : used to choose among 2 expr values based on the truth value of another expr
  • switch : used to choose among many values in an exclusive way
  • while : executes a block while a given expr is true
  • for : iterates over a container using an internal iterator
  • return : to return a value from a function
  • label : to define a labelled block in the script
  • cycle | continue : to repeat from the start of a given label or a loop
  • leave | break : to break from a labelled block or a loop

Exceptions Handling

  • try : to handle exceptions (includes an except and a finally block)
  • raise | throw : to raise an exception
  • catch : to block the propagation of an handled exception

Monitor Threads Synchronization

  • synchronize | sync : to synchronize threads access to a given object

Others

  • with : to set the Default Base VH Context for a Code Block
  • defined : to check for an (identificated) value holder existence
  • delete : to delete an (identificated) value holder from its context

Some Useful Constants

  • true : the true bool value
  • false : the false bool value
  • null : the null value
  • nil : same as null

Object Instance Access

  • thisclass : the reference to the currently running class module
  • this | self : to access the current object instance (note: it is a thread var, not an arg of a method)
  • thisvar | selfvar : to access the instance variables of the current object instance

These built in system function can be addressed using the "$" unary prefix.

You cannot get a reference to a built in function.

However, it is best to use the exported ones in the Global Named Values List using the "@" prefix (*) see examples below.

 

VRCalc++ Engine Built in Functions Description

this is a short description of some VRCalc++ built in functions and procedures (* see docs for more infos ...)

Flow Control & Exceptions Handlers

if

Syntax:
@if (boolexpr) then { // this returns null if boolexpr is false
   exprblockA
}

@if (boolexpr) then {
   exprblockA
} else {
   exprblockB
}
The "if" built in evaluates a boolean expression enclosed in a rounded block.
If the boolean expression returns a true value, then the following expressions block is evaluated.
Otherwise the following is skipped and not evaluated and if there is a next expression block, it is evaluated otherwise, if the 3rd block is missing, the returned value is null.

The returned value is the one of the expression blocks that gets evaluated or null.

the exprblock can be be enclosed in round or curly braces (preferred).

The word "then" after the boolexpr block and the word "else" after the 2nd block are optional.

 

switch

Syntax:
   @switch
   if (condExprA) then {
      valueExprA
   }
   else
   if (condExprB) then {
      valueExprB
   }
   else
   if (condExprC) then {
      valueExprC
   }
   ...
   else {
      valueExprDefault
   }

The "switch" built in evaluates an expr block for which a bool expr block returns true.

You may have many (boolexpr-i) then (exprblock-i).

You may have an else expr block which is evaluated if none of the above returns true.
The returned value is the one for which the bool expr returns true or the value of the else block (if any) otherwise the returned value is null.

If the else block is missing and none of the bool expr returns true, then null is returned.

The words "if" and "then" are optional. If there is an else block, the word "else" must be present.

 

while

Syntax:
@while (condExpr) do {

   valueExpr

}

The "while" built in repeatedly evaluates an expressions block while a given condition is true.

The returned value is the one of the last evaluated expression and it should be discarded.

The word "do" is optional.

 

repeat

Syntax:
@repeat {

   valueExpr // use @break to exit the loop

}
or:
@repeat {

   valueExpr

} while (condExpr) // repeat if condExpr is true
or:
@repeat {

   valueExpr

} until (condExpr) // repeat if condExpr is false

The "repeat" built in repeatedly evaluates an expr block.

The difference with "while" is that the optional bool expr block is evaluated after the first expr block is executed.

If no condexpr is specified, then you have to use "@break" to exit the loop.

The returned value should be discarded.

 

label

Syntax:
@label aLabelName {

   valueExpr

   @break from aLabelName // to break and exit the block

   @cycle into aLabelName // to repeat block execution from the start
}

The "label" built in executes a labelled code block. The block is entered at least once.

"aLabelName" follows the same rules for object names.

Inside the labelled block, it is possible to use ...

  • @break from aLabelName : to break and exit the labelled block

  • @cycle into aLabelName : to cause the labelled block to be executed again from the start

The returned value is one of the last evaluated expr and it is usually discarded.

 

for

Syntax:
@for (aVarName : aContainer) do {

   valueExpr

}

The "for" built in evaluates an expr block while sequentially iterating over a container items using an internal container iterator.

For each iteration, "aVarName" is set to the current internal iterator item.

"aVarName" is locally created and then released at end of the iteration unless it already exists into the current context.

Actually, the returned value is the container object itself and it is usually discarded.

The word "do" is optional and can be omitted.

Note: A valid container must implement the requested binary or scripted interfaces. (*) see docs and sources for more information.

 

break

Syntax:
@break

@break from aLabel

The "break" built in procedure is used to break from the nearest loop block that contains it such as while, repeat, for and so on or to break from a labelled block (see: label) used in the second form.

An alias of "break" is "leave".

"aLabelName" follows the same rules for object names.

Returned value: It never returns.

 

cycle

Syntax:
@cycle

@cycle into aLabel

Note: The "continue" keyword is an alias of "cycle".

The "cycle" built in procedure is used to cycle into the start of the nearest loop block that contains it such as while, repeat, for and so on or to break or to cycle into the start of a labelled block (see: label) used in the second form.

"aLabelName" follows the same rules for object names.

returned value: It never returns.

 

return

Syntax:
@return // to return from a procedure (actually returns a null value)

@return aExprValue // to return a value from a function

Used in the first form in a script defined function, the return procedure causes the enclosing function to terminate immediately by returning null to the caller (see: on, proc, procedure).

Used in the second form, it causes the enclosing function to terminate immediately by returning aValue to the caller (see: func, function and so on). aValue can be any expression.

returned value: It terminates the function or procedure and returns a value to the caller.

note: If return is not used, then the function always returns the last computed value during its body execution.

 

try

Syntax:
   @try {

      aValueExpr // this is the block to try -- use @raise to throw a structured exception object

   }
   except {

      aExceptionHandlerExpr
      // optionally test for an exception type ...
      // use @catch to avoid the exception to be raised again

   }
   finally {

      aCleanUpExpr // perform required clean up here

   }

The try built in procedure first tries to execute the block containing aValueExpr.

If some code here contained fails to execute by rising an exception.

Then the block in the except part is executed to handle the exception.

After that, the block in the finally part is always executed to perform any needed clean up.

Note: The except part is optional as well as the finally part.

You may have try blocks with a finally part only and others with an except part only and others with both parts and they must appear in the same order as they are shown above.

returned value: The returned value is one of the last evaluated expression into the block to try.

Note that if an exception is not caught (using @catch for example) into the except block, it is raised again no matter if the except block is present. also you may use @raise to raise again an exception.

 

raise

Syntax:
@raise // to raise again the last exception

@raise aEceptionExpr // to raise an exception

Used in the first form in an exception handler block, the raise procedure raises again the last exception in the form of an internal Structured Exception. Note that actually a new exception object of a structured type with a null value is created and then raised.

Used in the second form, it raises a user defined exception object (that can be any object type) in the form of an internal Structured Exception.

returned value: It never returns.

an alias of this procedure is "throw"

 

catch

Syntax:
@catch // to clear the last raised exception so it won't be raised again

used in an exception handler block, the catch procedure clears the last raised exception so it won’t be raised again.

returned value: It should be discarded.

 

Creating and Accessing Scripted Objects

new

Syntax:

@new (aClass) // this returns a new instance of the given class

@new [aClass] // same as above

this is used to create a new instance of a Scripted Class.

the returned value is an object instance of the given class.

 

thisclass

Syntax:

@thisclass // to access instance of the current class module methods & properties

the thisclass keyword is used to access the currently running scripted class module instance

its value is implemented as a delphi thread var

its object value is saved on the stack at every scripted method call and then restored afterwards

 

this

Syntax:

@this // to access the currently runnin scripted object instance

@self // same as above

the this keyword is used to access the currently running scripted object instance.

the self keyword is an alias of the this keyword

note: it is NOT an argument of a method, it is implemented as a delphi thread var.

note: this object value is saved on the stack at every scripted method call and then restored afterwards so it acts like a constant argument value of a method

 

VRCalc++ Special Prefixed Operators

There are some special prefix operators you may use for short:

  • $ : used to access a built in function (seldom used, use "@" instead)

  • $ $ : used to define a function

  • $ # : used to define a property

  • @ : used to access names into the Global Root of Registered Names

  • @ $ : this class context (same as "@thisclass")

  • @ @ : this class instance context (same as "@this")

  • @ # : this class instance variables list context (same as "@selfvar")

  • @ ^ : the inherited context (it selects the first one in the case of multiple inheritance)

 

VRCalc++ Objects Names

The Name of a VRCalc++ Named Object can be specified in a script using:

  • an unquoted string identifier starting with a letter (A..Z, a..z) | the underscore char ("_")
    followed by any sequence of letters (A..Z, a..z) | numbers (0..9) | the underscore char ("_")
  • a simple quoted string if the name contains other special chars or spaces (example: "this string contains #@ special chars")
  • an expression enclosed in square brackets [...] returning a string value (the expr is immediately evaluated)

Functions, Properties, Vars, Namespaces, Scripted Class Modules and so on are all named objects.

 

VRCalc++ Variable Declaration

a VRCalc++ Variable is a Named Value Holder.

a VRCalc++ Variable is created when it is first assigned an object value in its default Identificated ValueHolder context and by default it is a strong reference variable type.

for example ...

aVar = 123 // "aVar" is created in the default VHContext

you may specify the IVH Context where the Variable is to be created

for example ...

@selfvar.theVar = 456 // "theVar" is a Scripted Object Instance Variable

@classvar.theVar = 123 // "theVar" is Scripted Class Module Variable

however there are some special cases where you may want to explictly decalre and create a variable for example to specify a particular IVH Context where the variable is to be created or to specify the type of the variable if it is different from the default (which is strong reference type).

to explicitly declare and create a variable use the "var" keyword. its syntax is ...

 

@var <aContainerIndication> <aVarTypeIndication> aVarName = <aValueExpr>

 

the container indication aContainerIndication indicates the context where the variable is defined and inserted. it is optional. by default it is the Main Base VH Context.

syntax:

@into (aContainerExpr)

for short, instead of @into (…), you may specify the following …

@local

indicates the current local vars list context

@self, @this

indicates the current object instance

@class

indicates the current class module instance

@global

indicates the global vars list

@thread

indicates the current thread global vars list

 

the optional type of variable indication can be

@type aVarType

where aVarType can be

weak, weakref, WeakRef, link, bwdlink, objectlink, ObjectLink (these are all synonims)

in this case the creator used is the one that creates variables that do not add references to reference counted objects.

strong, strongref, StrongRef, owner, fwdlink, objectowner, ObjectOwner (these are all synonims)

in this case the creator used is the one that creates variables that always add references to reference counted objects.

simpleowner, SimpleOwner, simpleobjectowner, SimpleObjectOwner

in this case the creator used creates a variable that is the only responsible to free that object value. this is used for simple not reference counted object values.

simplelink, SimpleLink, simpleobjectlink, SimpleObjectLink

in this case the creator used creates a variable that contains only a link reference to a simple object. that object value is not released or freed when the variable of this type is released.

note: aVarType follows the same rules for object names here described that is it can be a simple identifier, a quoted string, a string expression enclosed in [...] braces.

for example you may write: weak, "weak" or ["w" + "eak"] to specify a weak reference variable.

@using (aVarCreatorExpr)

in this case the variable is created using the given var creator object expression.

it is optional. by deafult the global custom variable creator is used to create that variable and so it can be set to any creator value using the provided functions.

 

the variable name aVarName is mandatory and can be

  • an identificator
  • an escaped string
  • an expression enclosed in square braces[expr]” that must evaluate to a Stringable object

note: the "@" symbol in the preceding expressions allow us to have a variable named "into", "type", "using".

the symbol "=" and aValueExpr are optional. if missing the new variable is created with a null value otherwise aValueExpr is evaluated and its value is assigned to the newly created variable.

result value: the result value of the "var" instruction is the one of aValueExpr if present otherwise it is null.

you cannot define a variable twice using "var" in the same context otherwise an exception is raised.

note: you can create Lists of Variables and store Vars there as needed in a hierarchical way (see also the "with" keyword)

 

VRCalc++ Function Definition

To define a Scripted VRCalc++ Function, use the following syntax ...
C++
@function FuncName (FormalArgsList) // the formal args list can be empty
    UsingOption // using option to capture local var values by name
    StaticOrInstanceModifier // optional, default is instance
    RegOption // optional, default is public and registered into this class namespace
    DescrOption // optional, default is an empty description string
    Prologue // optional, can be "do" | ":"
FunctionBody // mandatory, it can be a "{...}" code block | a "(...)" code block

FuncName description:

In addition to the names rules specified above, a function name can be empty or can evaluate to an empty string or it can be (@noname | @pure) which evaluates to an empty string, this can be useful if the function Name does not matter because it is a private function called by address.

FormalArgsList description: A comma (",") separated list of formal args names (it can be empty). Args names follow the same rules for named objects (* see above).

A VRCalc++ Scripted Function can have a variable number of arguments, however, if a function declares some argument names in its formal arguments list then at least the same number of arguments values must be passed to that function when it is called otherwise an exception is raised.
Arguments declared in the formal args list become initialized, with actual arguments values, local variables when the function is called.

UsingOption to capture var values by name. This allow the use of Lambda Functions.

Its syntax is: [using | capture] [ varname1, varname2, varname3, ... ]. (*) See docs and samples for more information.

The capture option is often used with no name, static, private function definitions.
The captured vars values are the ones at the moment when the function is defined.
The captured vars should be local vars.

StaticOrInstanceModifier description: It can be ...

  • static | class : to define a class static function
  • instance | <empty> : to define an instance function

By default, it is an instance function.

RegOption description: It can be ...

  • public | <empty> : to define a public registered function, this is the default
  • private : to define a non registered function (in this case, store its definition value into a var (or some other Value Holder) for later invocation)
  • reg into (some_other_namespace) : to register the function in another namespace (for example "@root")

By default, it is a public function that is it is registered in the Named Values List of the current scripted module.

DescrOption description: It can be a string constant or an expression enclosed in square brackets that returns a string value prefixed by the ("descr" | "description") keyword.

FuncBody description: It is the body of the function which is a list of comma (",") separated expressions.
The function body can be enclosed by curly braces "{...}" or by simple round braces "(...)".
note: no syntax or semantic checking is performed on the body of the function when it is defined, the only requirement is that enclosed blocks are correctly balanced.
The returned value (when the function is later invoked) is the last one evaluated by the function unless the @return <value> system function is called inside the function body.
 

VRCalc++ Property Definition

To define a Scripted VRCalc++ Property, use the following syntax ...
C++
// property definition syntax
@property PropName
    StaticOrInstanceModifier // optional, default is instance
    PrivOrPubMod // optional // optional, default is public
    DescrOpt // optional
access (AccessFunctionRef) |
get (GetterFuncRef) set (SetterFuncRef)

PropName : The property name follows the same rules stated for VRCalc++ objects names and it is mandatory.

StaticOrInstanceModifier : It can be ...

  • class, static: defines a scripted class static property
  • instance: defines an instance property (this is the default)

PrivOrPubMod : It can be ...

  • private, internal: (Warning !!) The property is not registered and it's only accessible by its ref value.
  • public: The property is registered into the class and it is publicly accessible (this is the default).
  • reg into (some_other_namespace) : to register the property in another namespace (for example "@root")

by default it is a public propertythat is it is registered in the Named Values List of the current scripted module.

DescrOpt : The property description string (same as for functions)

access (AccessFuncRef) : Designates a single function to both get & set the value.
The function is called with no args to get the value.
The same function is called with a single argument to set the value.
 
get (GetFuncRef) set (SetFuncRef) : Designates a getter & setter functions pair.
The getter function expects no arguments and it is mandatory (it cannot be null).
The setter function expects a single value argument.
to define a read-only property pass @null in the set option.

Note: A property object is an Identificated ValueHolder so its reference can be taken using the "&" unary operator and its reference can be dereferenced using the "*" unary operator.

Note that variables created inside a module are "private" to that module, however you may access them using the provided functions that get the context of a module or a module instance or you may define a property to access them.

Using VRCalc++ Functions & Properties

Using Functions

Once a function is defined, you may invoke it using the function call operator: "aFunc(...)" which expects a (maybe empty) arguments list of comma (",") separated values.

Another way to invoke a function is using the inherited arguments option, this is especially useful if a function has a variable number of argument values. the inherited args option let you specify the index of the first argument to pass to the function to be called.

Examples ...

@this.MyFunc @^ // call a function using ALL the inherited arguments

@this.MyFunc @^[2] // call a function using the inherited args starting from arg at index (2)

@this.MyFunc @inheritedargs // same as @^

the inherited args option can also be specified at the end of the actual args list ...

@this.MyFunc (value1, value2) @^ // call a function with 2 args + ALL the inherited arguments !!

@this.MyFunc (aValue) @^[1] // call a function with 1 arg + any inherited arguments starting from index (1)

@this.MyFunc() @^ // call a function with no args + ALL the inherited arguments !!

Once a function is defined, you may also get its object address and store it into a variable, property or some other value holder (such as an array item) or you can pass this function reference to another function.

Using Properties

Once a property is defined, you may:

  • get its value
  • set its value (if the setter function is not null)
  • take its reference using the "&" operator since a property is an Identificated Value Holder to later dereference it using the "*" prefix operator

Understanding (Function) Object Instances

A Scripted Function is also an Instantiable Object that is it is bound to an Instance of an Object os some Scripted Class.

When you invoke a Scripted Function, its Bound Scripted Object Instance is used as the Current Object Instance.

Usually, you can access this Bound Object Instance using the "@this" function keyword.

When you call a function using its reference, for example ...
// mylibrary class module
// call a function by reference
@on DoSomething (aFunc)
{
   // ...
   aFunc()
   // ...
},

In this case, the Current Object Instance is used as "@this" by default inside "aFunc".

So, if you have an instance function that have to be passed to "DoSomething (aFunc)" and needs to access "@this" then you have to bind "@this" to the function instance.

For example, if you write ...
// ...
aDoSomethingObject.DoSomething (

   @function () instance private
   {
      // ...
      // call an instance method
      @this.MyMethod(),
      // ...
   }

),
// ...

then you may get an exception (like "name not found") when the function "MyMethod" gets called.

This is because by default "@this" is set to "aDoSomethingObject" object instance and not to the instance of the module you want.

To avoid this drawback, you have to bind the passed function to "@this" using ...
aDoSomethingObject.DoSomething (
   // make a function instance
   @Instantiable.MakeInstanceFor (
      // base def
      @function () instance private
      {
         // ...
         @this.MyMethod()
         // ...
      },
      // bind the function with ...
      @this
   )
),
or you may use a static private lambda function that do not use "@this" by capturing an object value ...
aObject = @this,
aDoSomethingObject.DoSomething (
   @function () using [aObject]
      static private
   {
      // ...
      aObject.MyMethod()
      // ...
   }
),

The above also works

 

About VRCalc++ (Identificated) Value Holders

VRCalc++ Variables are Simple Named Value Holders.
they are usually stored into Variables Lists that are Named Values Lists.
 
VRCalc++ Arrays are Identificated Value Holders.
their items are accessed using objects called identifiers.
in the case of a simple array, identifiers are integer values (indexes) that indicate the array item to be accessed.
 
VRCalc++ Properties are Named Value Holders.
 

About the "defined" and "delete" functions

the "defined" function tests for the existence of an (Identificated) Value Holder in a given Context.
it is expecially useful with Variables. it returns true if the IVH is defined in that context otherwise it returns false.
it is seldom used with arrays.
its syntax is ...
 
@defined <ValueHolderExpr>
 
the "delete" function tries to delete an (Identificated) Value Holder from a given context. it raises an exception if that Value Holder is not defined into such a context.
it is expecially useful with Array Items. it is seldom used with variables since them are automatically released when them go out of scope.
its syntax is ...
 
@delete <ValueHolderExpr>
 
note: the "defined" and "delete" functions do not work on VRCalc++ Properties and should not be used on them.
to test for the existence of a property or to delete a property use the provided "Named Values List" functions !!
(*) see docs & source code for details.
 

"defined" and "delete" functions samples

the following sample code uses "defined" and "delete" on a variable ...

@selfvar.aVar = 123,

@VRStd.Out << "aVar = " << @selfvar.aVar << @VRStd.EndL,

@VRStd.Out << "deleting aVar" << @VRStd.EndL,

@delete @selfvar.aVar,

@if ( @defined @selfvar.aVar ) {
    @VRStd.Out << "aVar is defined"
} else {
    @VRStd.Out << "aVar is not defined"
},
@VRStd.Out << @VRStd.EndL,
""
the above code finally prints "aVar is not defined" on std out
 
the following sample code uses "defined" and "delete" on an Array items ...
$ $ PrintArray(aArray) static
{
    @for (aItem : aArray) {
        @VRStd.Out << aItem << @VRStd.EndL
    }
},

aArray = @LiveObjects.Array.Create(),
aArray[0] = 123,
aArray[1] = 456,
aArray[2] = 789,

@with (aArray) {
    [3] = 753,
    @delete [2]
},

@$PrintArray(aArray),

@if (@defined aArray[3]) {
    @VRStd.Out << "defined"
} else {
    @VRStd.Out << "not defined"
},
@VRStd.Out << @VRStd.EndL,
"done"
the above code finally prints "not defined".
note the use of the "with" keyword to set the Base Main VH Context to "aArray"
 
the following sample code demonstrates the use of Named Values List functions to deal with script defined properties (all in all a Scripted Nodule is also a Named Values List containing Functions & Properties) ...
// define a sample property
@to GetSampleProperty() static { @classvar.theSamplePropertyValue },
@to SetSampleProperty (aValue) static { @classvar.theSamplePropertyValue = aValue },
@property SampleProperty static 
    get (@thisclass.GetSampleProperty) set (@thisclass.SetSampleProperty),

@on Init() static
{
    @classvar.theSamplePropertyValue = 0
},

@thisclass.Init(),

@thisclass.SampleProperty = 123,

@VRStd.Out << "SampleProperty = " << @thisclass.SampleProperty << @VRStd.EndL,

@if ( @defined @thisclass.SampleProperty ) {
    @VRStd.Out << "SampleProperty is defined"
} else {
    @VRStd.Out << "SampleProperty is not defined"
},
@VRStd.Out << @VRStd.EndL,

@if ( @NamedValuesList.IsRegisteredName (@thisclass, "SampleProperty") ) {
    @VRStd.Out << "SampleProperty is defined"
} else {
    @VRStd.Out << "SampleProperty is not defined"
},
@VRStd.Out << @VRStd.EndL,

@VRStd.Out << "deleting SampleProperty" << @VRStd.EndL,

// you cannot use "delete" on a property
@try {
    @delete @thisclass.SampleProperty
} except {
    @VRStd.Out << @LastExceptionMessage << @VRStd.EndL,
    @VRStd.Out << "error!" << @VRStd.EndL,
    @catch
},

    // unregister
    @NamedValuesList.Unregister (
        @thisclass,
        // no auto vh deref
        @System.EngineControl.UncheckedAutoValueHolderDeref { @thisclass.SampleProperty }
    ),

@if ( @NamedValuesList.IsRegisteredName (@thisclass, "SampleProperty") ) {
    @VRStd.Out << "SampleProperty is defined"
} else {
    @VRStd.Out << "SampleProperty is not defined"
},
@VRStd.Out << @VRStd.EndL,

@NamedValuesList.Unregister (@thisclass, @thisclass.GetSampleProperty),
@NamedValuesList.Unregister (@thisclass, @thisclass.SetSampleProperty),

@delete @classvar.theSamplePropertyValue,

"ok"

the above code defines a sample property, uses it, then deletes it using the "@NamedValuesList.Unregister (aNVList, aValueHolder)" function. note that "delete" cannot be used on a property.

note the use of "@NamedValuesList.IsRegisteredName (aNVLIst, aNameString)" function to check property name

now try this sample ...

// Defined

$ $ Defined (aContext, aIdentifier) static
{
    @defined aContext.[aIdentifier]
},

// Delete

$ $ Delete (aContext, aIdentifier) static
{
    @delete aContext.[aIdentifier]
},

// Variable Test

$ $ VariableTest() static
{
    @VRStd.Out << "Variable Test" << @VRStd.EndL,

    @VRStd.Out << "classvar test" << @VRStd.EndL,

    @classvar.theClassVar = 123,

    @if ( @thisclass.Defined (@classvar, "theClassVar") ) then {
        @VRStd.Out << "theClassVar is defined"
    } else {
        @VRStd.Out << "theClassVar is not defined"
    },
    @VRStd.Out << @VRStd.EndL,

    @thisclass.Delete (@classvar, "theClassVar"),

    @if ( @thisclass.Defined (@classvar, "theClassVar") ) then {
        @VRStd.Out << "theClassVar is defined"
    } else {
        @VRStd.Out << "theClassVar is not defined"
    },
    @VRStd.Out << @VRStd.EndL,

    @VRStd.Out << "ok" << @VRStd.EndL,

    @VRStd.Out << "localvar test" << @VRStd.EndL,

    @localenv {

        aVar = 456,

        @localenv {

            @if ( @thisclass.Defined (@local, "aVar") ) then {
                @VRStd.Out << "aVar is defined"
            } else {
                @VRStd.Out << "aVar is not defined"
            },
            @VRStd.Out << @VRStd.EndL,

            @thisclass.Delete (@local, "aVar"),

            @if ( @thisclass.Defined (@local, "aVar") ) then {
                @VRStd.Out << "aVar is defined"
            } else {
                @VRStd.Out << "aVar is not defined"
            },
            @VRStd.Out << @VRStd.EndL,

        }
    },

    @VRStd.Out << "done" << @VRStd.EndL,
},

// Array Test

$ $ ArrayTest() static
{
    @VRStd.Out << "Array Test" << @VRStd.EndL,

    aArray = @LiveObjects.Array.Create(),
    aArray [0] = 123,
    aArray [1] = 456,
    aArray [2] = 789,
    aArray [3] = 159,

    @if ( @thisclass.Defined (aArray, 3) ) then {
        @VRStd.Out << "item [3] is defined"
    } else {
        @VRStd.Out << "item [3] not is defined"
    },
    @VRStd.Out << @VRStd.EndL,

    @thisclass.Delete (aArray, 3),

    @if ( @thisclass.Defined (aArray, 3) ) then {
        @VRStd.Out << "item [3] is defined"
    } else {
        @VRStd.Out << "item [3] not is defined"
    },
    @VRStd.Out << @VRStd.EndL,

    @VRStd.Out << "done" << @VRStd.EndL,
},

// Main

$ $ Main() static
{
    @thisclass.VariableTest(),
    @thisclass.ArrayTest()
},

@thisclass.Main(),
""

it is ok ?!

the "Defined" and "Delete" functions were added to the VRCalc++ System Std Scripted Library.

 

VRCalc++ Scripted Class Registration Example

Suppose you have a scripted class that implements a scripted Random Object stored into a file named "AxRandomClass.vrcm" (*.vrcm is the standard VRCalc++ extension for class modules). The given class module name itself as "Random". Then it creates a name space into the Root named "VRMath" (the name space is created unless it already exists). If that class name is already registered, it stops script execution, otherwise, it continues with functions and properties declaration and in the end, it registers itself into "@root.VRMath".

Here is the code:
C++
// @VRMath.Random

@classmodule.name Random,

// create required namespaces if not already -- ensure that namespace exists
@engine.nvlist.pathfolders.forcecreate {
    @root.VRMath
},

@if (
    @classmodule.IsNameRegisteredInto ( @root.VRMath )
) then {
    // class already registered: break module load
    @exit
},

// continue with functions and properties definitions ...
// ... ... ...
// ... ... ...

// at end ...

// register this class module in its namespace
@classmodule.RegisterInto ( @root.VRMath ),

Defining a Function Example

Let's show some function definitions using the Random example. The following is our Random Object constructor, its function name is "Initialize" and takes no args, it creates an instance var named "theRandSeed" and sets it to "0".

Then it returns @this that is the same object instance (note that the expression @return @this is not needed because a function always returns the last computed value, in this case, @this).
C++
@function Initialize()
{
    @selfvar.theRandSeed = 0,
    @this
},
Note that @selfvar is used to define an instance var. The following is the definition of the RandSeed property.
// random seed property

@to GetRandSeed() { @return @selfvar.theRandSeed },
@to SetRandSeed (aValue) { @selfvar.theRandSeed = aValue },
@property RandSeed get (@thisclass.GetRandSeed) set (@thisclass.SetRandSeed),
The keyword "to" is an alias for "function". The property is defined by the get & set functions pairs. The following is the Randomize function definition:
C++
// randomize

@function Randomize()
{
    aCriticalSection = @system.CriticalSection.RandSeedAccess.Create(),
    @CriticalSection.block (aCriticalSection) {
        // save sys randseed
        aSaveRandSeed = @system.RandomSeed,
        @try {
            @system.Randomize(),
            // get updated randseed
            @selfvar.theRandSeed = @system.RandomSeed
        } finally {
            // restore sys randseed
            @system.RandomSeed = aSaveRandSeed
        }
    }
},
Note that @system.RandomSeed is a static property that returns the Global Delphi RTL Random Seed var value by default functions & properties are instance functions & properties. The following is the Random function definition:
C++
// random

@function Random()
{
    aCriticalSection = @system.CriticalSection.RandSeedAccess.Create(),
    @CriticalSection.block (aCriticalSection) {
        // save sys randseed
        aSaveRandSeed = @system.RandomSeed,
        // set our randseed
        @system.RandomSeed = @selfvar.theRandSeed,
        @try {
            @try {
                // check args
                @switch
                if (@Args.Count() == 0) {
                    @return @Math.Random()
                } else if (@Args.Count() == 1) {
                    @return @Math.Random (@Args.Value(0))
                } else {
                    @return @Math.RandomRange (@Args.Value(0), @Args.Value(1))
                },
            } finally {
                // get updated randseed
                @selfvar.theRandSeed = @system.RandomSeed
            }
        } finally {
            // restore sys randseed
            @system.RandomSeed = aSaveRandSeed
        }
    }
},

Note the use of @Math.Random (and so on) to compute the random value, this function uses the default random algorithm from Delphi RTL to compute the random value after having set our private instance random seed. This function properly saves & restores the system random seed.

Note: The use of @Args.Count & @Args.Value are used to access the Actual Function Arguments which are NOT declared with names in the function head.

 

Using a Lambda Function Samples

These are some examples of Lambda Functions definition and usage ...
C++
@function CallFuncArg (aFunc) static
{
    @VRStd.Out.WriteLine ("calling a function by addr ..."),
    aFunc()
},

@function TestLambdaFunction() static
{
    aMessage = "this is a message text !!",

    aCount = 5,
    aIndex = 0,
    @while (aIndex < aCount) {

        @thisclass.CallFuncArg (
            @function () capture [aMessage, aIndex] static private {
                @VRStd.Out.WriteLine ("message is : " + aMessage + " :: " + aIndex)
            }
        ),

        ++ aIndex
    },

    @VRStd.Out.WriteLine ("done")
},

@proc main() static
{
    @thisclass.TestLambdaFunction()
},

@thisclass.main(),
""
There is another example of lambdas usage: a function that returns a lambda function ...
C++
@function ReturnAdder (aXValue) static
{
    @function (aYValue) capture [aXValue] static private
    {
        aXValue + aYValue
    }
},

aAdderFunc = @$ReturnAdder (5),

aAdderFunc (1)

The above would print the value "6" on the std out channel.

 

Creating and Using a Scripted Object Example

To create a scripted object instance, use the @new keyword:
C++
// create and init a new random object
aRand = @new (@VRMath.Random).Initialize(),
To use our random object, you may call the following:
C++
// make sure we got a true random value
aRand.Randomize(),

// get a random value and store it into aVar
aVar = aRand.Random(),
Here is a more complex example ...
@localenv {
    aCount = 3,
    @while (aCount > 0) {
        @system.out.WriteLine ( aRand.Random() ),
        -- aCount
    }
},

The above code creates a local env in the stack and prints 3 random values on the std out. Note the use of the auto decr (--) unary operator.

Notes about Namespaces

Searches into named values list are optimized binary searches since they are kept sorted. However, if you are using a namespace repeatedly in some loops, you may store it into a var for quick access.

Let's show an example:
C++
// store the Math namespace into a var
aMath = @Math,
    ...
    aMath.Sin (aValue),
    ...

The above example calls the Sin function stored into the @Math namespace.

 

About VRCalc++ Binary Serialization

VRCalc++ Objects can be Binary Serialized into a Binary (File) Stream for later Deserialization.
 
in VRCalc++ Object Binary Serialization is NOT Automatic like in other Languages such as Java or C#.
 
to be Binary Serializable and Deserializable an Object Instance of a Scripted (Registered) Class have to implement the required scripted interface functions.
 

Object Binary Serialization (Write) recipe

to use Binary Serialization in a script you have first to create a Binary Data Writer Object, for exmple ...
// create a binary file stream writer
aBinaryFileWriter = @LiveObjects.BinaryFileStream.Writer.Create(),

@File.Name (aBinaryFileWriter, aDataFileName), // it is a File, set its file name

@File.Open (aBinaryFileWriter), // open it for writing

then you have to create an Object Data Output Stream and associate it with the Binary Writer created before, for example ...

// create an Object Data Output Stream
aObjectDataOutputWriter = @LiveObjects.ObjectDataStream.Output.Create(),

// associate it with a Binary File Writer
@ObjectDataOutputStream.BinaryWriter (aObjectDataOutputWriter, aBinaryFileWriter),

then you can perform an Object Serialization (write it into the file) using ...

@ObjectDataOutputStream.WriteObject (aObjectDataOutputWriter, aObject),

or ...

aObjectDataOutputWriter << aObject

do not forget to Close the Binary Data File Writer after this operation

 

Object Binary Deserialization (Read) recipe

to Deserialize an Object from a binary stream in a script you have first to create a Binary Data Reader Object, for example ...

// create a Binary Data File Reader
aBinaryFileReader = @LiveObjects.BinaryFileStream.Reader.Create(),

@File.Name (aBinaryFileReader, aDataFileName), // set its file name

@File.Open (aBinaryFileReader), // open the file for reading

then you have to create an Object Data Input Stream and associate it with the Binary Reader created before, for example ...

// create an Object Data Input Stream
aObjectDataInputReader = @LiveObjects.ObjectDataStream.Input.Create(),

// associate it with a Binary File Reader
@ObjectDataInputStream.BinaryReader (aObjectDataInputReader, aBinaryFileReader),

then you can perform an Object Deserialization (read it from the file) using ...

aObject = @ObjectDataInputStream.ReadObject (aObjectDataInputReader)

or ...

aObject = @null, // the Value Holder must exist before getting its reference
aObjectDataInputReader >> & aObject, // the ">>" operator expects a Reference to a VH
aObject

do not forget to Close the Binary Data File Reader after this operation

 

notes: as an alternative you may create a Buffered Binary RW File Stream for both Read/Write operations, like this ...

// create a Buffered RW File Stream
aRWBinaryFileStream = @LiveObjects.BufferedBinaryRWFileStream.Create(),

@File.Open (aRWBinaryFileStream), // open mode is R/W by default

@try {

   // perform de/serialization here ...

} finally {

   @File.Close (aRWBinaryFileStream)

}

 

the implicit interface a Scripted Object have to implement to be Binary De/serializable

to be Binary Serializable a Scripted Object have to implement 2 instance methods with the following names and arguments:

// Object Serialization -- write

@on "@IVRBinarySerializable.WriteInto" (aObjectDataOutputStream)
{

   // perform any Object/Data Serialization (write) there ...

}

and

// Object Deserialization -- read

@on "@IVRBinarySerializable.ReadFrom" (aObjectDataInputStream)
{

   // perform any Object/Data Deserialization (read) there ...

}

note that the Read method acts like a constructor for the newly created object

these two methods are called by the Object Data Input/Output Stream to Write/Read an Object Data

it is up to these methods to actually read/write an object's data

 

to write simple data types the write object method has to get the associated Binary Writer

aBinaryWriter = @ObjectDataOutputStream.BinaryWriter (aObjectDataOutputStream),

the Binary Writer implements functions to write simple data types

you can also use the "<<" operator to write simple data types

 

to read simple data types the read object method has to get the associated Binary Reader

aBinaryReader = @ObjectDataInputStream.BinaryReader (aObjectDataInputStream),

the Binary Reader implements functions to read simple data types

you can also use the ">>" operator to read simple data types

 

the functions to read from / write into a Binary (File) Stream can be found in the

  • BinaryStreamWriter namespace
  • BinaryStreamReader namespace

namespaces

for example you may use ...

@BinaryStreamWriter.WriteInteger (aWriter, aValue)

to write an integer into the stream

or you may use ...

aValue = @BinaryStreamReader.ReadInteger (aReader)

to read an integer from the stream

 

Using Shift Operators ("<<", ">>") with Binary Data Streams

since shift left/right ("<<", ">>") operators are supported by a Binary Reader/Writer to read/write simple data types, you may also write ...

aValue = @integer(123) // for example, value set before write

...

aWriter << aValue // write the value based on its actual simple data type (in this case an int32)

or you may write ...

aValue = @integer(0) // create an int32 value (the value must be set before read)

aReader >> aValue // read an int32 from the stream (the value must exist)

this is because simple data types support the required delphi interface to both read/write their binary values from/into a Binary Data Stream

 

Binary Serialization and the Direct Acyclic Graph of Objects

the Object Data Input/Output Stream used to De/serialize Objects keep into account null object values and the fact that an object could be already written/read into/from the binary stream in the same session.

so, on write ...

- if an object is null then a null tag is written into the binary stream

- if an object was written before into the stream then a link tag followed by its code (an integer) is written into the binary stream

and on read ...

- if the null tag indicator is read from the stream then the null object value is returned

- if the link tag indicator is read from the stream then the object with that code (read before from the stream) is returned to the caller

in other words, if the same object instance is written more than once then for the other ones a link to the first is acually written into the stream

- the tag is a byte

- the object code is the index (int32) in the list of the object already written/read

on write: before the write method is called for an object instance, the entire class path of the object is written into the binary stream

on read: the class path is read from the stream and an object instance of that class is created then the read method is called to build this object instance

 

About the VRCalc++ Standard System Scripted Run-Time Library

the VRCalc++ Standard System Scripted Run-Time Library is provided by Vincent Radio {Adrix.NT} and it includes support for ...

  • Char Streams Wrappers & Std I/O Objects

  • Runtime Exception Classes

  • Range Checking

  • Lists, List Iterators & List Utils

    • Array Lists

    • Paged Array Lists

  • IIContainers, IIContainers Iterators, IIContainers Utils

  • Multi Dimensional Arrays

  • Thread Objects

  • Math

    • Int Math

    • Random

  • System Types

    • Basic Data Types

  • and more ...

 

VRCalc++ Language Code Samples

This is a Guess a Number in the Range [0 .. N] Console sample ...
C++
@to PlayGame (aMaxValue) static
{
    aRnd = @new (@VRMath.Random).Initialize(),
    aRnd.Randomize(),

    aNumberToGuess = aRnd.RandomRange (0, aMaxValue + 1),

    aMaxNumOfTries = @VRIntMath.Utils.IntLog (aMaxValue, 2) + 1,

    aTryCount = 0,

    @repeat {

        @if (aTryCount >= aMaxNumOfTries) { @break },

        @VRStd.Out << "you have " << (aMaxNumOfTries - aTryCount)
                   << " tries left" << @std.endl,

        @VRStd.Out << "try #" << aTryCount + 1 << @std.endl,

        @VRStd.Out << "enter a number in the range
                       [" << 0 << " .. " << aMaxValue << "]: ",
        aStr = @VRStd.In.ReadStringLine(),
        aNumber = @String.Parse.AsInt (aStr),

        @switch
        if (aNumber < aNumberToGuess) {

            @VRStd.Out <<
             "retry, the number to guess is > than " << aNumber << @std.endl,
        }
        else
        if (aNumber > aNumberToGuess) {

            @VRStd.Out <<
             "retry, the number to guess is < than " << aNumber << @std.endl,
        }
        else {
            @VRStd.Out << "ok (" << aNumberToGuess
                       << " == " << aNumber << ")" << " you win !!!" << @std.endl,

            @return
        },

        ++ aTryCount
    },

    @VRStd.Out << "sorry, you loose !!" << @std.endl,
},

@proc Main() static
{
    @VRStd.Out << "Guess a Number" << @std.endl,

    @VRStd.Out << "enter max value: ",
    aStr = @VRStd.In.ReadStringLine(),
    aMaxValue = @String.Parse.AsInt (aStr),

    @VRStd.Out << "Guess a Number in the range
                   [" << 0 << " .. " << aMaxValue << "]" << @std.endl,

    @thisclass.PlayGame (aMaxValue)
},

@thisclass.Main(),
""

Note: to run this script properly, you need to load the VRCalc++ VR System Scripted Std Runtime Library.

 

Extending VRCalc++

VRCalc++ can be extended by Scripted Class Modules as well as by Externally Defined Binary Functions and Properties.

There are two (2) types of Delphi Object Pascal binary Functions you may define to extend VRCalc++:

  • Simple (no args) Functions (also called Script Parsers)
  • Functions with a Variable number of Args
binary functions are usually defined into Delphi Packages and registered by the provided plug in modules (implementing the required interface for the application) that are also Delphi Packages (or other kind of delphi DLL) an Application can Load at start up or when they are needed.

Simple Functions

A Simple (no args) Function (that can also parse the Script Text) has the following Delphi Object Pascal Signature ...
type TVRCalcFunction = function : TObject;
To register a Simple Function into the Global Root or in some Namespace starting from the Global Root, you may use ...
procedure RegisterFunction (
    aName : string;
    aFunc : TVRCalcFunction;
    aDescr : string
);
and ...
procedure RegisterFunctionToPath (
    const aNamesPath : array of string;
    aName : string;
    aFunc : TVRCalcFunction;
    aDescr : string
);

which are declared into the "UVRCalcEngineGlobalNamedValuesListRegUtils" Delphi Object Pascal Unit.

the RegisterFunction procedure registers a function at the Root Level

the RegisterFunctionToPath procedure registers a function in some given namespace starting from the Root

Functions with a Variable Number of Arguments

A Function with a Variable Number of Arguments has the following Object Pascal Signature ...
type TVRCalcArgsFunction = function (const aValuesArray : array of TObject) : TObject;
To Register a Function of this type, you may use ...
procedure RegisterArgsFunction (
    aName : string;
    aFunc : TVRCalcArgsFunction;
    aDescr : string
);

procedure RegisterArgsFunctionToPath (
    const aNamesPath : array of string;
    aName : string;
    aFunc : TVRCalcArgsFunction;
    aDescr : string
);
The following also register a function related static property ...
procedure RegisterGetSetArgsFunctionAndStaticPropertyToPath (
    const aNamesPath : array of string;
    aFuncName : string;
    aFunc : TVRCalcArgsFunction;
    aFuncDescr : string;
    aPropName : string;
    aPropDescr : string
);

which are also declared into the "UVRCalcEngineGlobalNamedValuesListRegUtils" Delphi Object Pascal Unit.

the RegisterArgsFunction procedure registers a function at the Root Level

the RegisterArgsFunctionToPath procedure registers a function in some given namespace starting from the Root

the RegisterGetSetArgsFunctionAndStaticPropertyToPath procedure registers a function and a property in some given namespace starting from the Root

Binary Coded Functions & Properties are usually Registered by Plug in Modules that reside in "*.BPL" files.

let's show some examples ...

Simple Functions Registration Sample

function MyScriptParserEvaluator() : TObject;
begin

   // do your custom script parsing here

   result := <<some object value>>

end;


procedure RegisterGlobalFunctions();
begin

   RegisterFunction ('myfunc', MyScriptParserEvaluator, 'a description');


   RegisterFunctionToPath (
      ['namespacelevel0', 'namespacelevel1'], 'myfunc', MyScriptParserEvaluator, 'a description'
   );

end;
 

Functions with Arguments Registration Sample

function MyFunctionEvaluator (const aValuesArray : array of TObject) : TObject;
begin

   // evaluate function here ...

   result := <<some object value>>

end;


function MyPropertyFunctionEvaluator (const aValuesArray : array of TObject) : TObject;
begin

   if (Length (aValuesArray) > 0) then

      // set ...

   else

      // get ...

   result := <<some object value>>

end;


procedure RegisterGlobalFunctions();
begin

   RegisterArgsFunction ('MyFuncName', MyFunctionEvaluator, 'a function description');

   RegisterArgsFunctionToPath (
      ['namespacelevel0', 'namespacelevel1'],
      'MyFuncName', MyFunctionEvaluator, 'a function description'
   );

   // to register both a function and a static property

   RegisterGetSetArgsFunctionAndStaticPropertyToPath (
      ['namespacelevel0', 'namespacelevel1'],
      'MyPropFuncName', MyPropertyFunctionEvaluator, 'a prop func description',
      'MyPropName', 'a prop descr'
   );

end;

 

Using registered binary coded functions in a VRCalc++ Script

in a VRCalc++ Script, you may address the registered binary coded functions this way ...
@namespacelevel0.namespacelevel1.MyFuncName

 

[*] you may find more examples in VRCalc++ Source Code & Docs.

 

Using the VRCalc++ Engine in Delphi Applications

To use VRCalc++ Scripted Class Modules or to Execute a Main Script inside a Package Enabled Delphi Application, you need to:

  • initialize the VRCalc++ Engine Environment
  • load the Required Plug in Modules (included the ones provided by the author)
  • load the Required Class Modules (included the ones provided by the author)
To run a Main Class Module, you also need to create a Main Class Module and its Instance Objects. To use the VRCalc++ Engine in your application, you need at least the following units ...
C++
// Object Pascal // Delphi Code

uses

    UVRObjectInterfaceAccess,

    // VR Std Char Streams support
    UVRStdCharStreams,
    YourApplicStdCharStmSup, // I/O std char streams implementation of your applic
    // may be a null impl if do not need them

    UVRGlobalFileDepsListResolverDefines,    // contains the default file deps list file ext

    UVRSimpleGlobalPlugInPackageListManager, // plug in modules list mgr
    UVRSimpleGlobalPlugInPackageListDepsFileLoadUtils,  // to load plug in modules
    // from a deps list file

    UVRCalcSXScriptDefinedModulesListDepsFileLoadUtils, // script def mods load utils
    // using a deps list file

    // VRCalc++ Engine Stuff
    UVRCalcEngineGlobalOptions,
    UVRCalcEngineGlobalThreadExecEnvInstance,
    UVRCalcEngineGlobalNamedValuesList,

    UVRCalcEngineInit,

    UVRCalcEngineGlobalNamedValuesListRegUtils,

    UVRTextLineBreakString,

    UVRCalcSimpleValueTypesSupport,    // just in the case you need object types conversions

    UVRCalcEngineFunctionType,         // in the case you need to invoke scripted funcs
    // this is used to set the main module source file pathname

    // this is used to set the main module source file pathname
    UVRFilePathnameSupportIntfType,

    // in the case you need a script defined module
    UVRCalcEngineTypes,
    UVRCalcScriptDefinedModule,
    UVRCalcScriptDefinedModuleType,

    // Engine Module Script Executor
    UVRCalcEngineModuleScriptEvaluator, // in the case you need to run a main script mod

    // this is used to find a registered class
    UVRCalcEngineGlobalNamedValuesListRegUtils,

    // the following are used to create a class instance and invoke its methods
    UVRCalcInstantiableClassType,
    UVRCalcInvocableObjectCallbackType,

    // more uses of your application ...
Then, you need a procedure like this to Start Up the Environment ...
C++
// start up env

procedure StartupVRCalcEngineExtendedEnvironment();
begin
    // to do:
    // init console system - associate i/o streams
    SetupStdCharIOStreams();

    // init global options
    UVRCalcEngineGlobalOptions.Initialize();

    // init the global vars list
    UVRCalcEngineGlobalThreadExecEnvInstance.InitializeGlobalVarsList();

    // init Thread Execution Env Instance
    UVRCalcEngineGlobalThreadExecEnvInstance.Initialize();

    // init the Global Named Values List
    UVRCalcEngineGlobalNamedValuesList.Initialize();

    // init the Global PlugIn Package List Manager
    InitializeGlobalPlugInPackageListManager();
    // load plug in modules
    LoadAllPluginModules();

    // writeln ('plug ins loaded');

    try
      // load script defined modules
      LoadAllScriptDefinedModules();
      // done
    except
       on aExcept : Exception do begin
          ShowVRCalcRuntimeException (aExcept, ExceptAddr());
          raise;
       end;
    end;

    // init VRCalc++ environ

    // get script file name

    // load script file

    // create main module // optional
    CreateMainModule();
    SetupMainModule();

    // create main module instance // optional
    CreateMainModuleInstance();
    // set up instance links
    SetupMainModuleInstance();

    SetAutoDisplayInstructionValueMode (true); // from the VRCalcSX console application
end;
Then, you need a procedure to Shut Down the Env like this ...
C++
// shut down env

procedure ShutdownVRCalcEngineExtendedEnvironment();
var
    aEnv : TVRCalcEngineExecutionEnvironment;
begin
    SetAutoDisplayInstructionValueMode (false);

    // get env
    aEnv := GlobalThreadExecEnvInstance();

    // unload all script defined modules
    // nothing to do here - scripted modules are always unloaded
    // because they are reference counted

    // finally destroy the main execution VR-Calc engine env (if any) ...
    SetMainModuleInstance (nil);

    // finally destroy the main execution VRCalc++ engine global env (if any) ...
    SetMainModule (nil);

    aEnv.RunningModule := nil;
    aEnv.ErrorModule := nil;

    // terminate Global Named Values List
    UVRCalcEngineGlobalNamedValuesList.Terminate();

    // terminate Thread Execution Env Instance
    UVRCalcEngineGlobalThreadExecEnvInstance.Terminate();

    // terminate global vars list
    UVRCalcEngineGlobalThreadExecEnvInstance.TerminateGlobalVarsList();

    // terminate global options
    UVRCalcEngineGlobalOptions.Terminate();

    // unload all plug in modules
    UnloadAllGlobalListSimplePlugInPackages();

    // terminate the Global PlugIn Package List Manager
    TerminateGlobalPlugInPackageListManager();

    // terminate console system
    ShutdownStdCharStreams();

    // done!
end;

Call the start up engine ext env in your start up applic code, call the shut down engine ext env in your shut down applic code. Now let's see in more detail ...

Set Up and Terminate Std Char Streams

The following code is taken by the VRCalcSX (VRCalc++ Script Executor) Console application. For your Applic, replace the actual type of the Reader and Writer as needed:
C++
// std i/o streams
// setup

procedure SetupStdCharIOStreams();
var
    aStdInReader : TVRCalcSXStdInputCharStreamReader;
    aStdOutWriter : TVRCalcSXStdOutputCharStreamWriter;
    aStdErrOutWriter : TVRCalcSXStdOutputCharStreamWriter;
begin

    aStdInReader := TVRCalcSXStdInputCharStreamReader.Create();
    AddReferenceTo (aStdInReader);
    try
        SetStdInReader (aStdInReader);
    finally
        ReleaseReferenceFrom (aStdInReader);
    end;

    aStdOutWriter := TVRCalcSXStdOutputCharStreamWriter.Create();
    AddReferenceTo (aStdOutWriter);
    try
        SetStdOutWriter (aStdOutWriter);
    finally
        ReleaseReferenceFrom (aStdOutWriter);
    end;

    aStdErrOutWriter := TVRCalcSXStdOutputCharStreamWriter.Create();
    AddReferenceTo (aStdErrOutWriter);
    try
        SetStdErrOutWriter (aStdErrOutWriter);
    finally
        ReleaseReferenceFrom (aStdErrOutWriter);
    end;

end;

// shutdown

procedure ShutdownStdCharStreams();
begin
    SetStdInReader (nil);
    SetStdOutWriter (nil);
    SetStdErrOutWriter (nil);
end;

Loading Plug in Modules

To load the required Plug in Modules, use something like this ...
C++
// to load all plugin modules

procedure LoadAllPluginModules();
var
    aRootPlugInModulesDepsListFileName : string;
begin
    // get root file name
    aRootPlugInModulesDepsListFileName := GetVRCalcSXPluginsListFilePathName();

    // load all plug in packages
    LoadAllSimplePlugInPackageModulesUsingDepsListFile (
        aRootPlugInModulesDepsListFileName
    );
end;

The above code is taken from the VRCalcSX Console Applic.

VRCalc++ Plug in Modules are responsible to Register the Exported External.

Global Functions & Properties in some Namespace starting from the Global Root which is accessible using the VRCalc++ At Operator (@) so they can be accessed by Script Code.

Loading Script Defined Modules

To load the required Script Defined Modules, use something like this ...
C++
// to load all script defined modules

procedure LoadAllScriptDefinedModules();
var
    aRootScriptDefinedModulesDepsListFileName : string;
begin
    // get root file name
    aRootScriptDefinedModulesDepsListFileName :=
                    GetVRCalcSXScriptDefinedModulesListFilePathName();

    // load all
    LoadAllRequiredScriptedModulesUsingDepsListFile (
      aRootScriptDefinedModulesDepsListFileName
    );
end;

The above code is taken from the VRCalcSX Console Applic.

Notes about Files Dependency List Files

Files Dependencies are stored in Files Deps List Files that are parsed so that a file is included only once. The Default Deps List File Ext is "*.vrdeplst" and are basically text files. They can contain end of line comments in the form ...
C++
' this is a comment

// this is another comment
These files List the Dependencies of a File to Other Files. The Files Deps List File Parser resolve duplicates in File Names so a File is Parsed only once. They can contain the following deps instructions ...
#use <filename> // parse a single file
#usefolder <folderpathname\*.ext> // use a folder contents
#usefoldertree <folderpathname\*.ext> // parse files of type *.ext in this and all subfolders
#include <depslistfilename> // include another deps list file

"filename" and "folderpathname" are relative to the folder where the deps list file is located. And can contain OS Env Vars Names in the form "%MyEnvVarName%". The following functions use a Files Deps List File to parse Plug in as well as Scripted Modules:

  • LoadAllSimplePlugInPackageModulesUsingDepsListFile (aRootDepsListFileName)

    // to Load Plug in BPL Modules

  • LoadAllRequiredScriptedModulesUsingDepsListFile (aRootDepsListFileName)

    // to Load Required Script Defined Modules

(*) see the Source Code & Docs for details.

Using Files Deps List Files in a Scripted Main Module

VRCalc++ Scripted Main Modules have usually the "*.vrc" File Ext. If a Main Script needs to use others Scripted Class Modules (usually stored in files with the (*.vrcm) File Ext), then the Main Script can have an Associated Files Dependencies List File.

The Associated Files Deps List File usually has the (*.vrdeplst) File Ext appended to the Main Script File Name (including its file ext), so if the Main Script File Name is "My Applic.vrc" then its Associated Files Deps List File Name should be "My Applic.vrc.vrdeplst". Then in your Main Script Source (before using your classes), you have to invoke ...
C++
// Load All Required Scripted Classes
@System.LoadAllRequiredModules();

This call uses by default your "My Applic.vrc.vrdeplst" File to Load the required Scripted Classes you have specified inside it using (for example).

Note: This function can have other parameters to better specify what to load, but the defaults are sufficient for this example.
#use <../User Lib/My Class.vrcm> // use a single file
#use <../User Lib/My Other Class.vrcm> // use a single file

#usefolder <../folderpathname/*.vrcm> // use a folder contents

#usefoldertree <../otherfolder/*.vrcm> ' use a folder and any subfolders contents

In turn, if a "*.vrcm" file depends upon other files, it can have a "*.vrcm.vrdeplst" file associated. For example, "../User Lib/My Class.vrcm" can have a "My Class.vrcm.vrdeplst" Associated File that Lists its Dependencies to Other Files. The Files Deps List File Parser follows the Links Automatically in a recursive way and ensures that a File is Parsed (in this case Loaded) Only Once.

(*) see VRCalc++ Code Examples and source code for more information.

Creating and Running a Main Scripted Module

To run a Main Scripted Module, you need something like this in your application ...
Pascal
 // our VRCalc++ Engine Main Module
 var theMainModule : TObject; //TVRCalcScriptDefinedModule;
 var theMainModuleInstance : TObject; //TVRCalcScriptDefinedModuleInstance;
 // the global env
 // the main module
 function GetMainModule() : TObject;
 begin
     result := AddReferenceTo ( theMainModule );
 end;

 procedure SetMainModule (aModule : TObject);
 var
     aOldModule : TObject;
 begin
     aOldModule := AddReferenceTo (theMainModule);
     if (aOldModule <> aModule) then begin
         AddReferenceTo (aModule);
         theMainModule := aModule;
         ReleaseReferenceFrom (aOldModule);
     end;
     ReleaseReferenceFrom (aOldModule);
 end;

 procedure CreateMainModule();
 var
     aModule : TObject; //TVRCalcScriptDefinedModule;
 begin
     aModule := AddReferenceTo ( TVRCalcScriptDefinedModule.Create() );
         SetMainModule (aModule);
     ReleaseReferenceFrom (aModule);
end;

 procedure SetupMainModule();
 begin
 // have nothing to do
end;

 // the main module instance

 function GetMainModuleInstance() : TObject;
 begin
     result := AddReferenceTo ( theMainModuleInstance );
end;

 procedure SetMainModuleInstance (aInstance : TObject);
 var
     aOldInstance : TObject; // TVRCalcScriptDefinedModuleInstance;
 begin
     aOldInstance := AddReferenceTo (theMainModuleInstance);
     if (aOldInstance <> aInstance) then begin
         AddReferenceTo (aInstance);
         theMainModuleInstance := aInstance;
         ReleaseReferenceFrom (aOldInstance);
     end;
     ReleaseReferenceFrom (aOldInstance);
end;

 procedure CreateMainModuleInstance();
 var
     aInstance : TObject;
 begin
     aInstance := AddReferenceTo ( TVRCalcScriptDefinedModuleInstance.Create() );
         SetMainModuleInstance (aInstance);
     ReleaseReferenceFrom (aInstance);
end;

 // set up links

 procedure SetupMainModuleInstance();
 var
     aInstance : TObject; //TVRCalcScriptDefinedModuleInstance;
     aModule : TObject; //TVRCalcScriptDefinedModule;
 begin
     aInstance := GetMainModuleInstance();
     aModule := GetMainModule();
     try
         SetModuleInstanceClassModule (aInstance, aModule);
         //aInstance.ClassModule := aModule;
     finally
         ReleaseReferenceFrom (aModule);
         ReleaseReferenceFrom (aInstance);
     end;
 end;
The above code is from the VRCalcSX Console Application. To run our Main Module with the given instance, you need a proc like this ...
Pascal
// execute main script module instance

procedure ExecuteMainScript();
var
    aFileName : string;
    //aEnv : TVRCalcEngineExecutionEnvironment;
    aInstance : TObject; //TVRCalcScriptDefinedModuleInstance;
    aScriptText : string;
    aValue : TObject;
    aSavedCurrDir : string;
begin
    aInstance := GetMainModuleInstance();
    try
        aFileName := GetMainScriptedModuleFileName();

        // get script text from file
        //aScriptText := UVRTextFileSupport.GetTextFileNameContentAsString (aFileName);
        if (Length (aFileName) > 0) then
            // load script text file
            aScriptText := UVRTextFileSupportExt.LoadTextFileName (aFileName)
        else
            // read std input
            aScriptText := UVRTextFileSupport.GetTextFileNameContentAsString (aFileName);
        //end if

        // set module script text
        SetModuleInstanceScriptText (aInstance, aScriptText);

        // [*] also set the source file pathname for the module
        SetMainModuleSourceFilePathname();

        //writeln ('about to run script');

        // save curr dir
        aSavedCurrDir := GetCurrentDir();
        // set curr dir to the one of the main module
        SetMainModuleCurrentDirectory();
        try
           try
              // run : try any evaluation ...
              aValue := EvaluateModuleInstance (aInstance);
              // ok!

              // the result value is always discarded
              ReleaseReferenceFrom (aValue);
              // ok!

           except
              on aExcept : Exception do begin
                 // show exception
                 ShowVRCalcRuntimeException (aExcept, ExceptAddr());
                 raise;
              end
           end
        finally
            // restore old curr dir
            SetCurrentDir (aSavedCurrDir);
      end;

    finally
        // release the instance after use
        ReleaseReferenceFrom (aInstance);
  end;
end;

The above code is from the VRCalcSX Console Application.

Using Scripted Class Objects in a Delphi Application

In this section, we use the "RandomObject" seen above as an example in the interface of your class (maybe a Form) declare the following:
C++
// class TVRCalcClientTestAppMainForm

        // the random object

        private theRandomObject : TObject;

        public function GetRandomObject() : TObject;
        public procedure SetRandomObject (aObject : TObject);

        public property RandomObject : TObject
            read GetRandomObject
            write SetRandomObject;

        // to create the Random Object

        public procedure CreateRandomObject();
In the implementation section, write the following:
C++
//========================================================================================

    // to Access our RandomObject

    function TVRCalcClientTestAppMainForm.GetRandomObject() : TObject;
    begin
        result := AddReferenceTo (self.theRandomObject)
    end;

    procedure TVRCalcClientTestAppMainForm.SetRandomObject (aObject : TObject);
    var
        aOldObject : TObject;
    begin
        aOldObject := self.RandomObject;
        if (aObject <> aOldObject) then begin
            AddReferenceTo (aObject);
            self.theRandomObject := aObject;
            ReleaseReferenceFrom (aOldObject)
        end;
        ReleaseReferenceFrom (aOldObject)
    end;

//========================================================================================

    // to create the Random Object

    procedure TVRCalcClientTestAppMainForm.CreateRandomObject();
    var
        aClass : TObject;
        aClassInstance : TObject;
      aValue : TObject;
    begin
        // get Random Object Class
        aClass := UVRCalcEngineGlobalNamedValuesListRegUtils.GetRegisteredClass 
                           (['VRMath', 'Random']);
        try
            aClassInstance := CreateClassInstanceOf (aClass);
            try

                aValue := InvokeObjectDefinedNamedFunctionMethodOf 
                                    (aClassInstance, 'Initialize', []);
                // discard result
                ReleaseReferenceFrom (aValue);
                // set new random object
                self.RandomObject := aClassInstance;
                // done

            finally
                ReleaseReferenceFrom (aClassInstance)
            end;

        finally
            ReleaseReferenceFrom (aClass)
        end;
    end;

//========================================================================================
Also, write the following declarations in the interface part of your Delphi class:
Delphi
// class TVRCalcClientTestAppMainForm
        // to test the Random Object
        public procedure RandomizeObject();
        public procedure TestRandomObject();
        public procedure TestRandomRange();
And in the implementation section, write the following:
Delphi
//========================================================================================

   // randomize object

    procedure TVRCalcClientTestAppMainForm.RandomizeObject();
    var
        aRandomObject : TObject;
        aValue : TObject;
    begin
        aRandomObject := self.RandomObject;
        try
            aValue := InvokeObjectDefinedNamedFunctionMethodOf 
                      (aRandomObject, 'Randomize', []);
            ReleaseReferenceFrom (aValue);
        finally
            ReleaseReferenceFrom (aRandomObject)
        end;
    end;

//========================================================================================

    // to test the Random Object

    procedure TVRCalcClientTestAppMainForm.TestRandomObject();
    var
        aRandomObject : TObject;
        aValue : TObject;
        aFloatValue : extended;
        aString : string;
    begin
        aRandomObject := self.RandomObject;
        try
            aValue := InvokeObjectDefinedNamedFunctionMethodOf (aRandomObject, 'Random', []);
            try
                aFloatValue := FloatValueOf (aValue);
                aString := FloatToStr (aFloatValue);
                // show result
                self.theResultStaticText.Caption := aString;
                // done
            finally
                ReleaseReferenceFrom (aValue)
            end;
        finally
            ReleaseReferenceFrom (aRandomObject)
        end;
    end;

    // Random Range (a, b)

    procedure TVRCalcClientTestAppMainForm.TestRandomRange();
    var
        aArg1Value : TObject;
        aArg2Value : TObject;
        aRandomObject : TObject;
        aValueResult : TObject;
        aResult : integer;
        aString : string;
    begin
        aArg1Value := CreateIntegerNumberValue (self.theRandomRangeMinSpinEdit.Value);
        try
            aArg2Value := CreateIntegerNumberValue (self.theRandomRangeMaxSpinEdit.Value);
            try
                // get Random Object
                aRandomObject := self.RandomObject;
                try
                    // call function
                    aValueResult := InvokeObjectDefinedNamedFunctionMethodOf 
                                 (aRandomObject, 'Random', [aArg1Value, aArg2Value]);
                    try
                        // get result
                        aResult := UncheckedIntegerValueOf (aValueResult);
                        aString := IntToStr (aResult);
                        // show result
                        self.theRndRngResultStaticText.Caption := aString;
                        // done
                    finally
                        ReleaseReferenceFrom (aValueResult)
                    end;
                finally
                    ReleaseReferenceFrom (aRandomObject)
                end;
            finally
                ReleaseReferenceFrom (aArg2Value)
            end;
        finally
            ReleaseReferenceFrom (aArg1Value)
        end;
    end;

//========================================================================================
Then, in your FormCreate method, write the following:
C++
...

// create random object
self.CreateRandomObject();

Then, associate Click Event Handlers to the procedures shown above.

VRCalc++ OOSL Modifications History

[*] The first (non object oriented) prototype version (derived from tinycalc and xcalc) was started on March 2006 featuring many characters of the final version including proc flow control structs although it was non object oriented and only capable of handling floating point numeric data, also the console was initially integrated with the runtime engine and to add a feature, you had to recompile all.

[*] The final object oriented version 2.0 was started on March 2009 and completed at end of 2009 (including procedural flow control script parsers) after having rewritten most of the Delphi code of the 2006 version to adapt it to Reference Counted Interfaced Objects (* see Inside VRCalc++ docs, COM Interfaces) and after having isolated the VRCalc++ Engine Code in their proper packages.

Also note that, because the use of object interfaces on which VRCalc++ symbolic operators are based on, support for operators overloading is also available and it will be extended in future releases.

[*] Partial support for many RTL objects and functions [Math, DateTime, and so on …] was added to final version in 2010 as long as support to access many Engine features [VRCalcEngineExtension]

[*] The Inside VRCalc++ documentation was started on Feb 2015 as a set of RTF docs

[*] Many built in Engine Classes were improved on March 2015 including the EngineThread Class that now supports Runnable Objects as well as Runnable Scripted Module Instances and some generic interfaces were added to support Scripted Modules

[*] Support for runtime Required Scripted Modules Loading Optimization was also added on March 2015 using module dependencies list files with a generic load modules engine and a couple of applets to smart edit the deps list files thus preventing loading required modules 2 or more times in a row and also resolving any circular refs among modules

[*] Although the VRCalc++ Engine was designed to work mainly with Reference Counted Interfaced Objects (* see COM Interfaces, Inside VRCalc++ docs), support for using simple not ref counted objects (such as RTL TList objects) is also provided from April 2015 and support for optional module termination callback scripted methods was added, it is needed in case you work with these objects to perform the required clean up (* see Inside VRCalc++ docs for details). Pluggable functions to work with some RTL System Classes (TList, TStrings, and so on …) were also added.

[*] As a result of the above, from April 2015, the list of VRCalc++ Engine Built in functions and script parsers was enlarged and improved by including many of the most important proc flow control structs and functions to access the runtime environment (before from 2009, they were defined only into an external package to plug in separately, now they are part of the Base Engine Module Package). support for global vars was also added along with some improvements of the VRCalc++ Engine Thread Execution Global Environment

[*] In April 2015, a pair of expressions operators and engine properties were added to optimize expressions evaluations in both time & space. One of the most important optimizations is the one that allow us to eval an expression using flat operators with no implicit precedence among them, it is up to the programmer to set operators precedence in expressions by using the required blocks enclosing here the expressions with higher eval precedence. This option allows us to save evaluation time & stack space

[*] In Aug 2015, format of Deps List Files was improved to support relative pathnames, include files, env vars expansion, directories and sub-folders recursion and user comments by modifying the relative syntax. Also, their use has been extended to pluggable (BPL) modules used to extend the language functions.

[*] In Nov 2015, the basic math operators functions were upgraded to support integer math operations side by side to floating point math with automatic conversions, that is all basic math is performed at highest precision for both unsigned and signed integral numbers and extended floating point numbers. format of numeric constants was extended to support unsigned and signed integrals and floating point numeric constants using type suffixes while remaining backward compatible.

[*] Also the VRCalc++ Console (* see below) was improved from Feb 2015 and a new console only executor of VRCalc++ scripts was created on Feb 2015 [VRCalcSX]

[*] In Sep 2016, a simple Test Applic Delphi Project embedding the VRCalc++ Engine was created to demonstrate Scripted Object of a Class Module Creation and Method Invocation (it uses a scripted RandomObject to generate Random Values).

[*] In Dec 2016, some non-interfaced objects with copy semantics (not reference counted) were created and added to the main library of live objects along with support functions to store simple data types without computing interfaces to save memory space (by default, a simple data type (string, integer, float, …) is created for computing purposes so it implements all the required interfaces to make computations and its size is very large). You cannot perform computations on simple storage objects of this type, you have to convert them to computing object implementing required interfaces to perform computations. their purpose is only to save space for storage.

[*] In Feb 2017, functions and properties declarations were improved by introducing new keywords (“public”, “private”, “pure”). VRCalc++ Console now shows error line & col.

[*] In Dec 2018, some useful scripted classes (such as: ArrayList, ListIterator, SubList, RandomObject, Observable Support …) along with useful scripted List utils (such as: QuickSort, BinarySearch …) and some useful scripted objects (such as: VRStd.In, VRStd.Out …) were added to the Auto-Load Scripted Modules List.

Other VRCalc++ Demos based on these classes and procs were added.

The new keyword “@abstract” was added to mark a scripted function as Abstract, put it inside an abstract method function to override, then it raises an exception if that method function gets called.

[*] In Jan 2019, support for Paged Arrays (based on VRPagedLists & Iterators) was added to VRCalc++ along with Scripted Classes for Lists based upon Paged Arrays.

[*] In Feb 2019, some useful Object Array Functions were added to the Extension Package including Reverse, BinarySearch, QuickSort, RelocateItems, RotateItems, and so on.

[*] In Nov 2019, scripted functions definitions were updated to support Lambda Functions that can capture the environment variables values which name you may specify in a using clause (* see docs for more details); this is often used with static private functions which usually have no name.
The keywords (strong, strongref, weak, weakref) were added in variables definition.

VRCalc++ Links

You may find VRCalc++ OOSL Source Code and Delphi Build Projects along with some other Delphi Projects & Source Code by Vincent Radio {Adrix.NT} on SourceForge.net at the following link:

you can also find ...

That's all, folks !!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Vincent Radio
Software Developer DARKSTAR humanware
Italy Italy
No Biography provided

Comments and Discussions

 
Questiongood job Pin
SaiGonApp4-Jun-21 5:13
professionalSaiGonApp4-Jun-21 5:13 
AnswerRe: good job Pin
Vincent Radio4-Jun-21 6:03
professionalVincent Radio4-Jun-21 6:03 
QuestionMessage Closed Pin
15-Dec-20 21:11
Memberjoandavis15-Dec-20 21:11 
GeneralMy vote of 5 Pin
AndreMurtaX17-Nov-20 15:20
MemberAndreMurtaX17-Nov-20 15:20 
GeneralRe: My vote of 5 Pin
Vincent Radio21-Nov-20 1:51
professionalVincent Radio21-Nov-20 1:51 
QuestionMessage Closed Pin
9-Sep-20 1:58
Memberlimeli8 digital9-Sep-20 1:58 
SuggestionMessage Removed Pin
9-Sep-20 1:56
Memberlimeli8 digital9-Sep-20 1:56 
GeneralGreat, thanks Pin
William.Front-end3-Sep-20 0:53
MemberWilliam.Front-end3-Sep-20 0:53 
GeneralRe: Great, thanks Pin
Vincent Radio3-Sep-20 5:50
professionalVincent Radio3-Sep-20 5:50 
QuestionGreat Stuff, but why not make JavaScript Engine Link and use that? Pin
Kirk 103898212-Sep-20 5:39
MemberKirk 103898212-Sep-20 5:39 
AnswerRe: Great Stuff, but why not make JavaScript Engine Link and use that? Pin
Vincent Radio2-Sep-20 20:13
professionalVincent Radio2-Sep-20 20:13 
Questionsame kind in .net Pin
Member 127077526-Jul-20 11:29
MemberMember 127077526-Jul-20 11:29 
AnswerRe: same kind in .net Pin
Vincent Radio2-Sep-20 20:14
professionalVincent Radio2-Sep-20 20:14 
GeneralMessage Removed Pin
18-Dec-18 6:12
professionalVincent Radio18-Dec-18 6:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.