ForthMinus

From OpenSimulator

(Difference between revisions)
Jump to: navigation, search
m (Reformatted code examples in Notes section)
 
(10 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
__NOTOC__
 +
{{Quicklinks}}
 +
<br />
 +
 
== Description ==
 
== Description ==
ForthMinus is an alternative scripting engine for OpenSim. It's dialect resembles (loosely) the [http://en.wikipedia.org/wiki/Forth_%28programming_language%29 FORTH]programming language and it's code runs within a Virtual Machine (VM). It's primary purpose was to test the feasibility of implementing a VM to handle the micro-threading of scripts as well as speed up the development of llFunctions for the main LSL script engine. It currently has some advantages and disadvantages versus the primary script engine (DotNetEngine):
+
ForthMinus is an alternative scripting engine for OpenSimulator. It's dialect resembles (loosely) the [http://en.wikipedia.org/wiki/Forth_%28programming_language%29 FORTH]programming language and it's code runs within a Virtual Machine (VM). It's primary purpose was to test the feasibility of implementing a VM to handle the micro-threading of scripts as well as speed up the development of llFunctions for the main LSL script engine. It currently has some advantages and disadvantages versus the primary script engine (DotNetEngine):
  
 
'''Advantages:'''
 
'''Advantages:'''
 
* Micro-threading - User scripts cannot lock up the Engine's threads. Perpetual loops can be created without fear of the engine becoming unresponsive.  
 
* Micro-threading - User scripts cannot lock up the Engine's threads. Perpetual loops can be created without fear of the engine becoming unresponsive.  
 
* State Saving - A ForthMinus script can be state saved thus allowing for script persistence through sim resets.
 
* State Saving - A ForthMinus script can be state saved thus allowing for script persistence through sim resets.
* Security - All scripts are ran under the VM opposed to being compiled into CIL and ran in parallel to OpenSim (although AppDomain implements some security measures).
+
* Security - All scripts are ran under the VM opposed to being compiled into CIL and ran in parallel to OpenSimulator (although AppDomain implements some security measures).
  
 
'''Disadvantages'''
 
'''Disadvantages'''
Line 14: Line 18:
 
To enable ForthMinus, you need to change the script_engine line within OpenSim.ini to:
 
To enable ForthMinus, you need to change the script_engine line within OpenSim.ini to:
  
  script_engine = OpenSim.Region.ScriptEngine.ForthMinus
+
  script_engine = OpenSim.Region.ScriptEngine.ForthMinus.dll
  
 
== Language Syntax ==
 
== Language Syntax ==
'''Definitions'''
+
=== Definitions ===
 
* Word - A function or subroutine. These can be user defined, or build in (via libraries).
 
* Word - A function or subroutine. These can be user defined, or build in (via libraries).
 
* Data Stack - The main repository for data the script will manipulate
 
* Data Stack - The main repository for data the script will manipulate
Line 23: Line 27:
 
* Dictionary - The dictionary holds the user defined words
 
* Dictionary - The dictionary holds the user defined words
 
* Variables - Variables can store data - however variables are much slower to utilize than the Data Stack.
 
* Variables - Variables can store data - however variables are much slower to utilize than the Data Stack.
 +
 +
=== Overview ===
  
 
ForthMinus is a simple language with no explicit grammar. Everything is executed in a postfix (or RPN) method, where the arguments for an operator come before the operator itself. For example, take this simple LSL script of a math problem:
 
ForthMinus is a simple language with no explicit grammar. Everything is executed in a postfix (or RPN) method, where the arguments for an operator come before the operator itself. For example, take this simple LSL script of a math problem:
Line 99: Line 105:
  
 
The word "@touched" is a special event word within ForthMinus. When an avatar touches a prim with a ForthMinus script in it, the engine checks to see if the script has "@touched" defined. If so, it pushes the word "@touched" to the ''bottom'' of the Execution Stack, thus not interrupting the current task of the script (this is similar to LSL - events are non-interrupting).
 
The word "@touched" is a special event word within ForthMinus. When an avatar touches a prim with a ForthMinus script in it, the engine checks to see if the script has "@touched" defined. If so, it pushes the word "@touched" to the ''bottom'' of the Execution Stack, thus not interrupting the current task of the script (this is similar to LSL - events are non-interrupting).
 +
 +
=== Quotations ===
 +
The engine has a data type called a quotation. A quotation is signified by square brackets ("[","]"). A quotation holds a chunk of data or even code. Quotations can be stored and manipulated as any other data (e.g. stored in the data stack, assigned a variable name, etc.). A quotation can be executed by the the word qcall. For example:
 +
 +
[ "Hello World" . ] qcall
 +
 +
In this example, the quotation itself is placed onto the data stack. The word "qcall" pops a quotation from the stack, and executes what is inbetween the brackets.
 +
 +
Besides storing hunks of code, quotations can be utilized to mimic other data structures, such as lists, vectors, and rotations. For example:
 +
 +
[ 45.3 23.5 100.3 ] qcall llsetpos
 +
 +
This would execute the vector stored in the quotation, push'ing it's three values into the data stack. The llsetpos word would set the host's position in the sim.
 +
 +
There are words to construct common SL related quotations from the stack. For example:
 +
 +
llgetpos v>q curpos !
 +
 +
* llgetpos pushes the prims current x, y, and z position onto the data stack
 +
* v>q pops the top three values of the stack and creates a quotation containing the x, y, z values ([ x y z ])
 +
* curpos ! stores the quotation into the variable curpos, hence, curpos = [ x y z ]
 +
 +
Quotations can be used in conditional statements:
 +
 +
0 [ "True" 0 llsay ] [ "False" 0 llsay ] qif
 +
 +
In this example:
 +
* 0 is pushed to the stack
 +
* The quotation [ "True" 0 llsay ] is pushed to the stack
 +
* The quotation [ "False" 0 llsay ] is pushed to the stack
 +
* qif pops three values off the stack. If the third deep is true, it qcall's the first quotation, otherwise, it qcall's the second. In this example, since 0 == false, the program will say "False" in world.
 +
 +
QIf statements can be layered by placing additional qif chunks within the quotation. For example:
 +
 +
3 dup 0 < [ "Negative" drop ] [ 0 > [ "Positive" ] [ "Zero" ] qif ] qif 0 llsay
 +
 +
This:
 +
* Pushes 3 into the data stack, then "dup" duplicates it and then pushes 0 to the stack (thus, or data stack is 3 3 0)
 +
* "<" pops two values (3 and 0 in this case), and if 3 < 0, pushes 1, otherwise pushes 0 (and in this case, of course it pushes 0, since 3 is not < 0)
 +
* The two quotations ([ "Negative" drop ] and [ 0 > [ "Positive" ] [ "Zero" ] qif ]) are pushed to the data stack.
 +
* The qif pops the truth value from the second step and the two quotations. Since the test was false, it qcall's the second hunk of code (0 > [ "Positive" ] [ "Zero" ] qif)
 +
* 0 is pushed, then ">" tests if the number is larger than 0, if so pushes "Positive", if not, pushes "Zero". In our example, it will push "Positive"
 +
* "0 llsay" will push 0, then say "Positive" on channel 0 in world.
 +
 +
There are other conditional and control words that utilize quotations:
 +
 +
1 [ "True!" 0 llsay ] qwhen
 +
 +
This will qcall the quotation when the previous item on the data stack is true.
 +
 +
10 [ "Hello World" 0 llsay ] qdo
 +
 +
This will execute the quotation 10 times.
 +
 +
Another nifty function that effects quotations is qforeach:
 +
 +
[ 1 2 3 4 ] [ 1 + ] qforeach
 +
 +
This will execute the second quotation on each element of the first, pushing the results. In this case, the stack would be "2 3 4 5".
 +
 +
Quotations can also be used to create custom user level words. The following two examples have the same end result:
 +
 +
: hello "Hello World!" 0 llsay ;
 +
 +
[ "Hello World!" 0 llsay ] hello qdef
  
 
== Primitive Libraries ==
 
== Primitive Libraries ==
Line 195: Line 266:
 
| ( n -- n )
 
| ( n -- n )
 
| Pushes variable n to stack
 
| Pushes variable n to stack
 +
|}
 +
 +
