-
Notifications
You must be signed in to change notification settings - Fork 11
Home
The compiler for Expression 3 is a huge work in progress and there for the syntax of this language is likely to change over the course of its development.
Expression 3 uses a language based on expression advanced 2 but is closer to c#, it is often described as a lua and c# mash up.
The expression 3 language contains directives that work almost the same way as they did in e2. All directives are prefixed with the @ character and should appear before any statements or expressions in your code. Unlike E2 directives can appear after comments.
This directive is used to set the name of the Gate the code has been uploaded to.
@name "Example - E3";
This directive is used to set the model of any newly created chip using this code.
@model "models/nezzkryptic/e3_chip.mdl";
This directive is used to include additonal code to your script. This can be very useful when creating libraries and user classes to be used across multiple gates.
@include "example_include";
The above example will include the script located at data/golem/example_include.txt
Just like in E2, expression 3 uses directives for creating wiremod inputs and outputs. These directives do not follow the usual rules and can appear any where in your code.
@input number A, B, C;
The above example creates 3 variables A, B, and C of type number, these variables will be updated when the input of the same name is changed.
@output number A, B, C;
The output directive is basically the same as the input directive how ever the wire output will be updated a triggered at the end of the execution with the value of the corresponding variable.
If either of these 2 directives appear inside a nested block (e.g if statement) the variables created will also be nested to that block.
Wired input and outputs are considered synced variables when defined in shared space. Wiremod inputs & outputs can not be defined client side and should always appear inside a server side code block when synchronisation is not necessary.
Because E3 can run both server & client side, variables defined on the server side are not accessible client side. To alleviate this the Synced directive has been added.
@synced number A, B, C;
Synced variables must only be defined in shared space and act as global variables. Synced variables can only be read client side and can only be assigned server side. Synced variables are synced every second the gate is running but synchronisation is not always guaranteed.
Synced variables use net messages and should be used sparingly.
Expression 3 support 2 styles of comments. Single line
//Example of a single line comment
Multi line
/* Example
of a multi line
comment */
In expression 3 a block of code is wrapped in curly brackets.
{
// Example
}
These brackets can be omitted causing only the first line to be used as the block.
An if statement is a conditional statement that will execute a block of code only under certain conditions.
if (Condition) block
elseif (Condition) block
else block`
if (false) {
debug.print(0)
} elseif (false) {
debug.print(1)
} else {
debug.print(2)
}
These statements will only execute a block of code based on if the code is running server side or client side.
server block:
server {
debug.print("server");
}
client block:
client {
debug.print("client");
}
You can also use @server or @client directives to define server side or client side only gates. These must be called at the top of your code and disable the use of server and client code blocks.
@server;
@client;
All assignment statements in expression 3 can be used to assign one or more values at once.#
When defining a variable it is important to note that it is nested to its current block.
class variable[, variable]* = expression[, expression]*;
number a = 7;
string first, last = "John", "Doe";
Same as a definition statement except defines a global variable, these variables are not nested to there current block.
global class variable[, variable]* = expression[, expression]*;
global number a = 7;
global string first, last = "John", "Doe";
Assignments are used to assign a value to an existing variable.
variable[, variable]* = expression[, expression]*;
a = 7;
first, last = "John", "Doe";
Arithmetic assignments are used to perform an arithmetic operation on a variable and then assign the result of that operation to the variable.
variable[, variable]* += expression[, expression]*;
exampleVar, anotherVar += 100, 200;
variable[, variable]* -= expression[, expression]*;
exampleVar, anotherVar -= 100, 200;
variable[, variable]* \= expression[, expression]*;
exampleVar, anotherVar \= 100, 200;
variable[, variable]* *= expression[, expression]*;
exampleVar, anotherVar *= 100, 200;
Expression 3 supports user defined functions.
function type variable([type parameter,]*) block
function int example(int a, int b) {
return a + b;
}
Delegates are function templates and are used by the compiler to retain information about a functions parameters as well as its return values. This will be covered in depth in a later section.
delegate type variable([type,]*) {
return number
}
delegate string example(string, string) {
return 1
}
The number that appears after return is the amount of values returned of type.
Expression 3 supports 2 types of inline functions as expressions.
function(int a, int b) { return a + b; }
(int a, int b) => { return a + b; }
These can be used to define a function with in a statment.
timer.Simple("test", 1, 1, () => { system.print("timer"); });
Expression ? Expression : Expression
Expression || Expression
Expression && Expression
Expression ^^ Expression
Expression | Expression
Expression & Expression
Expression & Expression
Expression == Expression
Expression != Expression
This is a nifty addition to expression 3 that allows you to compare if 1 value is equal to any of a list of values.
Expression == [[Expression,*]]
1 == [1, 2, 3]
Just like the previous this allows you to compare if 1 value not is equal to any of a list of values.
Expression != [[Expression,*]]
Expression > Expression
Expression < Expression
Expression >= Expression
Expression <= Expression
Expression << Expression
Expression >> Expression
Expression + Expression
Expression - Expression
Expression / Expression
Expression * Expression
Expression ^ Expression
Expression % Expression
+Expression
-Expression
!Expression
#Expression
The casting operator takes a class. The Compiler will then try to convert the value on the right to that class.
(Class) Expression
number i = (number) 101;
A Constructor is used to create an object.
new Class([expression,]*)
new vector.3d(1, 1, 1)
In expression 3 some objects can be called, user functions being the main one.
Value([Expression,]*)
//No good examples currently exist.
In expression 3 all functions exist with in a library.
Library.Function([Expression,]*)
debug.print(278)
Value.Method([Expression,]*)
"string".find("s")
Events are lambda based callbacks that are used to respond to events outside the main execution. Events work similar to the hook system in gmod lua.
event.add(string event name, string callback id, function callback )
event.add("Think", "example", function() { system.print("think") })
Similarly you can remove event call backs
event.remove(string event name, string callback id)
event.remove("Think", "example")
E3 provides a user class system, this means that anybody can create an object suited around their needs.
Lets look at an example of a user class.
class vec {
int x = 0;
int y = 0;
int z = 0;
vec(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
method vector toVector() {
return new vector(x, y, z);
}
method vec add(vec a) {
return new vec(x + a.x, y + a.y, z + a.z);
}
method vec sub(vec a) {
return new vec(x - a.x, y - a.y, z - a.z);
}
tostring() {
return "vec(" + x + "," + y + "," + z + ")";
}
}
We start with the class statement, which defines the classes name in our example it is 'vec'.
class className {
}
Next your notice we define some variables inside the classes main block of statements, variables defined here are actually known as attributes. These attributes will always default to the value they are defined, however each instance of a class is separate to that of other instances of the same class.
class myClass{
int var = value;
}
Our next statement is the constructor this is whats called when we need a new instance of that class e.g
new vec(1,2,3);
A constructor statement is always the name of the current class and then its parameters. A constructor will always contain the a variable called 'this' which is the new instance of the class and is the return value of the constructor.
You will also notice that here that attributes on a class can be accessed directly of the instance of the class.
int example = classObj.attribute
classObj.attribute = example;
This will also work outside of the class statement.
User classes use methods for functionality, A method is almost the same as a user function however just like a constructor it also has a variable called 'this' which is the instance of the class.
method returnType methodName() { //This is a method. }
E3 currently only supports 1 operator for user class as all other operations can be replicated using methods.
The tostring operator must return a string and is used when ever trying to concatenate your object or running a string conversion. This means that instead of creating an operator for addition or subtraction you would create a method to this this for you. If you look at the provided example, if we wanted to add two different vec objects together we would have to do
vec a = new vec(1,2,3);
vec b = new vec(4,5,6);
vec total = a.add(b);