-
Notifications
You must be signed in to change notification settings - Fork 340
Expression 2 Syntax
Expression 2's syntax will take some time to get used to. It has similar syntax to Typescript, but blends elements from other languages like C. Expression 2 is it's own custom language.
In Expression 2 conditionals, numbers are considered false if they equal 0
. Otherwise, they are considered true.
Functions and methods which conceptually return "true" or "false" return 1
and 0
respectively.
- Variables
- Scopes (Conditionals)
- Loops
- Preprocessor (new)
- Functions (new)
- Include
- Exception Handling (new)
- Events (new)
- Operators
- Advanced Syntax
In Expression 2, all variables must start with a capital letter.
variable = 5 # COMPILE ERROR! Must begin with a capital letter
Variable = "test" # VALID
There are 4 "base" types in E2. These are:
-
number
- The number type. This also represents truthiness, a "truthy" number being anything but 0. -
string
- A simple string delimited by "". They can be multiline. -
table
- A typed table that can have arbitrary string and number keys. Essentially aDictionary
in most other languages. -
array
- A sequential list of items. Cannot contain other arrays or tables. Essentially aVector
orList
in most other languages.
There are other types, but they are not essential for all uses (e.g. vector
)
Unlike dynamic programming languages, E2 is typed, so you cannot set variables to different types.
# COMPILE ERROR!
Variable = 55
Variable = "Fifty Five"
# VALID
MyList = array(1, 2, 3, 4)
MyList = array(5, 6, 7, 8)
You may define variables with the local
keyword before it, like so:
if (1) {
local MyVar = 55
}
print(MyVar) # COMPILE ERROR! MyVar is only available inside the 'if' scope
local
keeps the variable inside of the scope it is located in.
For more about scopes, read the next section.
Scopes are what limit variables to exist only in a block of code (often delimited by curly braces { <CODE_HERE> }
).
The "global" scope is the topmost scope outside of any sort of blocks. This is where @persist
, @inputs
and @outputs
variables are stored (so they can be accessed anywhere in the code).
An if
statement allows you to only run a block of code if a condition is met (variable passed is not 0)
if (1) {
print("This will run")
}
You can use elseif
to chain conditions.
if (0) {
...
} elseif (2) {
print("This will run since (0) does not meet conditions and (2) does.")
}
To handle no conditions being met, use else
.
if (0) {
print("This will not run since (0) is not truthy")
} else {
print("This will always run")
}
A switch
statement is essentially just an if
chain. It does have other features, such as fallthrough. This is where if you don't add the "break" under each case, it will run every case under it (even if the condition isn't met) until it finds a break or the end of the switch.
Note: Avoid calling a function inside the switch()
itself, as it would be called once for each condition until a match is found. Instead you should cache the result in a variable.
switch (Var) {
case 2, # if Var == 2
print("Var is 2")
break
case 3, # elseif Var == 3
print("Var is 3")
break
default, # else
print("Var is not 2 or 3")
}
Loops are special scopes that repeat themselves. They run at a certain condition, through each element of a list, and can exit/skip iterations.
There are many ways to loop something in Expression 2.
This will call the code 5 times (1 - 5 inclusive) with an optional (step) parameter being the third number
for (I = 1, 5, 1) {
...
}
This will call the code in the block while the condition is met.
while (perf(80)) {
...
}
do while
loop (new)
This loop is the same as a while loop, except it will run once, then check if the condition is met at the end, to see whether to exit the loop or repeat.
do {
...
} while (perf(80))
You can loop through all the values of an array/table with foreach.
foreach(K, V:entity = findToArray()) {
...
}
You often will / should annotate the type of the keys to iterate through a table.
By default foreach on a table will iterate string keys. So if you want to iterate numbers, annotate with :number
foreach(K:string, V:number = MyTable) {
...
}
You can use the break
keyword to exit a loop prematurely.
foreach(K, Ply:entity = players()) {
if (Ply:distance(entity()) < 400) {
break
}
# Haven't found a player closer than 400 units yet
}
You can use continue
to skip an execution of a loop.
for (I = 1, 5) {
if (I % 2 == 0) {continue} # Skip even numbers (You should use the for loop step parameter instead, though).
}
Expression 2 has a preprocessor that allows you to write comments, and tell if functions from E2 Extensions exist at compile time to avoid errors.
You can write single line comments. (Everything after a # will not run code)
print("Hello World!") # Prints out "Hello world"!
And multiline comments (everything inside #[]# will not be run)
#[
My hello world E2
]#
print("Hello World")
Directives are what explain what should be changed about the E2 at compile time.
They are in this format:
@<NAME> <ARGS>
# Where <ARGS> is usually a whitespace delimited list of variables and their types. Like so:
@persist A:number B:string [C D E]:entity
You can use #ifdef
, #ifndef
, and #else
preprocessor commands to define what code to run in case a function exists, or does not exist.
#ifdef propSpawn(svan)
print("You have propcore!")
#else
error("This E2 requires propcore!")
#endif
or
#ifndef print(...)
error("E2 is broken..")
#endif
Misc (new)
Additionally, commands #error
and #warning
exist for you to give a compile time error or warning. This is intended for library creators, in conjunction with the #if(n)def
commands. For example:
#ifndef propSpawn(svan)
#error This chip cannot run without propcore! Enable the `propcore` extension!
#endif
#ifndef superCoolFunction()
#warning This E2 won't be as cool without superCoolFunction()
#endif
Functions are ways to organize your code-flow.
function helloWorld() {
print("Hello world!")
}
helloWorld()
- Note parameters are by default numbers, unless you give a type like
<NAME>:<TYPE>
function hello(Text: string) {
print("Hello ", Text)
}
hello("World!")
Variadic Parameters (new)
You can use variadic parameters to be able to call functions as if wrapped by a table() or array() function.
local MSG_PREFIX = array(vec(0, 172, 0), "[neat] ", vec(255))
function void printMessage(...Args:array) {
printColor(MSG_PREFIX:add(Args))
}
printMessage("Cool! Here's a number: ", randint(1, 27), ".")
Prints hello with the name of the entity The value from the : type is stored in a variable named "This".
function entity:hello() {
print("Hello", This:name())
}
Call them like so:
entity():hello()
You can return a value from your functions using the return
keyword.
Note you need to explicitly say the type the function will return (Note the "number" between "function" and the function name (multiply))
function number multiply(X, Y:number) {
return X * Y
}
You can call functions dynamically with string calls.
This is basically E2's version of delegates/lambdas until lambdas are implemented, and should be avoided at all costs as they are expensive and hacky.
local F = "print"
F("Hello world!") # Same as print("Hello world!")
local X = "health"
local OwnerHealth = X(owner())[number] # Same as owner():health()
The #include
syntax is similar to #include
in C and C++.
Given a file, it will run the E2 code in that file at the #include
.
Note, since E2 runs the whole chip every execution, this should also be placed inside of an if (first())
statement.
Note, despite looking like a preprocessor command, this has nothing to do with the preprocessor. It is parsed like a normal statement.
# main.txt
if (first()) {
#include "mylibs/hololib" # <- leave .txt omitted
# Now you can use functions and global variables from the "hololib" file.
doStuff()
}
# mylibs/hololib.txt
function doStuff() {
print("Hello world!")
}
Exception Handling (new)
Expression 2 does not often throw errors, instead, by default, it will often silently fail and return a default value on function calls. If you turn on @strict
, these will be thrown as proper runtime errors. To handle these, you need to use try & catch.
try { # < -- If an error occurs in this block, it will exit the block and call the catch {} block, passing the error message string.
error("Unexpected error")
} catch(E) { # < -- Can rename E to anything. It just stores the error message.
assert(E == "Unexpected error")
}
This statement allows you to catch runtime errors and handle them. Note this does NOT cover internal lua errors caused by E2 Extensions erroring.
Events (new)
Expression 2 inherited an awful "clk" system from E1, based on re-running the code and essentially checking a variable to see what caused it to run, in order to scope events. This has caused so much confusion and headaches, that I've created the event
system in order to replace it.
event chat(Ply:entity, Said:string, Team:number) {
print(Ply, " just said ", Said)
}
Owner = owner()
event keyPressed(Ply:entity, Key:string, Down:number, Bind:string) {
if (Ply == Owner & !Down) {
print("Owner released key " + Key)
}
}
Here are the currently supported events and their parameters. Note that autocomplete should work when typing event
.
-
event tick()
(replacingrunOnTick
) -
event chat(Player:entity, Message:string, Team:number)
(replacingrunOnChat
) -
event keyPressed(Player:entity, Key:string, Down:number, Bind:string)
(replacingrunOnKey
) -
event input(Input:string)
(replacinginputClk
,inputClkName
,~Input
) -
event chipUsed(Player:entity)
(replacingrunOnUse
) -
event removed(Resetting:number)
(replacingrunOnLast
) -
event playerSpawn(Player:entity)
(replacingrunOnSpawn
) -
event playerDeath(Victim:entity, Inflictor:entity, Attacker:entity)
(replacingrunOnDeath
) -
event playerConnected(Player:entity)
(replacingrunOnPlayerConnect
) -
event playerDisconnected(Player:entity)
(replacingrunOnPlayerDisconnect
) -
event httpErrored(Error:string, Url:string)
(replacingrunOnHttp
) -
event httpLoaded(Body:string, Size:number, Url:string)
(replacingrunOnHttp
) -
event fileErrored(File:string, Error:number)
(replacingrunOnFile
) -
event fileLoaded(File:string, Data:string)
(replacingrunOnFile
)
E2 supports the following operators in the listed priority (highest priority first):
- Increment (
Var++
) / Decrement (Var--
) - Literals (hardcoded numbers/strings) / Trigger (
~Input
) / Delta ($Var
) / Connected wires (->InOut
) - Parenthesis (
(...)
) / Function calls - Method calls (
Var:method(...)
) / Indexing (Var[Index, type]
) - Unary positivation & negation (
(+Var)
,(-Var)
, but notVar+Var
) / Logical NOT!Var
- Exponentiation (
A ^ B
) - Multiplication (
A * B
) / Division (A / B
) / Modulo (A % B
, remainder of division) - Addition (
A + B
) / Subtraction (A - B
) - Binary Left Shift (
A << B
, equivalent toA * (2^B)
) / Binary Right Shift (A << B
, equivalent tofloor(A / (2^B))
) - Comparisons (
A > B
,A < B
,A >= B
,A <= B
) - Equal (
A == B
) / Not Equal (A != B
) - Binary XOR (
A ^^ B
) -
Binary AND (
A && B
). Note that this is reversed with the "classical"&
operator -
Binary OR (
A || B
). Note that this is reversed with the "classical"|
operator -
Logical AND (
A & B
). Note that this is reversed with the "classical"&&
operator -
Logical OR (
A | B
). Note that this is reversed with the "classical"||
operator - Ternary (
A ? B : C
andX ?: Y
, which is equivalent toX ? X : Y
) - Assignment (
A = B
) and some in-place arithmetic (A += B
,A -= B
,A *= B
,A /= B
)
Note that many of the operators function for other types, ie vector arithmetic with +-*/
(component-wise), concatenating strings with +
(but you should prefer format
or concat
for most operations).
Some of these operators are explained in more detail below:
You can think of ternary as an inline if
. You use it to assign or use different variables based on a condition
# Var is set to "Pick me", since 1 is truthy. Otherwise it'd be set to "Will never be picked :(".
Var = 1 ? "Pick me" : "Will never be picked :("
# You can also use :? if the second argument is used as a condition. In this case, "Pick me" is truthy, so it will be the value of Nullish.
Nullish = "Pick me" :? "Will not be picked"
This is not to be confused with binary AND, which E2 uses as &&
.
X = 1
Y = 0
if (X & Y) {
# This will not run, as both X and Y are not truthy (Y is 0).
}
This is not to be confused with binary OR, which E2 uses as ||
.
X = 1
Y = 0
if (X | Y) {
# This will run, since X is truthy, and the OR needs either X or Y to be truthy.
}
This will return a falsey value, if given a truthy value, and vice versa.
if (!0) {
# This will run, because the inverse of a falsy value is a truthy one.
}
Must precede an @input variable
Returns 1 if the current execution was caused by a change in the input variable. See the E2 Triggers tutorial
USE THIS INSTEAD OF changed()
if (~Button) {
print("Button has changed to " + Button)
}
Must precede an @input or @output or @persist variable
Returns the difference/delta of a variable between executions. See the E2 Triggers tutorial
USE THIS INSTEAD OF changed()
local ScrollChange = $Scroll
Must precede an @input or @output variable
If used on an input, returns 1 if it's wired to something. If used on an output, returns the number of wires to the output.
USE THIS INSTEAD OF changed()
if (->Digiscreen & Digiscreen) {
print("Digital screen wired.")
}
Get or Set the given index at a table-like object (array, table, gtable, etc). As E2 always needs to know the type of everything ahead of time, you need to provide a type when indexing an object that can contain different types (table, array, gtable):
local Index = 55
Table[Index, string] = "Foo"
assert("Foo" == Table[Index, string])
Some types can be indexed without type, because they always contain the same type, like strings (characters as strings) and vectors (coordinates as numbers).
# Get the third character of a string (equivalent to String:index(3))
print(String[3])
# Get the z part of a vector, equivalent to Vector:z(),
# :z() is more efficient, but there are some niche applications for indexing in this way.
print(Vector[3])
When indexing wirelink
without type, you access the highspeed memory of that device, when availiable.
@inputs Digiscreen:wirelink
# Set the color mode of a digital screen. This is equvialent to `Digiscreen:writeCell(1048569, 3)`
Digiscreen[1048569] = 3
Please do not alter the e2 docs ...
pages manually.
They are autogenerated from the E2Helper. In the future, this will hopefully be its own dedicated website or tool.
Basic Features: core, debug, number, selfaware,
string, timer
🌎 World: angle, color, find, ranger, sound,
🔣 Math: bitwise, complex, matrix, quaternion, vector, vector2/4
📦 Entities: bone, constraint, egp, entity, hologram, npc
👨 Players: chat, console, player, weapon
📊 Data storage: array, files, globalvars, serialization, table
💬 Communication: datasignal, http, signal, wirelink,
❓ Informational: gametick, serverinfo, steamidconv, unitconv
Disabled by default: constraintcore, effects, propcore, remoteupload, wiring
Wire-Extras (repo): camera, ftrace, holoanim, light, stcontrol, tracesystem
Expression 2 ⚙️
- Syntax 🔣
- Directives 🎛️
- Editor 🖥️
- Ops 📊
- Learning & Getting Help 📚
- Triggers ⏲️
- Events 🎬
- Find Functions 🔍
- Physics 🚀
- EGP Basics 📈
- Lambdas λ
- Lambda Timers λ⏲️
- Tips & Tricks 📘
Click To Expand
- 🟥 SPU
- 🟥 Address Bus
- 🟥 Extended Bus
- 🟥 Plug/Socket
- 🟥 Port
- 🟥 Transfer Bus
- 🟩 GPU
- 🟥 Dynamic Memory
- 🟥 Flash EEPROM
- 🟥 ROM
- 🟧 Beacon Sensor
- 🟧 Locator
- 🟧 Target Finder
- 🟧 Waypoint
- 🟥 XYZ Beacon
- 🟩 CPU
- 🟩 Expression 2
- 🟩 Gates
- 🟥 PID
- 🟧 CD Disk
- 🟥 CD Ray
- 🟧 DHDD
- 🟥 Keycard
- 🟥 RAM-card
- 🟧 Satellite Dish
- 🟧 Store
- 🟧 Transferer
- 🟥 Wired Wirer
- 🟧 Adv Entity Marker
- 🟧 Damage Detector
- 🟧 Entity Marker
- 🟧 GPS
- 🟧 Gyroscope
- 🟥 HighSpeed Ranger
- 🟧 Laser Pointer Receiver
- 🟥 Microphone
- 🟧 Ranger
- 🟧 Speedometer
- 🟧 Water Sensor
- 🟧 7 Segment Display
- 🟥 Adv. Hud Indicator
- 🟧 Console Screen
- 🟧 Control Panel
- 🟧 Digital Screen
- 🟧 EGP v3
- 🟧 Fix RenderTargets
- 🟥 GPULib Switcher
- 🟧 Hud Indicator
- 🟧 Indicator
- 🟧 Lamp
- 🟧 Light
- 🟧 Oscilloscope
- 🟧 Pixel
- 🟧 Screen
- 🟧 Sound Emitter
- 🟧 Text Screen
- 🟩 Cam Controller
- 🟧 Colorer
- 🟧 FX Emitter
- 🟧 HighSpeed Holoemitter
- 🟧 HoloEmitter
- 🟧 HoloGrid
- 🟥 Interactable Holography Emitter
- 🟥 Materializer
- 🟥 Painter
- 🟧 Adv. Input
- 🟧 Button
- 🟧 Constant Value
- 🟥 Door Controller
- 🟧 Dual Input
- 🟧 Dynamic Button
- 🟧 Eye Pod
- 🟧 Graphics Tablet
- 🟧 Keyboard
- 🟥 Lever
- 🟧 Numpad
- 🟧 Numpad Input
- 🟧 Numpad Output
- 🟧 Plug
- 🟧 Pod Controller
- 🟧 Radio
- 🟧 Relay
- 🟧 Text Receiver
- 🟧 Two-way Radio
- 🟧 Vehicle Controller
- 🟥 Door
- 🟥 Adv. Dupe. Teleporter
- 🟥 Buoyancy
- 🟧 Clutch
- 🟧 Detonator
- 🟧 Explosives
- 🟧 Explosives (Simple)
- 🟥 Forcer
- 🟩 Freezer
- 🟧 Gimbal (Facer)
- 🟧 Grabber
- 🟧 Hoverball
- 🟧 Hoverdrive Controller
- 🟥 Hydraulic
- 🟧 Igniter
- 🟧 Nailer
- 🟩 Prop Spawner
- 🟥 Servo
- 🟥 Simple Servo
- 🟧 Thruster
- 🟥 Touchplate
- 🟥 Trail
- 🟩 Turret
- 🟩 User
- 🟥 Vector Thruster
- 🟥 Vehicle Exit Point
- 🟧 Weight (Adjustable)
- 🟧 Weld/Constraint Latch
- 🟥 Wheel
- 🟥 Wire Magnet
- 🟥 Wired Npc Controller
- 🟧 Debugger
- 🟥 GUI Wiring
- 🟥 Multi Wire
- 🟧 Namer
- 🟥 Simulate Data
- 🟩 Wiring
- 🟥 Beam Reader
- 🟥 Implanter
- 🟥 Reader
- 🟥 Target Filter
- 🟥 User Reader
Gates 🚥
Click To Expand
TBD