==== LibSLBase ====
 +
{| border=1 style="border-collapse:collapse;th background-color:#666;width:100%;text-align:center;"
 +
! style="background-color:#D2ECD2;" | Word
 +
! style="background-color:#D2ECD2;" | Stack
 +
! style="background-color:#D2ECD2;" | Description
 +
|-
 +
| llsay
 +
| ( n1 n2 -- )
 +
| Says n1 on channel n2 in world
 +
|-
 +
| llwhisper
 +
| ( n1 n2 -- )
 +
| Whispers n1 on channel n2 in world
 +
|-
 +
| llshout
 +
| ( n1 n2 -- )
 +
| Shouts n1 on channel n2 in world
 +
|-
 +
| llgetpos
 +
| ( -- x y z )
 +
| Pushes the position of the object in world
 +
|-
 +
| llsetpos
 +
| ( x y z -- )
 +
| Sets the position of the object in world
 +
|-
 +
| llgetrot
 +
| ( -- x y z s )
 +
| Pushes the rotation of the object to the stack
 +
|-
 +
| llsetrot
 +
| ( x y z s -- )
 +
| Sets the rotation of the object
 +
|-
 +
| llgetscale
 +
| ( -- x y z )
 +
| Pushes the scale of the object to stack
 +
|-
 +
| llsetscale
 +
| ( x y z -- )
 +
| Sets the scale of the object
 
|}
 
