Click here to Skip to main content
13,898,496 members
Click here to Skip to main content
Add your own
alternative version

Stats

11.4K views
2 bookmarked
Posted 18 Dec 2018
Licenced CPOL

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

, 20 Feb 2019
Rate this:
Please Sign up or sign in to vote.
VRCalc++ Engine is embeddable in any Delphi Application using Dynamic Packages

Introduction

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

and so on ...

VRCalc++ includes support for the following:

  • access the RTL package data, funcs and procs
  • run-time exceptions handling
  • multi threaded functions and objects
  • Windows DLL access (Win32 version)

and more ...

VRCalc++ 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 independant script Engine code can also be recompiled and run under Linux and MacOS.

VRCalc++ Console is a 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.

VRCalcSX is a Console Program that only executes VRCalc++ scripts, both of them are extensible by pluggable modules.

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

VRCalc++ Links

You can find:

Background

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) in developing VRCalc++ OOSL.

Language Overview

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

Expressions are separated by the comma symbol (,) and can be enclosed in normal (...) or curly {...} blocks. There are 3 types of code blocks:

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

Integer number literal constants can be specified using a sequence of the digits (0 .. 9). You may also specify the type of the integral (its size in bits) along its value (default is the biggest int type). You may also specify if it is a signed or unsigned integer. Support for hex 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. After, you may also specify the exponent using (E | e) an optional sign (+|-) and an int number. After, 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 like this: "hello " "adrix" " you are an awesome programmer" the result is always concatenated.

char literal constants can be spefified using single quotes to surround them like this '#'. Both string & char constants include support for escape sequences. The escape char is (\). For example ...

  • '\'' // the ' char
  • '\t' // the TAB char
  • '\r' // CR
  • '\n' // LF
  • '\x' // prefix to specify hex digits
  • '\d' // prefix to specify decimal digits

Note: You may also concatenate strings and other values using the (+) operator 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 computation 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 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 main contexts for a variable are:

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

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

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

VRCalc++ Scripted Classes are stored in Text Files. A Scripted Class should have a name and should register itself into the Global Named Values List (starting from the Root).

VRCalc++ Scripted Classes usually defines:

  • 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 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. No lambda functions are present in this version of the engine.

Use small scripted classes instances with instance data instead implementing the 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 module defines its interface implicitly by defining:

  • functions (and arguments)
  • properties

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

VRCalc++ Scripts can contain comments:

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

All comments are skipped by the VRCalc++ interpreter.

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

A Scripted Module is also a Named Object. A Name Space is a List that is also a Named Object.

VRCalc++ Plug in Modules have to register functions into the Global Named Values List. Into the Root or in some Name Space starting from the Root. In VRCalc++, the dot operator (.) is used to access:

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

In VRCalc++, the at operator (@) is used to access the Global Root of registered names (see above). The VRCalc++ Engine defines some binary and unary operators. The most important of these is the Assignment Operator (=) other binary operators are ...

is: compares a value against a given class:

  • /: divide
  • *: mult
  • \: int div
  • %: modulus
  • +: addition
  • -: subtraction
  • &: bit and
  • |: bit or
  • ^: bit xor
  • <<, >>: shift left, shift right (overridden and also used for insertions & extractions)
  • &&: bool and
  • ||: bool or

Some unary operators are:

  • -, +: negation, same value
  • !: bool negation
  • &: get a ref to a value holder
  • *: get a value holder from a ref
  • ++, --: auto incr, auto decr prefix operators (post fix are NOT supported)

Note: Some binary operators can also be overridden by scripted objects.

Some op-assignment operators are supported such as:

  • +=, -=: add/sub & assign
  • *=, /=, \=, %=: mult/div & assign

and so on ...

The VRCalc++ Engine defines and includes some basic no args system functions (called Script Parsers). Some of them are ...

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
  • 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 a finally block)
  • raise: to raise an exception
  • catch: to block the propagation of an handled exception

Some Useful Constants

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

These built in system function can be addressed by the ($) unary prefix. However, it is best to use the exported ones in the Global Named Values List using the (@) prefix (*) see examples below.

VRCalc++ Objects Names

The name of a VRCalc++ named object can be specified in a script using:

  • an unquoted string starting with a letter (A..Z, a..z) | the underscore char (_)

    followed by any sequence of letters, numbers (0..9) | the underscore char

  • a simple quoted string if the name contains other special chars or spaces (example: "this string contains @")
  • an expression enclosed in square brackets [...] returning a string value (the expr is immediately evaluated)

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

VRCalc++ Function Definition

To define a Scripted VRCalc++ Function, use the following syntax ...

@function FuncName (FormalArgsList) // the formal args list can be empty

    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, can be a "{...}" code block | a "(...) code block

FuncName description:

In addition to the names rules specified above, a function name 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).

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 def value into a var for later invocation)

By default, it is a public function.

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 braces (...). The returned value 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 ...

// sample property definition

@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

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

DescrOpt: the property descr string (same as for funcs)

access (AccessFuncRef): designates a single function to both get & set the value. The function is called with no args to get the value. The function is called with a single arg to set the value.

get (GetFuncRef) set (SetFuncRef): designates a getter & setter functions pair. The getter function takes no args. The setter function takes a value arg.

Note: A property object is a ValueHolder so its ref can be taken using the (&) unary operator and its ref can be dereferenced using the (*) unary operator.

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 "RandomObject". Then it creates a name space into the Root named "AxMath" (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.AxMath".

Here is the code:

@classmodule.name RandomObject,

// create required namespaces if not already
@engine.nvlist.pathfolders.forcecreate {
    @root.AxMath
},

@if (
    @classmodule.IsNameRegisteredInto ( @root.AxMath )
) 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.AxMath ),

Defining a Function Example

Let's show some function definitions using the RandomObject example. The following is our RandomObject 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 word @return @this is not needed because a function always returns the last computed value, in this case, @this).

@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:

// 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 RTL Random Seed var value by default functions & properties are instance functions & properties. The following is the Random function definition:

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

Creating and Using a Scripted Object Example

To create a scripted object instance, use the @new keyword:

// create and init a new random object
aRand = @new (@AxMath.RandomObject).Initialize(),

To use our random object, you may call the following:

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

// store the Math namespace into a var
aMath = @Math,
    ...
    aMath.Sin (aValue),
    ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // 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 
                           (['AxMath', 'RandomObject']);
        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:

// class TVRCalcClientTestAppMainForm

        // to test the Random Object

        public procedure RandomizeObject();

        public procedure TestRandomObject();

        public procedure TestRandomRange();

And in the implementation section, write the following:

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

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

...

// 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 capable only to handle 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

[*] on 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

[*] on 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.

[*] on 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]

[*] on 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).

[*] on 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.

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

[*] on 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.

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

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

VRCalc++ Links

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

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

No Biography provided

You may also be interested in...

Pro

Comments and Discussions

 
GeneralMessage Closed Pin
26-Dec-18 1:09
memberMember 1410061726-Dec-18 1:09 
GeneralMy vote of 5 Pin
Vincent Radio18-Dec-18 6:12
memberVincent 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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190306.1 | Last Updated 21 Feb 2019
Article Copyright 2018 by Vincent Radio
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid