Contents¶
Overview¶
EveScript is a simple script language for event-based automation tasks.
from evescript.compiler import EveScriptCompiler
from evescript.executor import EveScriptExecutor
script = '''
if ($lightSensor > 20) {
say("It's daytime now!")
}
'''
def lightSensor():
return read_light_sensor_port()
compiler = EveScriptCompiler()
compiled_script = compiler.compile(script)
executor = EveScriptExecutor({
'actions': { 'say': lambda x: print(x) },
'variables': { '$lightSensor': lightSensor },
})
executor.run_script(compiled_script)
# Out: It's daytime now!
EveScript allows you to write simple event-based scripts that evaluate various conditions and execute actions. The conditions and actions are highly customizable to maximize the flexibility. EveScript can be used in embedded systems (such as Raspberry Pi) to implement a flexible event-based system.
Installation¶
pip install evescript
Usage¶
EveScript consists of a compiler EveScriptCompiler
and an executor EveScriptExecutor
.
Write a Script¶
An EveScript is written in the following form:
if (expression) {
action1(...)
action2(...)
}
where, the expression is a bool expression, and the actions are a list of “function” calls. The condition expression consists of variables, operators and constants (strings, numbers, and booleans).
Actions, variables, and operators are three types of entities that must be provided before the script can execute.
Here is a quick example (quickstart.es
) for you to start script with EveScript.
if ($lightSensor < 20) {
say("It's getting dark now!")
}
In this script, we used three entities that need to be provided when executing.
$lightSensor
: This is a variable. A variable is a custom function that is provided as a data source to the script.say
: This is an action. An action is a custom function that will be called when the expression is true.
We will provide these entities in the Run a script section.
For more details, see EveScript Language Reference.
Compile a Script¶
An EveScript file (*.es
) needs to be compiled before executed.
This is done by calling the compile()
method in the EveScriptCompiler
class.
from evescript.compiler import EveScriptCompiler
with open('quickstart.es') as f:
script = f.read()
compiler = EveScriptCompiler()
compiled_script = compiler.compile(f)
Run a Script¶
The compiled script can be executed with EveScriptExecutor
. When instancing EveScriptExecutor
,
the entities (actions, operators, and variables) used in the scripts must be provided. Each entity is a function or lambda.
from evescript.executor import EveScriptExecutor
ACTIONS = {
'say': lambda s: print(s),
}
VARIABLES = {
'$lightSensor': lambda x: 10,
}
# Provide the actions and the variables used in the script
executor = EveScriptExecutor({
'actions': ACTIONS,
'variables': VARIABLES,
})
# run the first trigger
executor.run_script(compiled_script)
Since we mocked the varialbe $lightSensor
to make it always returns 10, the action will be executed and will print It's getting dark now!
.
Decompile a Script¶
Sometimes it is necessary to retrieve the script source for given compiled script. This can be done with EveScriptDecompiler
.
It provides a decompile
method that takes a compiled script and returns the original script text.
NOTE: The decompiled script may not be identical to the original script - the whitespaces, new lines may differ, and the decompiled script will not contain any comments that may have appeared in the original script.
The following snippet shows how to decompile:
import os
import sys
from evescript.compiler import EveScriptCompiler
from evescript.decompiler import EveScriptDecompiler
script = '''
if ($lightSensor < 20) {
say("It's getting dark now!")
}
'''
compiler = EveScriptCompiler()
compiled_script = compiler.compile(script)
decompiler = EveScriptDecompiler()
decompiled_script = decompiler.decompile(compiled_script)
print(decompiled_script)
Reference¶
EveScript Language Reference¶
Basic Syntax¶
The basic form of an EveScript is as follows:
# if statement
if (expression) {
action1(...)
action2(...)
}
if (expression) {
...
}
# call action directly
action3()
An exmaple script may look like this:
if ($currentTime matchCron "0 0 * * *" && $lightSensor > 20 || $lightSensor < 10) {
say("Only run on midnight 00:00")
play("music.mp3")
}
# another condition
if ($currentTime matchCron "* * * * *") {
say("run every minute")
say(true)
}
In the above script, $currentTime
, $lightSensor
are called variables.
They are the data sources of this script. And the matchCron
, >
, ||
, <
are operators,
where matchCron
is a custom operator, while others are built-in operators. And the say
, play
are actions.
The if statement can be nested too.
# nested if
if (expr1) {
if (expr2) {
action()
}
}
You can also use if…else statement.
# if else statement
if (expr1) {
action1()
} else if (expr2) {
action2()
} else {
action3()
}
The following figure shows how a script is compiled and executed.

All variables, actions, and custom operators must be provided when executing this script. See Actions, Operators, and Variables for details.
Literals¶
EveScript supports the following types for literals:
Numbers: Only integers (
3
,10
,-4
) and decimals (0.6
,-2.4
) are supported. Scientific notation such as3e10
is NOT supported.String: A string must be quoted with double quotation marks:
"Hello, world"
. Single quotation mark is not supported.Boolean:
true
orfalse
.
Statements¶
Statements are similar to the if statements in other languages. It uses the C-style:
criteria must be placed in parenthesis
use
{ }
around the code block (i.e. actions)
Variables¶
Variables are part of the expressions in the statement.
When creating a EveScriptExecutor
, variables must be provided so that they can be used in the script.
Contrast to the usual concept of “variable” in other languages, variables in EveScript are NOT memory units for storing values. They are more like small functions that act as data sources, such as reading data from hardware I/O ports, reading the system clock, or fetching data from Internet.
When creating an executor, provide a dict as the variables
field of config, in which
keys are the variable names and the values are the functions that provide data.
The functions must take no parameters, and return a number, a string, or a boolean value.
The following code shows how to create a variable:
# Python code
def lightSensor():
# read the sensor value from the light sensor
return read_value_from_light_sensor()
# the key of the dict is the name of the variable
variables = {
'$lightSensor': lightSensor,
}
executor = EveScriptExecutor({
'variables': variables,
'actions': {},
'operators': {},
})
With this definition, the variable $lightSensor
is useable in the script.
# EveScript code
if ($lightSensor > 10) {
...
}
Operators¶
EveScript provides some built-in operators in order to construct expressions.
Operator |
Description |
---|---|
|
logical OR |
|
logical AND |
|
logical NOT |
|
equal to |
|
not equal to |
|
less than |
|
less than or equal to |
|
greater than |
|
greater than or equal to |
However, you can also define custom operators to implement your own logical operations. Similar to variables, custom operators are also small functions or lambdas that take two parameters (i.e. custom operators must be binary operators), and return a boolean value.
The following code snippet demonstrates how to create and use a custom operator:
# Python code
from datetime import datetime
from croniter import croniter
def matchCron(t, cron):
"""An operator that matches the provided time `t` with the `cron` string."""
return croniter.match(cron, t)
def currentTime():
"""A variable that returns current system time."""
return datetime.now()
executor = EveScriptExecutor({
'variables': { '$currentTime': currentTime },
'operators': { 'matchCron': matchCron },
'actions': {},
})
With this definition, $currentTime
and matchCron
can be used in the code to implement a crontab-like trigger:
# EveScript code
if ($currentTime matchCron "0 0 * * *") {
...
}
The following table lists the precedence of operators.
Precedence |
Operators |
---|---|
1 |
|
2 |
|
3 |
|
4 |
|
Actions¶
Actions are the functions listedn in the { }
block. They must be defined and provided when instantiating the EveScriptExecutor
.
An action function can take zero or more parameters, and has no return value.
Note there is no semicolon ;
at the end of each action.
The following code snippet shows how to define an action:
# Python code
def lightSensor():
# read the sensor value from the light sensor
return read_value_from_light_sensor()
def say(text):
"""Define an action `say` that prints a message on the console."""
print(text)
# the key of the dict is the name of the variable
variables = {
'$lightSensor': lightSensor,
}
executor = EveScriptExecutor({
'variables': { '$lightSensor': lightSensor },
'actions': { 'say': say },
'operators': {},
})
With this definition, say(text)
can be called in the scripts:
# EveScript code
if ($lightSensor > 10) {
say("It's daytime now!")
}
EBNF Definition¶
<script> ::= { <statement> }
<block> ::= "{" { <statement> } "}"
| <statement>
<statement> ::= <if_statement>
| <action>
<if_statement> ::= "if" "(" <expr> ")" <block> [ "else" <block> ]
<expr> ::= <term> "||" <expr>
| <term>
<term> ::= <factor> "&&" <term>
| <factor>
<factor> ::= "(" <expr> ")
| "!" <factor>
| <predicate>
<predicate> ::= <operand> <operator> <operand>
| <boolean>
<operator> ::= ">"
| ">="
| "<"
| "<="
| "=="
| "!="
| keyword
<operand> ::= variable
| <const>
<const> ::= string
| number
| <boolean>
<boolean> ::= 'true'
| 'false'
<action> ::= keyword "(" <params> ")"
<params> ::= <param> { "," <param> }
| empty
<param> ::= <operand>
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
Bug reports¶
When reporting a bug please include:
Your operating system name and version.
Any details about your local setup that might be helpful in troubleshooting.
Detailed steps to reproduce the bug.
Documentation improvements¶
EveScript could always use more documentation, whether as part of the official EveScript docs, in docstrings, or even on the web in blog posts, articles, and such.
Feature requests and feedback¶
The best way to send feedback is to file an issue at https://github.com/charlee/evescript/issues.
If you are proposing a feature:
Explain in detail how it would work.
Keep the scope as narrow as possible, to make it easier to implement.
Remember that this is a volunteer-driven project, and that code contributions are welcome :)
Development¶
To set up evescript for local development:
Fork evescript (look for the “Fork” button).
Clone your fork locally:
git clone git@github.com:YOURGITHUBNAME/evescript.git
Create a branch for local development:
git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes run all the checks and docs builder with tox one command:
tox
Commit your changes and push your branch to GitHub:
git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature
Submit a pull request through the GitHub website.
Pull Request Guidelines¶
If you need some code review or feedback while you’re developing the code just make the pull request.
For merging, you should:
Include passing tests (run
tox
) 1.Update documentation when there’s new API, functionality etc.
Add a note to
CHANGELOG.rst
about the changes.Add yourself to
AUTHORS.rst
.
- 1
If you don’t have all the necessary python versions available locally you can rely on Travis - it will run the tests for each change you add in the pull request.
It will be slower though …
Tips¶
To run a subset of tests:
tox -e envname -- pytest -k test_myfeature
To run all the test environments in parallel:
tox -p auto
Authors¶
Charlee Li - https://charlee.li
Changelog¶
0.5.0 (2021-03-29)¶
Support if…else statement.
Support executing actions outside if statement.
Support nested if statement.
Support actions with no params.
0.4.0 (2021-03-27)¶
Added support for using true/false as conditions.
0.3.0 (2021-03-20)¶
Fixed typo (EveScriptExector => EveScriptExecutor)
0.2.1 (2021-03-20)¶
Added decompiler.
Compiler will not rename built-in operators.
0.1.1 (2021-03-19)¶
First release on PyPI.