|}
  
Line 279: Line 393:
 
| touch_start(integer num)
 
| touch_start(integer num)
 
| @touched
 
| @touched
| @touchednum
+
| @touchednum @touchedname @touchedkey
 
|-
 
|-
 
| ''listen(integer channel, string name, key id, string message)''
 
| ''listen(integer channel, string name, key id, string message)''
Line 289: Line 403:
 
| @changedwhat
 
| @changedwhat
 
|}
 
|}
 +
 +
== Notes ==
 +
<pre>
 +
drop ( x -- )
 +
2drop ( x y -- )
 +
3drop ( x y z -- )
 +
nip ( x y -- y )
 +
2nip ( x y z -- z )
 +
</pre>
 +
 +
Duplicating stack elements:
 +
<pre>
 +
dup ( x -- x x )
 +
2dup ( x y -- x y x y )
 +
3dup ( x y z -- x y z x y z )
 +
dupd ( x y -- x x y )
 +
over ( x y -- x y x )
 +
pick ( x y z -- x y z x )
 +
tuck ( x y -- y x y )
 +
</pre>
 +
 +
Permuting stack elements:
 +
<pre>
 +
swap ( x y -- y x )
 +
swapd ( x y z -- y x z )
 +
rot ( x y z -- y z x )
 +
-rot ( x y z -- z x y )
 +
roll ( x y z t -- y z t x )
 +
-roll ( x y z t -- t x y z )
 +
</pre>

Latest revision as of 15:16, 21 November 2018


[edit] Description

ForthMinus is an alternative scripting engine for OpenSimulator. It's dialect resembles (loosely) the FORTHprogramming language and it's code runs within a Virtual Machine (VM). It's primary purpose was to test the feasibility of implementing a VM to handle the micro-threading of scripts as well as speed up the development of llFunctions for the main LSL script engine. It currently has some advantages and disadvantages versus the primary script engine (DotNetEngine):

Advantages:

  • Micro-threading - User scripts cannot lock up the Engine's threads. Perpetual loops can be created without fear of the engine becoming unresponsive.
  • State Saving - A ForthMinus script can be state saved thus allowing for script persistence through sim resets.
  • Security - All scripts are ran under the VM opposed to being compiled into CIL and ran in parallel to OpenSimulator (although AppDomain implements some security measures).

Disadvantages

  • 100% NOT LSL compatible - It does not resemble LSL/C/C# at all.
  • Slower - Since code is executed interpretively, scripts are quite a bit slower than compiled CIL is.

[edit] Using ForthMinus

To enable ForthMinus, you need to change the script_engine line within OpenSim.ini to:

script_engine = OpenSim.Region.ScriptEngine.ForthMinus.dll

[edit] Language Syntax

[edit] Definitions

  • Word - A function or subroutine. These can be user defined, or build in (via libraries).
  • Data Stack - The main repository for data the script will manipulate
  • Execution Stack - The list of commands to process
  • Dictionary - The dictionary holds the user defined words
  • Variables - Variables can store data - however variables are much slower to utilize than the Data Stack.

[edit] Overview

ForthMinus is a simple language with no explicit grammar. Everything is executed in a postfix (or RPN) method, where the arguments for an operator come before the operator itself. For example, take this simple LSL script of a math problem:

default
{
     state_entry()
     { 
         llSay(0,(string)((30 + 5) / 5));
     }
}

In ForthMinus, the functionaly similar code would be:

30 5 + 5 / 0 llsay
Exec. Stack Data Stack Description
30 5 + 5 / 0 llsay Initial state of program
5 + 5 / 0 llsay 30 Pushes the 30 to the top of the data stack
+ 5 / 0 llsay 30 5 Pushes the 5 to the top of the data stack
5 / 0 llsay 35 The word "+" adds the top two items on the stack and pushes the result (35)
/ 0 llsay 35 5 Pushes the 5 to the top of the data stack
0 llsay 7 The word "/" divides the second to top item by the top item in the data stack, and pushes the result
llsay 7 0 Pushes the 0 to the top of the data stack
llsay says the second top item of the data stack on the channel of the top item.

