GorillaScript

GorillaScript is a precompiled scripting language, which parses delphi pascal code, compiles to a bytecode format and executes it afterwards. It is possible to use only the bytecode files in release state for faster execution.

The scripting was not developed for ultra-fast execution, the more its intention is to use it as GUI interface on top of the firemonkey framework. But of course we will optimize speed and performance in future. You can write script methods for event types like the TNotifyEvent of a TTimer.OnTimer (extendable).

GorillaScript syntax is inspired by new delphi syntax and the parser is based on the open-source Castalia Parser. Not all syntax elements are supported yet, but most of them like, classes, enumerations, sets, fields, functions, procedures, properties and much more.

To combine your Gorilla3D application with the scripting, the system allows to register native types like classes, records, interfaces, enumerations and sets.

It is possible to use GorillaScript with all Gorilla3D available components and classes.

Besides the regular usage for FMX and Gorilla3D components, GorillaScript is intended to be used for writing shaders (not implemented yet!)

Features

  • Registering of native classes, interfaces, records, enumeration types and set types
  • Usage of native data types: Int8, UInt8, ShortInt, Byte, Int16, UInt16, SmallInt, Word, Int32, UInt32, Integer, Cardinal, LongInt, LongWord, Int64, UInt64, Single, Double, String, Pointer, Boolean, TValue, TMethod, Nil
  • Usage of arrays with variant content
  • Declaring script classes, Interfaces, enumeration types, set types
  • Declaring local script variables
  • Declaring fields, properties and methods (function / procedure / constructor / destructor / class function / class procedure) in script classes
  • Class properties with accessors to fields and methods
  • Declaring global script functions / procedures
  • Mathmatical operations: add, subtract, multiply, divide, modulo
  • Boolean operations: equal, not-equal, less, less-equal, greater, greater-equal, not, and, or, xor
  • Bitwise operations: and, or, xor, shr, shl
  • Compiler defines (automatically all default delphi-defines will be available, f.e. “MSWINDOWS”, “ANDROID”, “CPUX86”, “CPUX64”, …)
  • For-Loops, While-Do-Loops, Repeat-Loops
  • If-Then-Else-Statements
  • Case-Statements
  • In-Operator for checking enumerator in sets or strings in other strings
  • Addition and subtraction on sets
  • Possibility to intercept stdio operations with individual handlers
  • Registering new native event types for interacting with script
  • Utilities for math functions (System.Math.TMath), ui (FMX.UI.TUI), stdio functions (System) and more.

Types

Like Delphi/ObjectPascal, GorillaScript is also a typed scripting language. Each variable, parameter or return-value needs to be declared by a specific type.

