24 KiB
Berry Language Reference for Tasmota
Note: this file is supposed to use as a reference manual for Generative AI in a compact form. For Claude AI it costs ~6k tokens.
Command Line Options
When running Berry from the command line in Tasmota, several options are available:
berry [options]
Available options:
-s: Enable strict mode for the Berry compiler-g: Force named globals in VM (required for solidification)-i: Enter interactive mode after executing script-l: Parse all variables in script as local-e: Load script source string and execute-m <path>: Custom module search path(s)-c <file>: Compile script file to bytecode-o <file>: Save bytecode to file-v: Show version information-h: Show help information
Common usage in Tasmota:
berry -s -g
This runs Berry with strict mode enabled and named globals, which is the recommended configuration for code that will be solidified.
Introduction
Berry is an ultra-lightweight, dynamically typed embedded scripting language designed for resource-constrained environments. The language primarily supports procedural programming, with additional support for object-oriented and functional programming paradigms. Berry's key design goal is to run efficiently on embedded devices with very limited memory, making the language highly streamlined while maintaining rich scripting capabilities.
Tasmota Integration: Berry is integrated into Tasmota firmware. For Tasmota-specific features, see the companion document BERRY_TASMOTA.md.
Basic Information
Comments
# This is a line comment
#- This is a
block comment
-#
Literals
Numerical Literals
40 # Integer literal
0x80 # Hexadecimal literal (integer)
3.14 # Real literal
1.1e-6 # Real literal with scientific notation
Boolean Literals
true # Boolean true
false # Boolean false
String Literals
'this is a string'
"this is a string"
String literals can be concatenated without operators:
s = "a" "b" "c" # s == "abc"
s = "a" # Multi-line strings
"b"
"c" # s == "abc"
Escape sequences:
\a- Bell\b- Backspace\f- Form feed\n- Newline\r- Carriage return\t- Horizontal tab\v- Vertical tab\\- Backslash\'- Single quote\"- Double quote\?- Question mark\0- Null character\ooo- Character represented by octal number\xhh- Character represented by hexadecimal number\uXXXX- Unicode character (UTF-8 encoded)
Nil Literal
nil # Represents no value
Identifiers
An identifier starts with an underscore or letter, followed by any combination of underscores, letters, or numbers. Berry is case-sensitive.
a
TestVariable
Test_Var
_init
baseClass
_
Keywords
if elif else while for def
end class break continue return true
false nil var do import as
try except raise static
Types and Variables
Built-in Types
Simple Types
- nil: Represents no value
- Integer: Signed integer (typically 32-bit)
- Real: Floating-point number (typically 32-bit)
- Boolean:
trueorfalse - String: Sequence of characters
- Function: First-class value that can be called
- Class: Template for instances
- Instance: Object constructed from a class
Class Types
- list: Ordered collection of elements
- map: Key-value pairs collection
- range: Integer range
- bytes: Byte buffer
Variables
Variables are dynamically typed in Berry. They can be defined in two ways:
# Direct assignment (creates variable if it doesn't exist)
a = 1
# Using the var keyword
var a # Defines a with nil value
var a = 1 # Defines a with value 1
var a, b # Defines multiple variables
var a = 1, b = 2 # Defines multiple variables with values
Scope and Lifecycle
Variables defined in the outermost block have global scope. Variables defined in inner blocks have local scope.
var i = 0 # Global scope
do
var j = 'str' # Local scope
print(i, j) # Both i and j are accessible
end
print(i) # Only i is accessible here
Expressions
Operators
Arithmetic Operators
-(unary): Negation+: Addition or string concatenation-: Subtraction*: Multiplication/: Division%: Modulo (remainder)
Relational Operators
<: Less than<=: Less than or equal to==: Equal to!=: Not equal to>=: Greater than or equal to>: Greater than
Logical Operators
&&: Logical AND (short-circuit)||: Logical OR (short-circuit)!: Logical NOT
Bitwise Operators
~: Bitwise NOT&: Bitwise AND|: Bitwise OR^: Bitwise XOR<<: Left shift>>: Right shift
Assignment Operators
=: Simple assignment+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=: Compound assignment:=: Walrus assignment (assigns and returns value)
Domain and Subscript Operators
.: Member access[]: Subscript access
Conditional Operator
? :: Ternary conditional
Concatenation Operators
+: String or list concatenation..: String concatenation or range creation
Operator Precedence (highest to lowest)
()(grouping)()(function call),[](subscript),.(member access)-(unary),!,~*,/,%+,-<<,>>&^|..<,<=,>,>===,!=&&||? :=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=:=
Statements
Expression Statements
a = 1 # Assignment statement
print(a) # Function call statement
Block
A block is a collection of statements. It defines a scope.
do
# This is a block
var a = 1
print(a)
end
Conditional Statements
# Simple if
if condition
# code executed if condition is true
end
# if-else
if condition
# code executed if condition is true
else
# code executed if condition is false
end
# if-elif-else
if condition1
# code executed if condition1 is true
elif condition2
# code executed if condition1 is false and condition2 is true
else
# code executed if both condition1 and condition2 are false
end
Iteration Statements
# while loop
while condition
# code executed repeatedly while condition is true
end
# for loop (iterating over a container)
for variable: expression
# code executed for each element in the container
end
# Examples
for i: 0..5
print(i) # Prints 0, 1, 2, 3, 4, 5
end
for item: ['a', 'b', 'c']
print(item) # Prints a, b, c
end
for key: map.keys()
print(key, map[key]) # Iterates over map keys
end
Jump Statements
# break - exits the loop
while true
if condition
break
end
end
# continue - skips to the next iteration
for i: 0..5
if i == 3
continue
end
print(i) # Prints 0, 1, 2, 4, 5
end
Import Statement
import math # Import math module
import hardware as hw # Import hardware module as hw
Exception Handling
# Raising exceptions
raise 'my_error' # Raise an exception
raise 'my_error', 'error message' # Raise with message
# Catching exceptions
try
# code that might raise an exception
except 'my_error'
# code executed if 'my_error' is raised
end
# Catching with exception variable
try
# code that might raise an exception
except 'my_error' as e
# e contains the exception value
end
# Catching with exception and message variables
try
# code that might raise an exception
except 'my_error' as e, msg
# e contains the exception value, msg contains the message
end
# Catching any exception
try
# code that might raise an exception
except ..
# code executed for any exception
end
# Catching multiple exception types
try
# code that might raise an exception
except 'error1', 'error2' as e, msg
# code executed if either 'error1' or 'error2' is raised
end
Functions
Function Definition
# Named function
def add(a, b)
return a + b
end
# Anonymous function
add = def (a, b)
return a + b
end
# Lambda expression (compact form)
add = / a, b -> a + b
Function with Variable Arguments
def print_all(a, b, *args)
print(a, b)
for arg: args
print(arg)
end
end
print_all(1, 2, 3, 4, 5) # args will be [3, 4, 5]
Calling Functions with Dynamic Arguments
def sum(a, b, c)
return a + b + c
end
call(sum, 1, 2, 3) # Calls sum(1, 2, 3)
call(sum, 1, [2, 3]) # Calls sum(1, 2, 3)
Closures
def counter(start)
var count = start
return def()
count += 1
return count
end
end
c = counter(0)
print(c()) # 1
print(c()) # 2
Object-Oriented Programming
Class Declaration
class Person
var name, age
def init(name, age)
self.name = name
self.age = age
end
def greet()
print("Hello, my name is", self.name)
end
end
Static Members
class MathUtils
static var PI = 3.14159
static def square(x)
return x * x
end
end
print(MathUtils.PI) # Access static variable
print(MathUtils.square(5)) # Call static method
Inheritance
class Student : Person
var school
def init(name, age, school)
super(self).init(name, age) # Call parent constructor
self.school = school
end
def greet()
super(self).greet() # Call parent method
print("I study at", self.school)
end
end
Instantiation and Method Calls
person = Person("John", 30)
person.greet() # Hello, my name is John
student = Student("Alice", 20, "University")
student.greet() # Hello, my name is Alice
# I study at University
Operator Overloading
class Vector
var x, y
def init(x, y)
self.x = x
self.y = y
end
def +(other)
return Vector(self.x + other.x, self.y + other.y)
end
def tostring()
return "Vector(" + str(self.x) + ", " + str(self.y) + ")"
end
end
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(v3) # Vector(4, 6)
Built-in Classes
List
# Creating lists
l1 = [] # Empty list
l2 = [1, 2, 3] # List with elements
l3 = list() # Empty list using constructor
l4 = list(1, 2, 3) # List with elements using constructor
# Accessing elements
l2[0] # First element (1)
l2[-1] # Last element (3)
# Range-based access (slicing)
l2[1..3] # Elements from index 1 to 3 inclusive [2, 3]
l2[1..] # Elements from index 1 to end [2, 3]
l2[1..-1] # Elements from index 1 to last element [2, 3]
l2[0..-2] # All elements except the last one [1, 2]
l2[-2..-1] # Last two elements [2, 3]
# Modifying lists
l2.push(4) # Add element to end
l2.pop() # Remove and return last element
l2.pop(1) # Remove and return element at index 1
l2.insert(1, 5) # Insert 5 at index 1
l2.remove(1) # Remove element at index 1
l2.resize(5) # Resize list to 5 elements (fills with nil)
l2.clear() # Remove all elements
# Negative indices for modification
l2[-1] = 10 # Set last element to 10
l2[-2] += 5 # Add 5 to second-to-last element
# Other operations
l2.size() # Number of elements
l2.concat() # Join elements as string (no separator)
l2.concat(", ") # Join elements with separator
l2.reverse() # Reverse the list
l2.copy() # Create a shallow copy
l2 + [4, 5] # Concatenate lists (new list)
l2 .. 4 # Append element (modifies list)
# Finding elements
l2.find(3) # Returns index of first occurrence or nil if not found
# Iterating over indices
for i: l2.keys() # Iterate over list indices
print(i, l2[i])
end
# Comparing lists
[1, 2] == [1, 2] # true
[1, 2] != [1, 3] # true
Map
# Creating maps
m1 = {} # Empty map
m2 = {"key": "value"} # Map with key-value pair
m3 = map() # Empty map using constructor
# Accessing elements
m2["key"] # Get value by key
m2.find("key") # Get value by key (returns nil if not found)
m2.find("key", "default") # Get value with default if not found
# Modifying maps
m2["new_key"] = "new_value" # Add or update key-value pair
m2.insert("key", "value") # Insert key-value pair (returns true if inserted, false if key exists)
m2.remove("key") # Remove key-value pair
# Other operations
m2.size() # Number of key-value pairs
m2.contains("key") # Check if key exists
for k: m2.keys() # Iterate over keys
print(k, m2[k])
end
Range
# Creating ranges
r1 = 0..5 # Range from 0 to 5 (inclusive)
r2 = range(0, 5) # Same using constructor
r3 = 10.. # Range from 10 to MAXINT
# Accessing properties
r1.lower() # Lower bound (0)
r1.upper() # Upper bound (5)
r1.incr() # Increment (default 1)
# Modifying ranges
r1.setrange(1, 6) # Change range bounds
r1.setrange(1, 10, 2) # Change range bounds and increment
# Using in for loops
for i: 0..5
print(i) # Prints 0, 1, 2, 3, 4, 5
end
String Operations
# String indexing and slicing
s = "hello"
s[0] # "h"
s[1] # "e"
s[-1] # "o" (last character)
s[1..3] # "ell" (characters from index 1 to 3)
s[1..] # "ello" (characters from index 1 to end)
s[1..-1] # "ello" (characters from index 1 to last)
s[0..-2] # "hell" (all characters except the last one)
Bytes
# Creating bytes objects
b1 = bytes() # Empty bytes
b2 = bytes("1122AA") # From hex string
b3 = bytes(10) # Pre-allocated 10 bytes
b4 = bytes(-8) # Fixed size 8 bytes
# Accessing bytes
b2[0] # First byte (0x11)
b2[1..2] # Bytes from index 1 to 2
# Modifying bytes
b2[0] = 0xFF # Set byte at index 0
b2.resize(10) # Resize buffer
b2.clear() # Clear all bytes
# Reading/writing structured data
b2.get(0, 2) # Read 2 bytes as unsigned int (little endian)
b2.get(0, -2) # Read 2 bytes as unsigned int (big endian)
b2.geti(0, 2) # Read 2 bytes as signed int
b2.set(0, 0x1234, 2) # Write 2-byte value
b2.add(0x1234, 2) # Append 2-byte value
# Conversion
b2.tohex() # Convert to hex string
b2.asstring() # Convert to raw string
b2.tob64() # Convert to base64 string
b2.fromhex("AABBCC") # Load from hex string
b2.fromstring("Hello") # Load from raw string
b2.fromb64("SGVsbG8=") # Load from base64 string
File
# Opening files
f = open("test.txt", "w") # Open for writing
f = open("test.txt", "r") # Open for reading
# Writing to files
f.write("Hello, world!") # Write string
f.write(bytes("AABBCC")) # Write bytes
# Reading from files
content = f.read() # Read entire file
line = f.readline() # Read one line
raw_data = f.readbytes() # Read as bytes
# File positioning
f.seek(10) # Move to position 10
pos = f.tell() # Get current position
size = f.size() # Get file size
# Closing files
f.flush() # Flush buffers
f.close() # Close file
Standard Libraries
String Module
import string
# String operations
string.count("hello", "l") # Count occurrences (2)
string.find("hello", "lo") # Find substring (3), returns -1 if not found
string.split("a,b,c", ",") # Split by separator (["a", "b", "c"])
string.split("hello", 2) # Split at position (["he", "llo"])
# Character operations
string.byte("A") # Get byte value (65)
string.char(65) # Get character from byte ('A')
# Case conversion
string.toupper("hello") # Convert to uppercase ("HELLO")
string.tolower("HELLO") # Convert to lowercase ("hello")
# String transformation
string.tr("hello", "el", "ip") # Replace characters ("hippo")
string.replace("hello", "ll", "xx") # Replace substring ("hexxo")
string.escape("hello\n") # Escape for C strings
# Checking
string.startswith("hello", "he") # Check prefix (true)
string.startswith("hello", "HE", true) # Case-insensitive check (true)
string.endswith("hello", "lo") # Check suffix (true)
string.endswith("hello", "LO", true) # Case-insensitive check (true)
# Formatting
string.format("Value: %d", 42) # Format string
format("Value: %.2f", 3.14159) # Format (global function)
f"Value: {x}" # f-string format
f"Value: {x:.2f}" # f-string with format specifier
f"{x=}" # f-string debug format
Math Module
import math
# Constants
math.pi # Pi (3.14159...)
math.inf # Infinity
math.nan # Not a Number
math.imin # Smallest possible integer
math.imax # Largest possible integer
# Basic functions
math.abs(-5) # Absolute value (5)
math.floor(3.7) # Round down (3)
math.ceil(3.2) # Round up (4)
math.round(3.5) # Round to nearest (4)
math.min(1, 2, 3) # Minimum value (1)
math.max(1, 2, 3) # Maximum value (3)
# Exponential and logarithmic
math.sqrt(16) # Square root (4)
math.pow(2, 3) # Power (8)
math.exp(1) # e^x (2.71828...)
math.log(2.71828) # Natural logarithm (1)
math.log10(100) # Base-10 logarithm (2)
# Trigonometric
math.sin(math.pi/2) # Sine (1)
math.cos(0) # Cosine (1)
math.tan(math.pi/4) # Tangent (1)
math.asin(1) # Arc sine (pi/2)
math.acos(1) # Arc cosine (0)
math.atan(1) # Arc tangent (pi/4)
math.atan2(1, 1) # Arc tangent of y/x (pi/4)
# Angle conversion
math.deg(math.pi) # Radians to degrees (180)
math.rad(180) # Degrees to radians (pi)
# Random numbers
math.srand(42) # Seed random generator
math.rand() # Random integer
# Special checks
math.isinf(math.inf) # Check if value is infinity (true)
math.isnan(math.nan) # Check if value is NaN (true)
JSON Module
import json
# Parsing JSON
data = json.load('{"name": "John", "age": 30}')
print(data.name) # John
# Error handling with json.load
data = json.load('{"invalid": }') # Returns nil on parsing error
if data == nil
print("Invalid JSON")
end
# Generating JSON
person = {
"name": "Alice",
"age": 25,
"hobbies": ["reading", "swimming"]
}
json_str = json.dump(person) # Compact JSON
json_formatted = json.dump(person, "format") # Formatted JSON
OS Module
import os
# Directory operations
os.getcwd() # Get current directory
os.chdir("/path/to/dir") # Change directory
os.mkdir("/path/to/new/dir") # Create directory
os.remove("/path/to/file") # Delete file or directory
os.listdir() # List current directory
os.listdir("/path") # List specific directory
# Path operations
os.path.isdir("/path") # Check if path is directory
os.path.isfile("/path/file.txt") # Check if path is file
os.path.exists("/path") # Check if path exists
os.path.split("/path/file.txt") # Split into ["/path", "file.txt"]
os.path.splitext("file.txt") # Split into ["file", ".txt"]
os.path.join("path", "file.txt") # Join into "path/file.txt"
# System operations
os.system("command") # Execute system command
os.exit() # Exit interpreter
Global Module
import global
# Accessing globals
global_vars = global() # List of all global variables
global.contains("var_name") # Check if global exists
value = global.var_name # Get global value
global.var_name = 42 # Set global value
value = global.("dynamic_name") # Dynamic access by name
Introspect Module
import introspect
# Inspecting objects
members = introspect.members(obj) # List of object members
value = introspect.get(obj, "attr") # Get attribute value
introspect.set(obj, "attr", value) # Set attribute value
name = introspect.name(obj) # Get object name
is_method = introspect.ismethod(fn) # Check if function is method
# Module operations
mod = introspect.module("math") # Import module dynamically
# Pointer operations (advanced)
ptr = introspect.toptr(addr) # Convert int to pointer
addr = introspect.fromptr(ptr) # Convert pointer to int
Error Handling
Standard Exceptions
assert_failed: Assertion failedindex_error: Index out of boundsio_error: IO malfunctionkey_error: Key errorruntime_error: VM runtime exceptionstop_iteration: End of iteratorsyntax_error: Syntax errorunrealized_error: Unrealized functiontype_error: Type error
Raising Exceptions
raise "my_error" # Raise exception
raise "my_error", "message" # Raise with message
Catching Exceptions
try
# Code that might raise an exception
except "my_error"
# Handle specific exception
end
try
# Code that might raise an exception
except "error1", "error2"
# Handle multiple exceptions
end
try
# Code that might raise an exception
except "my_error" as e
# e contains the exception value
end
try
# Code that might raise an exception
except "my_error" as e, msg
# e contains exception, msg contains message
end
try
# Code that might raise an exception
except ..
# Catch all exceptions
end
Error Handling Patterns
Many functions in Berry return nil to indicate errors rather than raising exceptions. This is common in functions that parse data or perform operations that might fail:
# JSON parsing
data = json.load('{"invalid": }') # Returns nil on parsing error
if data == nil
print("Invalid JSON")
end
# Map access
value = map.find("key") # Returns nil if key doesn't exist
if value == nil
print("Key not found")
end
# String operations
index = string.find("hello", "z") # Returns -1 if substring not found
if index == -1
print("Substring not found")
end
Assertions
assert(condition) # Raises assert_failed if condition is false
assert(condition, "message") # Raises with custom message
Best Practices
Variable Naming
- Use descriptive names for variables and functions
- Use camelCase or snake_case consistently
- Prefix private members with underscore (convention only)
Code Organization
- Group related functions and classes
- Use modules for logical separation
- Keep functions small and focused
Memory Management
- Be mindful of memory usage on embedded systems
- Release resources when no longer needed
- Use fixed-size buffers when appropriate
Error Handling
- Use exceptions for exceptional conditions
- Check return values for expected errors
- Provide meaningful error messages
Performance
- Avoid creating unnecessary objects
- Reuse buffers when processing large data
- Use native functions for performance-critical code