Another example is a familiar script:

default
{
     state_entry()
     {
          llSay(0,"Hello Avatar!");
     }

     touch_start(integer num)
     {
          llSay(0,"Touched!");
     }
}

Would be this in ForthMinus:

: @touched "Touched!" 0 llsay ;
"Hello Avatar!" 0 llsay

A bit of explanation is needed for that last script. The line ": @touched "Touched!" 0 llsay ;" is defining a new user-word "@touched" and adding it to the dictionary. The ":" word starts the definition of a new word. The first word following the ":" is the name of the new word to define (in this case, "@touched"). The words following is what the new word will do (in this case, pushes "Touched!" and 0 to the stack, and then pops them and says "Touched!" to channel 0 in world). The ";" indicates the end of the definition.

The word "@touched" is a special event word within ForthMinus. When an avatar touches a prim with a ForthMinus script in it, the engine checks to see if the script has "@touched" defined. If so, it pushes the word "@touched" to the bottom of the Execution Stack, thus not interrupting the current task of the script (this is similar to LSL - events are non-interrupting).

[edit] Quotations

The engine has a data type called a quotation. A quotation is signified by square brackets ("[","]"). A quotation holds a chunk of data or even code. Quotations can be stored and manipulated as any other data (e.g. stored in the data stack, assigned a variable name, etc.). A quotation can be executed by the the word qcall. For example:

[ "Hello World" . ] qcall

In this example, the quotation itself is placed onto the data stack. The word "qcall" pops a quotation from the stack, and executes what is inbetween the brackets.

Besides storing hunks of code, quotations can be utilized to mimic other data structures, such as lists, vectors, and rotations. For example:

[ 45.3 23.5 100.3 ] qcall llsetpos

This would execute the vector stored in the quotation, push'ing it's three values into the data stack. The llsetpos word would set the host's position in the sim.

There are words to construct common SL related quotations from the stack. For example:

llgetpos v>q curpos !
  • llgetpos pushes the prims current x, y, and z position onto the data stack
  • v>q pops the top three values of the stack and creates a quotation containing the x, y, z values ([ x y z ])
  • curpos ! stores the quotation into the variable curpos, hence, curpos = [ x y z ]

Quotations can be used in conditional statements:

0 [ "True" 0 llsay ] [ "False" 0 llsay ] qif

In this example:

  • 0 is pushed to the stack
  • The quotation [ "True" 0 llsay ] is pushed to the stack
  • The quotation [ "False" 0 llsay ] is pushed to the stack
  • qif pops three values off the stack. If the third deep is true, it qcall's the first quotation, otherwise, it qcall's the second. In this example, since 0 == false, the program will say "False" in world.

QIf statements can be layered by placing additional qif chunks within the quotation. For example:

3 dup 0 < [ "Negative" drop ] [ 0 > [ "Positive" ] [ "Zero" ] qif ] qif 0 llsay

This:

  • Pushes 3 into the data stack, then "dup" duplicates it and then pushes 0 to the stack (thus, or data stack is 3 3 0)
  • "<" pops two values (3 and 0 in this case), and if 3 < 0, pushes 1, otherwise pushes 0 (and in this case, of course it pushes 0, since 3 is not < 0)
  • The two quotations ([ "Negative" drop ] and [ 0 > [ "Positive" ] [ "Zero" ] qif ]) are pushed to the data stack.
  • The qif pops the truth value from the second step and the two quotations. Since the test was false, it qcall's the second hunk of code (0 > [ "Positive" ] [ "Zero" ] qif)
  • 0 is pushed, then ">" tests if the number is larger than 0, if so pushes "Positive", if not, pushes "Zero". In our example, it will push "Positive"
  • "0 llsay" will push 0, then say "Positive" on channel 0 in world.

There are other conditional and control words that utilize quotations:

1 [ "True!" 0 llsay ] qwhen

This will qcall the quotation when the previous item on the data stack is true.

10 [ "Hello World" 0 llsay ] qdo

This will execute the quotation 10 times.

Another nifty function that effects quotations is qforeach:

[ 1 2 3 4 ] [ 1 + ] qforeach

This will execute the second quotation on each element of the first, pushing the results. In this case, the stack would be "2 3 4 5".

Quotations can also be used to create custom user level words. The following two examples have the same end result:

: hello "Hello World!" 0 llsay ;
[ "Hello World!" 0 llsay ] hello qdef

[edit] Primitive Libraries

These are pre-defined words that are loaded into the engine when a script is created. The engine is capable of dynamically loading libraries, so a future option could be a user could load additional libraries within their script. There is also options to load pre-defined user-level (e.g. words written in ForthMinus) words into a script, allowing end-users to create a custom library of commonly used code.

