Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

KeywordMeaning
ifExecutes the block after it, if the top value on stack is equal to true.
whileExecutes the block after it as long as the block pushes true onto the stack.
returnLeaves the current global block.
return_localLeaves 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 stack
  • swap: Swaps the top two values
  • drop: Removes the top value
  • clear: 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 memory
  • unit: Allocate single unit (equivalent to 1 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 as true return_local)
  • break: Exits the loop (implemented as false 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 NOT
  • and: Logical AND
  • or: 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

  1. Always push condition values: When using while, ensure the condition boolean is pushed at the end of each iteration.

  2. Use early returns: Simplify complex logic with early return statements.

  3. Minimize nesting: Prefer early returns over deeply nested if blocks.

  4. Stack order matters: Remember that operators pop values in reverse order due to stack-based execution.

  5. 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 capacity
  • ena.vec.push: Add element to vector
  • ena.vec.at: Get element at index
  • ena.vec.cap: Get vector capacity
  • ena.vec.size: Get vector size
  • ena.vec.from_stack: Create vector from stack values
  • ena.vec.reverse: Reverse vector elements
  • ena.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