DataType Range Format
Int8-128..127Signed 8-bit
UInt80..255Unsigned 8-bit
ShortInt-128..127Signed 8-bit
Byte0..255Unsigned 8-bit
Int16-32768..32767Signed 16-bit
UInt160..65535Unsigned 16-bit
SmallInt-32768..32767Signed 16-bit
Word0..65535Unsigned 16-bit
Int32-2147483648..2147483647Signed 32-bit
UInt320..4294967295Unsigned 32-bit
Integer-2147483648..2147483647Signed 32-bit
Cardinal0..4294967295Unsigned 32-bit
LongInt 32-bit platforms and 64-bit Windows platforms: -2147483648..2147483647; 64-bit POSIX platforms include iOS and Linux: -9223372036854775808..9223372036854775807 Signed 32-bit / Signed 64-bit
LongWord32-bit platforms and 64-bit Windows platforms: 0..4294967295; 64-bit POSIX platforms include iOS and Linux: 0..18446744073709551615 Unsigned 32-bit/Unsigned 64-bit
Int64-9223372036854775808..9223372036854775807Signed 64-bit
UInt640..18446744073709551615Unsigned 64-bit
Single1.18e-38 .. 3.40e+384 Bytes
Double2.23e-308 .. 1.79e+3088 Bytes
StringUnicode WideChar Stringeach Char of fixed 2 Bytes
PointerNIL/Ordinal4 Bytes (32-Bit) / 8 Bytes (64-Bit)
BooleanTRUE/FALSE1 Byte
TArray
var LArr1 : TArray;
begin
  LArr1 := TArray.Create(5);
  LArr1[0] := 'abc';
  LArr1[1] := 123.25;
  LArr1[2] := true;
  LArr1[4] := LArr1[0];
  System.WriteLn(LArr1.ToString() + #13#10);
end;
 
Dynamic Array with variable content
TValue
var LVal : TValue;
begin
  LVal := 'StringValue';
  System.WriteLn(LVal);
end;
 
Variant type with variable content
TMethod Do not use directly in scripts.Reference record structure to link scripting and native methods
Class
...
type
  TMyClass = class(TObject)
    private
    protected
    public
    published
  end;
 
  TMyClass2 = class(TMyClass)
  end;
 
  TMyClass3 = class(TMyClass2)
  end;
  ...
Class structure allowing inheritance
RecordCan not be declared in GorillaScript. Only native registration allowed. Record structure with fields but without inheritance
Interface
ITestInterface = interface(IInterface)
['{12345678-1234-1234-12345678}']
end;
Enumeration
TTestEnum = (One, Two, Three);
1-8 Bytes
Set
TTestSet = set of TTestEnum;
1-8 Bytes

Operators

Operator Type Description
+mathmaticalAddition or concatenation
-mathmaticalSubtraction
*mathmaticalMultiplication
/mathmaticalDivision
divmathmaticalDivision
modmathmaticalModulo
=booleancompare if 2 values are equal
<>booleancompare if 2 values are not equal
booleancompare if first value is smaller/less than second value or both values are equal
>=booleancompare if first value is greater/larger than second value or both values are equal
<booleancompare if first value is smaller/less than second value
>booleancompare if first value is greater/larger than second value
notbooleannegate/invert value
andbooleanoperation to concatenate 2 boolean values
orbooleanoperation to concatenate 2 boolean values
xorbooleanoperation to concatenate 2 boolean values
andbitwiseoperation to concatenate 2 value on low level
orbitwiseoperation to concatenate 2 value on low level
xorbitwiseoperation to concatenate 2 value on low level
shrbitwiseoperation to shift by a specific number of bits to the right
shlbitwiseoperation to shift by a specific number of bits to the left
astypecastas operator for explicit type-casts, supported for classes or basic types (string, int, int64, boolean and floats)
incheckchecks if right side contains the left side. it's a multitype operator for enums or strings

Routines

In GorillaScript you can define global functions/procedures and class/interface methods. Declaration and calling conventions should be comparable to Delphi/ObjectPascal.

Global Functions and Procedures

program demo;
 
uses
	System.SysUtils;
 
	function Calculate(A : Integer; B : Double) : Double;
	begin
	  Result := A * B;
	end;
 
	procedure Output(AStr : String; _A : Integer; _B : Double);
	var LStr : String;
	begin
	  LStr := Calculate(_A, _B).ToString();
	  System.WriteLn(AStr + LStr);
	end;
 
	procedure Main();
	begin
	  Output('Result = ', 10, 123.45);
	end;
 
begin
  Main();
end.

Special Features

GorillaScript provides some useful features for faster development, which are not compatible with Delphi. So don't use those, if you'd like to use your source in both (Delphi & GorillaScript)

  • Auto-TypeCasts for simple types, f.e. combine a string and integer without conversion
System.WriteLn('Value is ' + LInt32Val);
 
  • Implicit rounding of floats, when needed on operations
  • Extended subtract operator on strings (String.Replace)
LValue := 'Hello World';
LStr := LValue1 - 'Hello ';
 
// will produce: "World"
System.WriteLn(LStr);
 
  • Extended multiplication operator on strings (String.Repeat)
LValue := 'Hello ';
LStr := LValue * 3;
 
// will produce: "Hello Hello Hello "
System.WriteLn(LStr);
 
  • Extended division operator on strings (String.SubString)
LValue := 'Hello World';
LArr := LValue / 4;
 
// will produce an array with 4 elements ['Hel', 'lo ', 'Wor', 'ld']
System.WriteLn(LArr.ToString());
 
LValue := 'Hello World';
LArr := LValue / ' ';
 
// will produce an array with 2 elements ['Hello', 'World']
System.WriteLn(LArr.ToString());
 
  • Extended shift-left operator on strings (String.SubString)
LValue := 'Hello World';
LStr := LValue shl 3;
 
// will produce: "lo World"
System.WriteLn(LStr);
 
  • Extended shift-right operator on strings (String.SubString)
LValue := 'Hello World';
LStr := LValue shr 3;
 
// will produce: "Hello Wo"
System.WriteLn(LStr);
 
  • In-Operator for strings
if ('ello' in 'Hello World') then
  System.WriteLn('yes!');
 
  • case-statement labels allow all types (not only ordinal types)
LStr := 'Hello';
case LStr of
  'Hellooo' : System.WriteLn('case #1');			
  'Hallo'..'Hulu' : System.WriteLn('case #2');
 
  else System.WriteLn('case else');
end;
 
  • Variant arrays by default
var LArr1 : TArray;
begin
  LArr1 := TArray.Create(5);
  LArr1[0] := 'abc';
  LArr1[1] := 123.25;
  LArr1[2] := true;
  LArr1[4] := LArr1[0];
  System.WriteLn(LArr1.ToString() + #13#10);
end;
 
  • TypeCast Operator “as” for output conversion
var LStr : String;
    LFlt : Single;
begin
  // auto-string/bool/int/float conversion, will output here: "True"
  LStr := (('123' as Int32) as Boolean) as String;
  System.WriteLn(LStr as String);
 
  // auto-rounding, will output "124"
  LFlt := ((123.56789 as Int32) as Double);
  System.WriteLn(LFlt as String);
end;
 

Stages

GorillaScript is based on the typical scripting stages:

  1. parse
  2. compile
  3. execute

Those stages describe a full execution process, but can be reduced to single stage, by only using the bytecode format.

This means you can save a compiled bytecode to file and load it independently again to execute it. This may be useful for release versions of your app, where nobody should see any script source code.

Implementation

The scripting component provides 2 types of implementation. You're allowed to run a script temporarily or in keep-alive mode.

Temporary Scripts

Temporary script will be executed once and all instances destroyed afterwards. By temporary scripts no further interaction with created instance is possible. This feature is useful for quick manipulation purposes. The most important usage may be simulating a Delphi context only by scripting, f.e. in a console application.

program ScriptDemo;
 
{$APPTYPE CONSOLE}
 
{$R *.res}
 
uses
  System.SysUtils,
  Gorilla.Script in '..\..\lib\script\Gorilla.Script.pas';
 
var LEngine : TGorillaScriptEngine;
begin
  try   
    WriteLn('*** GORILLA3D SCRIPTING TEST ***');
    WriteLn('');
 
    LEngine := TGorillaScriptEngine.Create(nil);
    try
    {$IFDEF MSWINDOWS}
      LPath := '';
    {$ENDIF}
    {$IFDEF ANDROID}
      LPath := IncludeTrailingPathDelimiter(
        System.IOUtils.TPath.GetHomePath());
    {$ENDIF}
 
      // load and parse program source code
      LEngine.LoadFromFile(LPath + 'app.pas');
 
      // compile parsed entities to bytecode
      LEngine.Compile();
 
      // execute bytecode
      LEngine.Execute();
    finally
      FreeAndNil(LEngine);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Keep-Alive Scripts

Keep-Alive script integration allows users to execute GorillaScript on top of an existing Delphi application. This may be the most popular mode, because it allows to create visual components and to interact with them by events.

By this mode you can setup a form completely by scripting, without loosing capabilities of the native Delphi application.

unit TestWinU;
 
interface
 
uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms,
  Gorilla.Script;
 
type
  TTestWin = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  protected
    FEngine : TGorillaScriptEngine;
  public
  end;
 
var
  Form2: TForm2;
 
implementation
 
{$R *.fmx}
 
uses
  System.IOUtils;
 
{ TTestWin }
 
procedure TTestWin.FormCreate(Sender: TObject);
var LPath : String;
begin
  // create script engine
  FEngine := TGorillaScriptEngine.Create(nil);
 
{$IFDEF MSWINDOWS}
  LPath := '';
{$ENDIF}
{$IFDEF ANDROID}
  LPath := IncludeTrailingPathDelimiter(System.IOUtils.TPath.GetHomePath());
{$ENDIF}
 
  // load and parse program source code
  FEngine.LoadFromFile(LPath + 'GorillaTest.pas');
 
  // compile parsed entities to bytecode
  FEngine.Compile();
 
  // execute bytecode in keep-alive mode (parameter = true)
  FEngine.Execute(true);
 
  // script instances are still available from here...
end;
 
procedure TTestWin.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FEngine);
end;
 
end.

IO-Interceptor

In script you can use the functions “System.Write”, “System.WriteLn”, “System.Read” and “System.ReadLn” to read/write from/to standard input-output (stdio).

For console applications no further treatment is needed. On GUI applications this leads to exceptions, because the stdio is not available.

Due to that, we provide an interceptor class to catch stdio calls.

type
  TGorillaScriptIOHandlerGUI = class(TGorillaScriptIOHandler)
    public
      class var Memo : TMemo;
 
      class procedure Write(AValue : String); override;
      class procedure WriteLn(AValue : String); override;
      class function Read() : String; override;
      class function ReadLn() : String; override;
  end;
 
...
 
{ TGorillaScriptIOHandlerGUI }
 
class procedure TGorillaScriptIOHandlerGUI.Write(AValue : String);
begin
  TThread.Synchronize(nil,
    procedure()
    begin
      Memo.Lines.Add(AValue);
    end
  );
end;
 
class procedure TGorillaScriptIOHandlerGUI.WriteLn(AValue : String);
begin
  TThread.Synchronize(nil,
    procedure()
    begin
      Memo.Lines.Add(AValue);
    end
  );
end;
 
class function TGorillaScriptIOHandlerGUI.Read() : String;
begin
  Result := '';
end;
 
class function TGorillaScriptIOHandlerGUI.ReadLn() : String;
begin
  Result := '';
end;  

In this example we need to assign the memo to the interceptor, so the component knows where to write lines to:

TGorillaScriptIOHandlerGUI.Memo := Memo1;

To activate the IO-Interceptor you just need to set it as default interceptor:

TGorillaScriptIOHandler.Default := TGorillaScriptIOHandlerGUI;

Syntax

Even if we've tried to stay as close as possible to Delphi language syntax, some restrictions and extended features have to be kept in mind.

Program

Every GorillaScript starts with a main unit, where you define a typical pascal program. In the program initialization block you can call functions. You will need to define at least one global function here and call it.

It is not possible to use variables in global context. You can only define local variables in Main() routine.

program Test;
 
interface
 
procedure Main();
begin
  System.WriteLn('Hello world!');
end;
 
begin
  Main();
end.

Includes

Besides the System unit, you always need to include units, when using types of those. Even when using types indirectly, like as field or property type. The complete qualified unit name is needed for successful inclusion, f.e. “System.Types”. Units can be included in interface section by the usage of the “uses” identifier.

unit Test;
 
interface
 
uses
  System.Types, System.Classes, FMX.UI, FMX.StdCtrls;
 
  ...

Global Functions

procedure Main();
begin
  // write some code here
end;

Global Constants

const 
	DEFAULT_STR : String = 'Test' + '-Output-' + 'Test:';

Local Variables

Local variables are declared like Delphi syntax by the “var” identifier.

procedure Test();
var i : Integer;
    LStr : String;
    LFloat1, LFloat2 : Single;
begin
  // write some code here
end;

Syntax Elements

Since Rev. 2730+ you're able to call EXIT or HALT like in Delphi syntax.

Exit

The exit statement will immediatly leave a function or program code.

NOTICE: Currently it's not possible to directly set the result value in brackets!

In the example below the function will return “A large value!” if the supplied argument is larger than 100. Otherwise it will return the integer value as string.

function MyTest(AValue : Integer) : String;
begin
  if (AValue > 100) then
  begin
    Result := 'A large value!';
    Exit;
  end;
 
  Result := IntToStr(AValue);
end;

Halt

The HALT statement is useful to immediatly stop the program from execution. It doesn't matter if you're in a function or in main program code. The executor will immediatly stop bytecode execution.

In the example below we stop the program if the supplied argument is larger than 100. Otherwise nothing will happen.

procedure StopMyProgram(AValue : Integer);
begin
  if (AValue > 100) then
    Halt;
end;

Classes

Self

At the current development state it is always necessary to supply the self-reference variable when accessing fields, properties or methods of the class. In future this will be removed like in Delphi syntax.

Wrong syntax

FMyField := 123;

Correct syntax

Self.FMyField := 123;

Scripting

GorillaScript allows to declare new classes in script. Class inheritance is provided, but only for script classes. Only TObject and TInterbasedObject are allowed as native ancestors.

...
type
  TMyClass = class(TObject)
    private
    protected
    public
    published
  end;
 
  TMyClass2 = class(TMyClass)
  end;
 
  TMyClass3 = class(TMyClass2)
  end;
  ...

Scripting classes allow usage of visibility identifiers: private, protected, public and published.

Fields

	TTest = class
		protected
			FFieldBool : Boolean;
			FFieldStr : String;
			FFieldInt : Int32;
 
		public
			FFieldDbl : Double;
			FFieldObj : TTest;
	end;

Properties

Properties are a very useful tool to access fields directly or by a getter or setter method. In the following example we extend our TTest class by a GetFieldBool() and SetFieldBool() protected method. In the public property “FieldBool” we can access the protected field “FFieldBool” by those methods indirectly. We than have the option to influence behaviour.

The second property “FieldStr” is set to read-only on our protected field “FFieldStr”, which forbids writing to. Of course this can also be done by getter or setter methods.

The third property “FieldInt” allows reading and writing operations directly on the protected field “FFieldInt”

Note: You can mix getter/setter methods and direct field access on read/write.

	TTest = class
		protected
			FFieldBool : Boolean;
			FFieldStr : String;
			FFieldInt : Int32;			
 
			function GetFieldBool() : Boolean;
			procedure SetFieldBool(AValue : Boolean);
 
		public
			FFieldDbl : Double;
			FFieldObj : TTest;
 
			property FieldBool : Boolean read GetFieldBool write SetFieldBool;
			property FieldStr : String read FFieldStr;
			property FieldInt : Int32 read FFieldInt write FFieldInt;
	end;

Methods

Functions and procedures declared in a class, interface or record are called methods. Those can be defined in all available viewspaces (private, protected, public, published) like fields.

But besides a simple object method declaration, Delphi offers constructors, destructors and class functions / class procedures. They are declared by a different reserved word, because their behaviour is a bit different than the simple methods.

Constructors are getting automatically called on instance creation and destructors when an instance is about to be destroyed.

Class functions / procedures OR static methods are like simple functions and procedures, but they are called in different context. Regular functions/procedures are called by an instance (object) of the class, while static methods are called by the class itself. So they have no instance context and therefore no access to fields or object methods.

This is getting important, when using “Self” in those methods. For static methods it is the class type itself, but for object methods it's the object.

	TTest = class
		protected
			[...]			
			function Calculate(AFactor : Double) : Double; virtual;
 
		public
			[...]			
			constructor Create(); virtual;
			destructor Destroy(); override;
 
			class function GetDefaultStr() : String;
 
			procedure Output(AIdx : Integer);
	end;

After our method head declaration is done, we need to implement the function.

/// <summary>
/// Test Scripting Unit
/// </summary>
unit app.test;
 
interface
 
const 
	DEFAULT_STR : String = 'Test' + '-Output-' + 'Test:';
type
	TTest = class
	[...]
	end;
 
implementation
 
{ TTest }
 
constructor TTest.Create();
begin
	inherited Create();
 
	// use property test
	Self.FieldBool := true;
 
	Self.FFieldStr := 'Hello World';
	Self.FFieldInt := 124;
	Self.FFieldDbl := 1.25;
	Self.FFieldObj := Self;
end;
 
destructor TTest.Destroy();
begin
	inherited Destroy();
end;
 
function TTest.GetFieldBool() : Boolean;
begin
	Result := Self.FFieldBool;
	System.WriteLn('TTest.GetFieldBool() called');
end;
 
procedure TTest.SetFieldBool(AValue : Boolean);
begin
	Self.FFieldBool := AValue;
	System.WriteLn('TTest.SetFieldBool() called');
end;
 
class function TTest.GetDefaultStr() : String;
begin
	Result := DEFAULT_STR;
end;
 
procedure TTest.Output(AIdx : Integer);
var LTmpStr : String;
    LSeed : Single;
begin
    LSeed := System.RandomFloat();
	LTmpStr := TTest.GetDefaultStr() +
		'Object{' +
		'"fieldStr":"' + Self.FFieldStr +
		'", "fieldInt":"' + Self.FFieldInt.ToString() +
		'", "fieldDbl":"' + Self.FFieldDbl.ToString() +
		'", "fieldBool":"' + Self.FieldBool.ToString() +
		'", "fieldObj":"' + Self.FFieldObj.ToJSON() +
		'"}';
	LTmpStr := AIdx.ToString() + #9' [' + LSeed.ToString() + ']'#9#9 + LTmpStr + 
		' => ' + Self.Calculate(AIdx * 0.5).ToString();
	System.WriteLn(LTmpStr);
end;
 
function TTest.Calculate(AFactor : Double) : Double;
begin
	Result := Self.FFieldInt.ToDouble() + Self.FFieldDbl * AFactor;
end;
 
end.

TypeCasts

Since Rev. 2730 we've implemented the AS-Operator for classes and basic types. You can now check if a object variable can be casted into a specific class type.

In our example below, we create an instance of TTest2, but our local variable is only of type TTest. It will still work without the as-operation to set the local variable, but in some cases you might need type-safety.

type	
    TTest = class
    end;
 
    TTest2 = class(TTest)
    end;
 
[...]
 
var LCls : TTest;
begin
  LCls := TTest2.Create(ADummy) as TTest;
end;

Native

procedure RegisterMyClasses(AEngine : TGorillaScriptEngine);
begin
  AEngine.RegisterNativeClasses([TMyNativeClass1, TMyNativeClass2]);
end;

Records

Scripting

Declarations of records in script is currently not possible.

Native

procedure RegisterMyNativeRecords(AEngine : TGorillaScriptEngine);
begin
  AEngine.RegisterNativeStructs([TypeInfo(TPoint), TypeInfo(TRectF)]);
end;

Interfaces

Scripting

Script interfaces allow to declare properties and methods. Fields are not allowed in interfaces.

ITestInterface = interface(IInterface)
['{12345678-1234-1234-12345678}']
end;

Methods

Properties

Native

Registration of native interfaces differs from the other registrations methods due to RTTI limitations. We do need the full qualified name of your native interface as string variable.

procedure RegisterMyInterfaces(AEngine : TGorillaScriptEngine);
begin
  AEngine.RegisterNativeInterfaces(['System.Classes.IControl']);
end;

Due to missing RTTI of interfaces, it is not possible to access native interface properties. Only interface methods are accessable.

Enumerations

Native and script enumeration types are supported.

Scripting

Enumeration values can be declared with or without a preset value.

TTestEnum = (teOne, teTwo = 1, teThree = 2);

Native

procedure RegisterMyEnums(AEngine : TGorillaScriptEngine);
begin
  AEngine.RegisterNativeEnums([TypeInfo(TFontStyle), ...]);
end;

Sets

Native and script set of enumeration types are supported.

Scripting

In scripting sets always need an existing script/native enumeration type. Dynamic declarations like “TTestSet = set of (EnumVal1, EnumVal2, EnumVal3);” are not allowed!

TTestSet = set of TTestEnum;

Native

procedure RegisterMySets(AEngine : TGorillaScriptEngine);
begin
  AEngine.RegisterNativeSets([TypeInfo(TStyledSettings), TypeInfo(TShiftState)]);
end;

Events

You are allowed to declare a scripting method with the same parameters like a native event and to assign to a native instance. The script will manage to call your scripting method, when a native instance event getting called by delphi.

unit EventTest;
 
interface
 
uses
  FMX.UI;
 
type
  TMyClass = class(TObject)
    protected
      FButton : TButton;
      procedure DoOnClick(ASender : TObject);
 
    public
      constructor Create();
  end;
 
implementation
 
{ TMyClass }
constructor TMyClass.Create();
begin
  inherited Create();
 
  Self.FButton := TButton.Create(TUI.GetMainForm());
 
  // Link script method with native component event
  // When Delphi calls the native event, the script method will be called.
  Self.FButton.OnClick := @Self.DoOnClick;
end;
 
procedure TMyClass.DoOnClick(ASender : TObject);
begin
  TUI.ShowMessage('Button clicked!');
end;
 
end.

Loops

For-Loop

System.WriteLn('for-loop with continue should compute 9');
LVal := 0;
for LInt := 0 to 9 do
  if (LInt = 5) then
    Continue
  else
    LVal := LVal + 1;
 
System.WriteLn(LVal.ToString());
System.WriteLn('for-loop with break should compute 5');
LVal := 0;
for LInt := 0 to 9 do
begin
  if (LInt = 5) then
  begin
    Break;
  end
  else LVal := LVal + 1;
end;
 
System.WriteLn(LVal.ToString());

Repeat-Until-Loop

System.WriteLn('repeat-loop with multiple condition should compute 5');
LInt := 0;
repeat
  LInt := LInt + 1;
until (LInt >= 10) and (LInt > 5) or (LInt = 5);
System.WriteLn(LInt.ToString());
System.WriteLn('repeat-loop with break should compute 6');
LInt := 0;
repeat
  if (LInt = 6) then
    Break;
 
  LInt := LInt + 1;
until (LInt >= 10);
System.WriteLn(LInt.ToString());

While-Do-Loop

System.WriteLn('while-loop with break should compute 6');
LInt := 0;
while (LInt < 10) do
begin
  if (LInt = 6) then
    Break;
 
  LInt := LInt + 1;
end;
 
System.WriteLn(LInt.ToString());
System.WriteLn('while-loop with continue should compute 9');
LInt := 0;
LVal := 0;
while (LInt < 10) do
begin
  LInt := LInt + 1;
  if (LInt = 6) then
    Continue;
 
  LVal := LVal + 1;
end;
 
System.WriteLn(LVal.ToString());

If-Statements

System.WriteLn('complex if-condition should return 123.');
LInt := 123;
if (LInt > 100) then
begin
  if (LInt > 50) then
  begin
    if (LInt > 25) then
      if (LInt > 10) then
      begin
        System.WriteLn('123');
      end
      else System.WriteLn('FALSE (4)')
    else
     System.WriteLn('FALSE (3)');
  end
  else System.WriteLn('FALSE (2)');
end
else System.WriteLn('FALSE (1)');

Case-Of-Statements

System.WriteLn('"case LInt of" should return : case #3');
LInt := 8;
case LInt of
  0    : System.WriteLn('case #1');			
  1..2 : System.WriteLn('case #2');
 
  3..4, 
  5,
  6..9 : begin
             System.WriteLn('case #3');
           end;
 
  else System.WriteLn('case else');
end;
System.WriteLn('"case LStr of" should return : case #2');		
LStr := 'Hello';
case LStr of
  'Hellooo' : System.WriteLn('case #1');			
  'Hallo'..'Hulu' : System.WriteLn('case #2');
 
  else System.WriteLn('case else');
end;

Internals

SearchPaths

If no further search paths were set in your TGorillaScriptEngine component, the scripting will take the first script file directory as base directory to search from. You have to set all necessary search paths before parsing stage starts.

FEngine.SearchPaths.Add('./subdir/');
FEngine.SearchPaths.Add('C:\test\dir\');

User-Specific Compiler Defines

You are allowed to add global user specific compiler defines in your TGorillaScriptEngine component. You have to apply those defines before parsing stage starts.

FEngine.CompilerDirectives.Add('THE_FORCE_IS_WITH_YOU');

Predefined Libs

GorillaScript provides some basic libraries, like UI components (buttons, edits, …) or Gorilla3D components. Those libraries automatically register all necessary types for you, by simply calling their registration function.

General libs:

  • Gorilla.Script.Lib.OS (automatically registered)
  • Gorilla.Script.Lib.Math (automatically registered)
  • Gorilla.Script.Lib.UI (RegisterUIComponents)

Modular Gorilla3D libs:

  • Gorilla.Script.Lib.Gorilla (RegisterGorillaComponents)
  • Gorilla.Script.Lib.Particles (RegisterGorillaParticles)
  • Gorilla.Script.Lib.Physics (RegisterGorillaPhysics)
  • Gorilla.Script.Lib.Audio (RegisterGorillaAudio)
  • Gorilla.Script.Lib.Input (RegisterGorillaInput)
  • Gorilla.Script.Lib.Utils (RegisterGorillaUtils)
  • Gorilla.Script.Lib.GUI (RegisterGorillaGUI)
  Gorilla.Script.Lib.UI.RegisterUIComponents(FEngine);
  Gorilla.Script.Lib.Gorilla.RegisterGorillaComponents(FEngine);
  Gorilla.Script.Lib.Particles.RegisterGorillaParticles(FEngine);
  Gorilla.Script.Lib.Physics.RegisterGorillaPhysics(FEngine);
  Gorilla.Script.Lib.Audio.RegisterGorillaAudio(FEngine);
  Gorilla.Script.Lib.Input.RegisterGorillaInput(FEngine);
  Gorilla.Script.Lib.Utils.RegisterGorillaUtils(FEngine);
  Gorilla.Script.Lib.GUI.RegisterGorillaGUI(FEngine);

Examples

A simple script

program GorillaTest;
 
uses
  System.SysUtils, GorillaTest.App;
 
  procedure Main();
  var LApp : TGorillaApp;
  begin		
    // this script is used in keep-alive mode
    // the created script object will be destroyed automatically
    // when the executor will be destroyed.
    LApp := TGorillaApp.Create();
  end;
 
begin
  Main();
end.
unit GorillaTest.App;
 
interface
 
uses
  System.SysUtils, FMX.OS, FMX.UI, FMX.Types, FMX.Forms, FMX.StdCtrls;
 
type	
  TGorillaApp = class
    protected
      FForm   : TForm;
      FButton : TButton;
      FEdit   : TEdit;
 
    public
      property Form : TForm read FForm;
      property Button : TButton read FButton;
      property Edit : TEdit read FEdit;
 
      constructor Create(); override;
      procedure DoOnButtonClicked(ASender : TObject);
  end;
 
implementation
 
constructor TGorillaApp.Create();
begin
  inherited Create();
 
  Self.FForm := TUI.GetMainForm();
 
  Self.FEdit := TEdit.Create(Self.FForm);
  Self.FEdit.Parent := Self.FForm;
  Self.FEdit.Width := 200;
  Self.FEdit.Height := 32;
  Self.FEdit.Align := TAlignLayout.Top;
  Self.FEdit.Margins.Left := 8;
  Self.FEdit.Margins.Right := 8;
  Self.FEdit.Margins.Top := 4;
  Self.FEdit.Margins.Bottom := 4;
 
  Self.FButton := TButton.Create(Self.FForm);
  Self.FButton.Parent := Self.FForm;
  Self.FButton.Position.Point := TPointF.Create(200, 128);
  Self.FButton.Text := 'Click me!';
  Self.FButton.Size.Width := 128;
  Self.FButton.Size.Height := 48;
 
  // we link here our scripting method to a native event property
  // Gorilla script executor will forward this OnClick event to our scripting method
  Self.FButton.OnClick := @Self.DoOnButtonClicked;
end;
 
procedure TGorillaApp.DoOnButtonClicked(ASender : TObject);
var LBtn : TButton;
begin
  // we just move the button randomly
  if (Self.Button = ASender) then
  begin
    LBtn := ASender;
    LBtn.Position.Point := TPointF.Create(
      System.Random(400), System.Random(400));
  end;
end;
 
end.

3D Demo

program Gorilla3DTest;
 
uses
	System.SysUtils, FMX.UI, Gorilla3DTest.App;
 
	procedure Main();
	var LApp : TGorillaApp;
	begin
		// this script is used in keep-alive mode
		// the created script object will be destroyed automatically
		// when the executor will be destroyed.
		LApp := TGorillaApp.Create();
	end;
 
begin
	Main();
end.
unit Gorilla3DTest.App;
 
interface
 
uses
	System.SysUtils, System.Math,
	FMX.OS, FMX.UI, FMX.Types, FMX.Forms, FMX.StdCtrls, FMX.Objects3D, 
	Gorilla.Viewport, Gorilla.Camera, Gorilla.Light, Gorilla.Controller.Input,
	Gorilla.Material.Lambert, Gorilla.Material.Bumpmap;
 
const 
	GORILLA_CAPTION : String = 'Gorilla3D Scripting 3D Demo';
 
type	
	TGorillaApp = class
		protected
			FWindow    : TForm;
			FViewport  : TGorillaViewport;
			FCenter    : TDummy;
			FCamera    : TGorillaCamera;
			FLight     : TGorillaLight;
			FCube      : TGorillaCube;
			FSphere    : TGorillaSphere;
			FPlane     : TGorillaPlane;
			FMaterial1 : TGorillaLambertMaterialSource;
			FMaterial2 : TGorillaBumpMapMaterialSource;
			FTimer     : TTimer;
			FInput     : TGorillaInputController;
 
			FSinCurve  : Double;
 
			procedure DoOnKeyUp(ASender : TObject; AKeyCode : Integer);
			procedure DoOnTimer(ASender : TObject);
 
		public
			property Viewport : TGorillaViewport read FViewport;
			property Camera : TGorillaCamera read FCamera;
			property Light : TGorillaLight read FLight;
 
			property Cube : TGorillaCube read FCube;
			property Sphere : TGorillaSphere read FSphere;
			property Plane : TGorillaPlane read FPlane;
 
			property Timer : TTimer read FTimer;
 
			constructor Create(); override;
	end;
 
implementation
 
constructor TGorillaApp.Create();
var LForm : TForm;
begin
	inherited Create();
 
	LForm := TUI.GetMainForm();
	Self.FSinCurve := 0.0;
 
	Self.FWindow := LForm;
	Self.FWindow.Caption := GORILLA_CAPTION;
 
	// create viewport
	Self.FViewport := TGorillaViewport.Create(LForm);
	Self.FViewport.Parent := LForm;
 
	// create dummy and camera
	Self.FCenter := TDummy.Create(Self.FViewport);
	Self.FCenter.Parent := Self.FViewport;
 
	Self.FCamera := TGorillaCamera.Create(Self.FCenter);
	Self.FCamera.Parent := Self.FCenter;
	Self.FCamera.Target := Self.FCenter;
	Self.FCamera.Position.Point := TPoint3D.Create(0, -1, 10);
 
	// create light source
	Self.FLight := TGorillaLight.Create(Self.FViewport);
	Self.FLight.Parent := Self.FCamera;
	Self.FLight.Position.Point := TPoint3D.Create(0, -10, 0);
	Self.FLight.LightType := TLightType.Spot;
	Self.FLight.RotationAngle.X := -90;
 
	// create cube and sphere
	Self.FCube := TGorillaCube.Create(Self.FViewport);
	Self.FCube.Parent := Self.FViewport;
	Self.FCube.Scale.Point := TPoint3D.Create(5, 5, 5);
	Self.FMaterial1 := TGorillaLambertMaterialSource.Create(Self.FCube);
	Self.FMaterial1.Parent := Self.FCube;
	Self.FMaterial1.Texture.LoadFromFile('..\assets\textures\crate1_diffuse.png');
	Self.FCube.MaterialSource := Self.FMaterial1;
 
	Self.FSphere := TGorillaSphere.Create(Self.FCube);
	Self.FSphere.Parent := Self.FCube;	
	Self.FSphere.Position.Point := TPoint3D.Create(-3, -1, 0);
	Self.FMaterial2 := TGorillaBumpMapMaterialSource.Create(Self.FSphere);
	Self.FMaterial2.Parent := Self.FSphere;
	Self.FMaterial2.Texture.LoadFromFile('..\assets\textures\bump\harshbricks-albedo.png');
	Self.FMaterial2.NormalMap.LoadFromFile('..\assets\textures\bump\harshbricks-normal.png');
	Self.FMaterial2.DisplacementMap.LoadFromFile('..\assets\textures\bump\harshbricks-height5-16.png');
	Self.FMaterial2.SpecularMap.LoadFromFile('..\assets\textures\bump\harshbricks-ao2.png');
	Self.FSphere.MaterialSource := Self.FMaterial2;
 
	Self.FPlane := TGorillaPlane.Create(Self.FViewport);
	Self.FPlane.Parent := Self.FViewport;
	Self.FPlane.RotationAngle.X := -90;
	Self.FPlane.SetSize(100, 100, 1);
	Self.FPlane.MaterialSource := Self.FMaterial1;
 
	// create a timer for animation
	Self.FTimer := TTimer.Create(Self.FViewport);
	Self.FTimer.Parent := Self.FViewport;
	Self.FTimer.Interval := 16;
	Self.FTimer.OnTimer := @Self.DoOnTimer;
 
	Self.FInput := TGorillaInputController.Create(LForm);
	Self.FInput.Supported := [TGorillaInputDeviceType.Keyboard];
	Self.FInput.OnKeyUp := @Self.DoOnKeyUp;
	Self.FInput.Enabled := true;
end;
 
procedure TGorillaApp.DoOnKeyUp(ASender : TObject; AKeyCode : Integer);
begin
	TUI.ShowInfo('Taste gedrückt = ' + AKeyCode.ToChar());
end;
 
procedure TGorillaApp.DoOnTimer(ASender : TObject);
var LY, LX : Double;
begin
	// update window caption with FPS for checking performance
	Self.FWindow.Caption := GORILLA_CAPTION + ' ' + Self.FViewport.FPS.ToString() + ' FPS';
 
	// animate 3D elements
	Self.FViewport.BeginUpdate();
	try
		Self.FSinCurve := Self.FSinCurve + 0.1;
		LY := TMath.Sin(Self.FSinCurve);
		LX := TMath.Cos(Self.FSinCurve);
 
		Self.FCube.Position.Point := TPoint3D.Create(0, LY * 2, 0);
		Self.FCube.RotationAngle.Y := Self.FCube.RotationAngle.Y + 1;
		Self.FSphere.RotationAngle.Y := Self.FSphere.RotationAngle.Y - 1;
 
		Self.FLight.Position.Point := TPoint3D.Create(LX * 5, -10, LY * 3);
	finally
		Self.FViewport.EndUpdate();
	end;
end;
 
end.

Next step: Interaction