[edit] LibCore

Word marked with "*" are primarily for testing purposes for sim owners and not end users.

Word Stack Description
dup ( n -- n n ) Duplicates the top item of the stack
. * ( n -- ) Prints the top item of the stack in console
dump * ( -- ) Dumps the VM's current status in console
swap ( n1 n2 -- n2 n1) Swaps the order of the top two items in the stack
savestate * ( n -- ) Saves the VM's state to file n.fmo
words * ( -- ) Lists the primitive words loaded to console
loadlib ( n -- ) Loads the words in n.dll
 : [;] Special Define a user word. The first word following the : in the estack is the name. All words following until ; is the definition of the word
savestack ( ... n -- ) Pops the entire stack into variable n
loadstack ( n -- ... ) Push's the stack saved in variable n
clearstack ( ... -- ) Clears the entire stack
if,[else],then ( n -- ) If n is true, execute words until else or then. If n is false, execute else to then
not ( n -- n ) If n is true, push false, if n is false, push true
> ( n1 n2 -- n ) If n1 > n2, push true else push false
< ( n1 n2 -- n) If n1 < n2, push true else push false
>= ( n1 n2 -- n ) If n1 >= n2, push true else push false
<= ( n1 n2 -- n) If n1 <= n2, push true else push false
= ( n1 n2 -- n ) If n1 == n2, push true else push false
 != ( n1 n2 -- n ) If n1 != n2, push true else push false (technically pushes "= not" to estack)
 ! ( n1 n2 -- ) Saves n1 to variable n2
@ ( n -- n ) Pushes variable n to stack

[edit] LibSLBase

Word Stack Description
llsay ( n1 n2 -- ) Says n1 on channel n2 in world
llwhisper ( n1 n2 -- ) Whispers n1 on channel n2 in world
llshout ( n1 n2 -- ) Shouts n1 on channel n2 in world
llgetpos ( -- x y z ) Pushes the position of the object in world
llsetpos ( x y z -- ) Sets the position of the object in world
llgetrot ( -- x y z s ) Pushes the rotation of the object to the stack
llsetrot ( x y z s -- ) Sets the rotation of the object
llgetscale ( -- x y z ) Pushes the scale of the object to stack
llsetscale ( x y z -- ) Sets the scale of the object

[edit] LibMath

Word Stack Description
+ ( n1 n2 -- n ) Adds n1 and n2 together. If n1 or n2 are strings, result will be a combined string
- ( n1 n2 -- n) Pushes n1 - n2
* ( n1 n2 -- n) Pushes n1 * n2
/ ( n1 n2 -- n ) Pushes n1 / n2
round ( n -- n ) Rounds n to nearest integer
sin ( n -- n ) Pushes the sin of n
cos ( n -- n ) Pushes the cos of n
tan ( n -- n ) Pushes the tan of n
pi ( -- n ) Pushes PI
abs ( n -- n ) Pushes the absolute value of n
sqrt ( n -- n) Pushes the square root of n
pow ( n1 n2 -- n) Pushes n1 to the n2'th power
floor ( n -- n ) Floors n and pushes it
vecmag ( x y z -- n) Pushes the vecmag of vector <x, y, z>
vecnorm ( x y z -- x y z ) Pushes the normalized vector for <x, y, z>
vecdist ( x1 y1 z1 x2 y2 z2 -- n ) Pushes the distance between <x1, y1, z1> and <x2, y2, z2>

[edit] Events

Events are handled by tucking words to the bottom of the execution stack as well as adding variables with the parameters of the event.

LSL Event Exec Word Variables
touch_start(integer num) @touched @touchednum @touchedname @touchedkey
listen(integer channel, string name, key id, string message) @listen @listench @listenname @listenkey @listenmsg
changed(integer change) @changed @changedwhat

[edit] Notes

drop ( x -- )
2drop ( x y -- )
3drop ( x y z -- )
nip ( x y -- y )
2nip ( x y z -- z )

Duplicating stack elements:

dup ( x -- x x )
2dup ( x y -- x y x y )
3dup ( x y z -- x y z x y z )
dupd ( x y -- x x y )
over ( x y -- x y x )
pick ( x y z -- x y z x )
tuck ( x y -- y x y )

Permuting stack elements:

swap ( x y -- y x )
swapd ( x y z -- y x z )
rot ( x y z -- y z x )
-rot ( x y z -- z x y )
roll ( x y z t -- y z t x )
-roll ( x y z t -- t x y z )
Personal tools
General
About This Wiki