Chapter 1 - Introduction
Welcome to the Ena book! This book serves as a simple introduction to the Ena Programming Language.
Disclaimer
Ena is not complete yet. This means that Ena is not a mature language you would want to use for something serious. However, if you wanted to try something new, Ena is for you.
How is Ena different?
Unlike many other languages, Ena is a stack-based language. If you have worked with languages like Forth, you may find many Ena’s concepts similar to those in Forth.
Installing Ena
As of now, Ena does not provide any pre-built binaries. You will have to build it on your own.
The simplest way is to install Rust and then compile and install Ena:
$ cargo install enalang
You can also compile and install Ena from source:
$ git clone https://github.com/kislball/enalang
$ cd enalang
$ cargo install --path ./enalang
Hello, world!
The following code snippet describes a program which prints out a “Hello, world!” message.
# Comments are written using a hash sign
# Each Ena program is composed of blocks. Blocks are similar to functions, except that they do not accept
# any arguments.
# By default, Ena's VM uses `main` as the name for main block.
main {
"Hello, world!" # Literals put values on stack. Blocks usually operate on stack.
println # println prints the top value on stack, converts it into a string and prints it
}
Without comments:
main {
"Hello, world!" println
}
Running
Ena uses a VM to run code. The VM cannot read Ena code directly; it reads an intermediate representation (IR) from Ena’s compiler.
First, compile your source code to IR:
$ ena compile ./hello_world.ena -o hello_world.enair
Next, compile the standard library (you only need to do this once):
$ ena compile "std/*.ena" -o std.enair
Link your program with the standard library:
$ ena link hello_world.enair std.enair -o executable.enair
Finally, run your program:
$ ena run ./executable.enair
You should now see a “Hello, world!” message.
For convenience, you can create a shell script to automate this process. The repository includes a run_example.sh script:
#!/bin/sh
# Usage: ./run_example.sh <filename_without_extension>
ena compile "std/*.ena" -o ./std.enair
ena compile ./examples/$1.ena -o ./main.enair
ena link ./main.enair ./std.enair -o output.enair
ena run ./output.enair
rm output.enair main.enair std.enair
What’s next?
In the next chapter, we will look into more details about coding in Ena.
Chapter 2 - Basic Ena syntax
Literals
As stated in the first chapter, all literals in Ena code put themselves on top of stack.
about_literals {
"Hello, world!" # strings
42069 # numbers
true false # boolean
null # null value
'about_literals # puts block, making it similar to a closure
:atom # values which only equal to themselves
}
Warning: you should NEVER put local blocks on the stack. This is considered unsafe and WILL result in runtime errors.
Blocks
A basic unit of any Ena program is a block.
# A block which is executed every time it is called
main {
"hi"
}
# A block which is only executed once. After that, the top value is cached and will always be put on top of stack after execution.
random (
ena.vm.random
)
# Now random will always output the same value.
main {
random ena.vm.debug
random ena.vm.debug
# prints out the same value
}
Block name can consist of any characters. However, ID cannot begin with a digit, quote(single or double), colon or a whitespace.
Keywords
| Keyword | Meaning |
|---|---|
if | Executes the block after it, if the top value on stack is equal to true. |
while | Executes the block after it as long as the block pushes true onto the stack. |
return | Leaves the current global block. |
return_local | Leaves the current local block. |
Keyword Examples
# if example - note comparisons work in reverse
check_value {
3 5 > if { # Tests 5 > 3: true
"5 is greater than 3" println
}
}
# while example
count {
i ( unit )
0 i =
# while executes the block and expects a boolean on stack
5 i @ > while {
i @ println
i @ 1 + i =
5 i @ > # Push condition for next iteration
}
}
# return example
early_exit {
condition if {
"Exiting early" println
return
}
"This won't execute if condition is true" println
}
Stack Operations
Ena provides several built-in operations for stack manipulation:
dup: Duplicates the top value on stackswap: Swaps the top two valuesdrop: Removes the top valueclear: Clears the entire stack
stack_operations {
5 # Stack: [5]
dup # Stack: [5, 5]
3 # Stack: [5, 5, 3]
swap # Stack: [5, 3, 5]
drop # Stack: [5, 3]
}
Memory and Variables
Ena uses a memory model with explicit allocation:
variables {
# Allocate one unit of memory for a variable
x ( unit )
# Store value at memory location
42 x =
# Load value from memory location
x @ println # Prints: 42
}
Operators
=: Store value at memory location (pops value and pointer)@: Load value from memory location (pops pointer, pushes value)units: Allocate n units of memoryunit: Allocate single unit (equivalent to1 units)
Arithmetic Operations
Standard arithmetic operations work on stack values:
arithmetic {
5 3 + # Addition: 8
2 10 - # Subtraction: 8 (10 - 2, pops in reverse)
4 3 * # Multiplication: 12
3 15 / # Division: 5 (15 / 3, pops in reverse)
2 8 pow # Power: 256 (8^2, pops in reverse)
2 4 root # Root: 2 (4^(1/2), pops in reverse)
}
Note: Operations pop values in reverse order (rightmost operand is popped first). For example, 2 10 - pops 10 first, then 2, resulting in 10 - 2 = 8.
Comparison Operations
Note: Comparison operators work in reverse of typical expectations. a b > tests if b > a (the second value is greater than the first).
comparisons {
5 3 > # Tests 3 > 5: false
3 5 > # Tests 5 > 3: true
5 5 == # Equal: true
3 5 >= # Tests 5 >= 3: true
5 3 <= # Tests 3 <= 5: true
}
Boolean Operations
boolean_ops {
true false and # Logical AND: false
true false or # Logical OR: true
true ! # Logical NOT: false
}
Block Calls
Blocks can call other blocks by name:
helper {
"Helper called" println
}
main {
helper # Calls helper block
}
Escaped Blocks
Escaped blocks (using single quote) put the block reference on stack:
dynamic_call {
'helper call # Dynamically calls the helper block
}
helper {
"Called dynamically" println
}
Chapter 3 - Control Flow
Control flow in Ena is managed through a combination of keywords, stack manipulation, and block calls. This chapter explores the various control flow mechanisms available in the language.
Conditional Execution with if
The if keyword executes a block when the top value on the stack is true.
check_positive {
n ( unit )
n =
0 n @ > if { # a b > tests b > a, so 0 n @ > tests n > 0
"Number is positive" println
return
}
"Number is not positive" println
}
main {
5 check_positive
-3 check_positive
}
Important: The if keyword pops the boolean value from the stack before executing the block.
Loops with while
The while keyword repeatedly executes a block as long as the condition evaluates to true. The block must push a boolean value onto the stack at the end of each iteration.
A common pattern is to use true while with conditional returns:
count_to_five {
i ( unit )
0 i =
true while {
i @ 5 == if {
return
}
i @ print " " print
i @ 1 + i =
true
}
}
main {
count_to_five
}
Output: 0 1 2 3 4
Loop Control
Ena provides two special operations for loop control:
continue: Skips to the next iteration (implemented astrue return_local)break: Exits the loop (implemented asfalse return_local)
skip_threes {
i ( unit )
0 i =
true while {
i @ 10 == if {
return
}
# Skip value 3
i @ 3 == if {
i @ 1 + i =
true
continue
}
i @ print " " print
i @ 1 + i =
true
}
}
Output: 0 1 2 4 5 6 7 8 9
Early Returns
Global Return with return
The return keyword exits the current global block entirely:
find_value {
i ( unit )
0 i =
true while {
i @ 100 == if {
"Not found\n" print
return
}
i @ 42 == if {
"Found 42!\n" print
return
}
i @ 1 + i =
true
}
}
Local Return with return_local
The return_local keyword exits only the innermost local block (enclosed in parentheses):
example {
value (
condition if {
special_value return_local
}
default_value
)
value @ println
}
Pattern: Condition Blocks
A common pattern in Ena is to evaluate conditions in your loop body:
process_items {
i ( unit )
0 i =
true while {
i @ 5 == if {
return
}
i @ print " " print
i @ 1 + i =
true
}
}
Recursion
Ena supports recursive function calls. Here’s the factorial example from the standard examples:
factorial_inner {
dup 1 == if {
1 return
}
dup 1 swap - factorial_inner *
}
factorial {
factorial_inner *
}
main {
5 factorial println # Outputs: 120
}
Short-Circuit Evaluation
Ena does not have built-in short-circuit evaluation for boolean operations. You must implement it manually using if blocks:
safe_check {
value ( unit )
value =
# Check if value is not null first
value @ null == if {
false return
}
# Now safe to perform additional checks
value @ some_property_check
}
Comparison Operators
Ena provides several comparison operators that work on the stack:
==: Equal to>: Greater than<: Less than>=: Greater than or equal to<=: Less than or equal to
comparisons {
# Operators work in reverse: a b > tests if b > a
3 5 > # Tests if 5 > 3: true
5 3 > # Tests if 3 > 5: false
3 5 < # Tests if 5 < 3: false
5 3 < # Tests if 3 < 5: true
5 5 == # Tests if 5 == 5: true
}
Important: Comparison operators work in reverse of typical expectations. a b > tests if b > a, not a > b. This is due to how values are popped from the stack.
Boolean Operations
!: Logical NOTand: Logical ANDor: Logical OR
boolean_example {
val1 ( unit )
val2 ( unit )
true val1 =
false val2 =
result ( unit )
val1 @ val2 @ or result =
result @ if {
"At least one is true" println
}
}
Exception Handling
Ena supports exception handling through the try keyword:
will_fail {
drop # Fails because stack is empty
}
main {
'will_fail try
"Error caught: " print
ena.vm.debug
}
When an exception occurs, it is placed on the stack and can be inspected.
Best Practices
-
Always push condition values: When using
while, ensure the condition boolean is pushed at the end of each iteration. -
Use early returns: Simplify complex logic with early
returnstatements. -
Minimize nesting: Prefer early returns over deeply nested
ifblocks. -
Stack order matters: Remember that operators pop values in reverse order due to stack-based execution.
-
Document complex control flow: Use comments to explain non-obvious control flow patterns.
Chapter 4 - Standard Library
The Ena standard library provides essential functionality for I/O operations, memory management, string manipulation, control flow helpers, and more. This chapter documents the key modules and their usage.
Core Operations (ops.ena)
Logical Operators
# Logical NOT equal
!= {
== !
}
Input/Output (io.ena)
Basic Printing
# Print value as string without newline
print {
into_string ena.vm.io.print
}
# Print value with newline
println {
print "\n" ena.vm.io.print
}
Example:
main {
"Hello, World!" println
42 print " is the answer" println
}
Loop Control (loop.ena)
Break and Continue
# Exit from current loop
break {
false return_local
}
# Skip to next iteration
continue {
true return_local
}
Example:
find_number {
i ( unit )
0 i =
true while {
i @ 10 == if {
break
}
i @ println
i @ 1 + i =
true
}
}
Memory Management (mem.ena)
Memory Allocation
# Allocate memory units
units {
alloc
}
# Allocate single unit
unit {
1 units
}
Example:
main {
# Allocate array of 5 elements
arr ( unit )
5 units arr =
# Set specific values
42 arr @ 0 + =
99 arr @ 1 + =
# Read values
arr @ 0 + @ println # 42
arr @ 1 + @ println # 99
}
Note: The memcpy and memfill functions in the standard library use outdated loop patterns and may not work correctly. Use manual loops for memory operations.
String Operations
Ena provides built-in string operations through the VM:
String Length
main {
"Hello, World!" string.len println # 13
}
String Concatenation
Note: String concatenation works in reverse order due to stack popping - the second string is popped first.
main {
"World!" "Hello, " string.concat println # Produces "Hello, World!"
}
String Splitting
main {
" " "one two three" string.split
# Stack now contains: ["one", "two", "three", 3]
ena.vm.debug_stack
}
String Contains
main {
"lo" "Hello" string.contains println # true
"xyz" "Hello" string.contains println # false
}
String Characters
main {
"ABC" string.chars
# Stack now contains individual characters
ena.vm.debug_stack
}
Number Operations (number.ena)
Square Root Operations
sqrt {
2 swap root
}
rsqrt {
sqrt 1 /
}
Block Operations (call.ena)
Dynamic Block Calling
# Call a block by reference
# Expects escaped block on stack
call_block {
'my_block call
}
Collections (collections/vec.ena)
The standard library includes vector operations for working with dynamic arrays. Note: Vector operations require explicitly compiling and linking std/collections/vec.ena with your program - they are not included in the default std.enair.
Common Vector Operations
ena.vec.with_capacity: Create vector with capacityena.vec.push: Add element to vectorena.vec.at: Get element at indexena.vec.cap: Get vector capacityena.vec.size: Get vector sizeena.vec.from_stack: Create vector from stack valuesena.vec.reverse: Reverse vector elementsena.vec.each: Apply block to each element
Example:
# Note: Requires compiling and linking with std/collections/vec.ena
main {
vector ( unit )
5 ena.vec.with_capacity vector =
10 vector @ ena.vec.push
20 vector @ ena.vec.push
30 vector @ ena.vec.push
0 vector @ ena.vec.at println # 10
vector @ ena.vec.size println # 3
}
VM Debug Operations
Stack Inspection
# Print top value
ena.vm.debug
# Print entire stack
ena.vm.debug_stack
# Print call stack
ena.vm.debug_calls
Random Numbers
main {
# Get random number
ena.vm.random println
}
Type Operations
Type Checking
is_exception {
# Check if top value is an exception
}
into_exception {
# Convert value to exception
}
into_string {
# Convert value to string
}
File System Operations (fs.ena)
The standard library provides file system operations for reading and writing files:
# Read file contents
# Arguments: filename
ena.vm.fs.read
# Write to file
# Arguments: content filename
ena.vm.fs.write
Operating System Operations (os.ena)
# Get environment variable
# Arguments: var_name
ena.vm.os.env
# Execute system command
# Arguments: command
ena.vm.os.exec