jelly
Jelly
An abstract virtual machine for building concurrent, fault-tolerant applications. Jelly combines a stack-based bytecode interpreter with Erlang-inspired actor-model concurrency and supervision trees.
Features
- Stack-based VM - Simple, predictable execution model with a rich instruction set
- Actor Model Concurrency - Lightweight processes with message passing
- Fault Tolerance - Process linking, monitoring, and supervision trees
- First-class Data Structures - Maps, arrays, and strings with built-in operations
- Networking - Socket operations for building networked applications
- Exception Handling - Try-catch blocks with rethrow support
Quick Start
Installation
Add Jelly to your shard.yml:
dependencies:
jelly:
github: grkek/jelly
Then run:
shards install
Hello World
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
instructions = [
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Hello, Jelly!")),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
process = engine.process_manager.create_process(instructions: instructions)
engine.processes.push(process)
engine.run
Core Concepts
The Stack Machine
Jelly uses a stack-based architecture. Values are pushed onto a stack, and operations pop their arguments and push results back.
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Compute (10 + 5) * 3 = 45
instructions = [
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(10_i64)),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(5_i64)),
VM::Instruction.new(VM::Code::ADD), # Stack: [15]
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(3_i64)),
VM::Instruction.new(VM::Code::MULTIPLY), # Stack: [45]
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
process = engine.process_manager.create_process(instructions: instructions)
engine.processes.push(process)
engine.run
Processes and Message Passing
Jelly processes are lightweight units of execution with their own stack, mailbox, and local variables. Processes communicate exclusively through asynchronous message passing.
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Create a worker that receives and processes messages
worker_instructions = [
VM::Instruction.new(VM::Code::RECEIVE), # Wait for message
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Received: ")),
VM::Instruction.new(VM::Code::SWAP),
VM::Instruction.new(VM::Code::STRING_CONCATENATE),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
worker = engine.process_manager.create_process(instructions: worker_instructions)
engine.processes.push(worker)
# Send a message to the worker
tuple = {worker.address, VM::Value.new("Hello from sender!")}
sender_instructions = [
VM::Instruction.new(VM::Code::SEND, VM::Value.new(tuple)),
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
sender = engine.process_manager.create_process(instructions: sender_instructions)
engine.processes.push(sender)
engine.run
Process Registry
Processes can register themselves with names for easier discovery:
# Worker registers itself
VM::Instruction.new(VM::Code::REGISTER_PROCESS, VM::Value.new("my_worker"))
# Another process can look it up
VM::Instruction.new(VM::Code::WHEREIS_PROCESS, VM::Value.new("my_worker"))
# Stack now contains the worker's address
Supervision Trees
Jelly supports Erlang-style supervision for building fault-tolerant applications. Supervisors monitor child processes and restart them according to configurable strategies.
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Create a supervisor
supervisor = engine.create_supervisor(
strategy: VM::Supervisor::RestartStrategy::OneForOne, # Only restart failed child
max_restarts: 3,
restart_window: 5.seconds
)
# Define a child specification
child_specification = VM::Supervisor::Child::Specification.new(
id: "worker",
instructions: worker_instructions,
restart: VM::Supervisor::RestartType::Transient, # Restart only on abnormal exit
max_restarts: 3,
restart_window: 5.seconds
)
supervisor.add_child(child_specification)
engine.run
Restart Strategies:
OneForOne- Only restart the failed processOneForAll- Restart all children if one failsRestForOne- Restart the failed process and all processes started after it
Restart Types:
Permanent- Always restartTransient- Restart only on abnormal exitTemporary- Never restart
Process Linking and Monitoring
Processes can be linked (bidirectional) or monitored (unidirectional) to receive notifications when other processes exit.
# Link two processes (if one dies, the other receives an exit signal)
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(other_pid.to_i64)),
VM::Instruction.new(VM::Code::LINK),
# Monitor a process (receive DOWN message when it exits)
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(other_pid.to_i64)),
VM::Instruction.new(VM::Code::MONITOR),
# Enable exit trapping to receive exit signals as messages instead of dying
VM::Instruction.new(VM::Code::PUSH_BOOLEAN, VM::Value.new(true)),
VM::Instruction.new(VM::Code::TRAP_EXIT)
Instruction Reference
Stack Manipulation
| Instruction | Description | Stack Effect |
|---|---|---|
PUSH_INTEGER |
Push integer onto stack | → value |
PUSH_FLOAT |
Push float onto stack | → value |
PUSH_STRING |
Push string onto stack | → value |
PUSH_BOOLEAN |
Push boolean onto stack | → value |
PUSH_NULL |
Push null onto stack | → null |
POP |
Remove top value | value → |
DUPLICATE |
Copy top value | a → a, a |
SWAP |
Swap top two values | a, b → b, a |
ROT |
Rotate top three | a, b, c → b, c, a |
OVER |
Copy second value to top | a, b → a, b, a |
Arithmetic
| Instruction | Description | Stack Effect |
|---|---|---|
ADD |
Add two numbers | a, b → (a+b) |
SUBTRACT |
Subtract | a, b → (a-b) |
MULTIPLY |
Multiply | a, b → (a*b) |
DIVIDE |
Divide | a, b → (a/b) |
MODULO |
Modulo | a, b → (a%b) |
NEGATE |
Negate | a → (-a) |
String Operations
| Instruction | Description | Stack Effect |
|---|---|---|
STRING_CONCATENATE |
Join strings | a, b → (a+b) |
STRING_LENGTH |
Get length | str → length |
STRING_SUBSTRING |
Extract substring | str, start, len → substr |
STRING_TRIM |
Remove whitespace | str → trimmed |
STRING_SPLIT |
Split by delimiter | str, delim → array |
STRING_UPPER |
Uppercase | str → upper |
STRING_LOWER |
Lowercase | str → lower |
STRING_REPLACE |
Replace substring | str, pat, rep → result |
STRING_INDEX |
Find substring index | haystack, needle → index |
STRING_STARTS_WITH |
Check prefix | str, prefix → bool |
STRING_ENDS_WITH |
Check suffix | str, suffix → bool |
STRING_CONTAINS |
Check contains | str, needle → bool |
CHAR_AT |
Get character at index | str, index → char |
CHAR_CODE |
Get char code | str → code |
CHAR_FROM_CODE |
Create char from code | code → char |
Comparisons
| Instruction | Description | Stack Effect |
|---|---|---|
LESS_THAN |
Less than | a, b → (a<b) |
GREATER_THAN |
Greater than | a, b → (a>b) |
LESS_THAN_OR_EQUAL |
Less or equal | a, b → (a<=b) |
GREATER_THAN_OR_EQUAL |
Greater or equal | a, b → (a>=b) |
EQUAL |
Equal | a, b → (a==b) |
NOT_EQUAL |
Not equal | a, b → (a!=b) |
Logical Operations
| Instruction | Description | Stack Effect |
|---|---|---|
AND |
Logical AND | a, b → (a && b) |
OR |
Logical OR | a, b → (a || b) |
NOT |
Logical NOT | a → (!a) |
Variables
| Instruction | Description |
|---|---|
LOAD_LOCAL |
Load local variable onto stack |
STORE_LOCAL |
Store top of stack to local variable |
LOAD_GLOBAL |
Load global variable onto stack |
STORE_GLOBAL |
Store top of stack to global variable |
Flow Control
| Instruction | Description |
|---|---|
JUMP |
Unconditional jump by offset |
JUMP_IF |
Jump if top of stack is true |
JUMP_UNLESS |
Jump if top of stack is false |
CALL |
Call subroutine |
RETURN |
Return from subroutine |
HALT |
Stop process execution |
Concurrency
| Instruction | Description |
|---|---|
SPAWN |
Create new process |
SPAWN_LINK |
Spawn and link atomically |
SPAWN_MONITOR |
Spawn and monitor atomically |
SELF |
Push own process address |
SEND |
Send message to process |
RECEIVE |
Receive any message (blocking) |
RECEIVE_SELECT |
Receive matching pattern |
RECEIVE_TIMEOUT |
Receive with timeout |
SEND_AFTER |
Schedule delayed message |
REGISTER_PROCESS |
Register name for process |
WHEREIS_PROCESS |
Look up process by name |
KILL |
Terminate another process |
SLEEP |
Pause execution |
Fault Tolerance
| Instruction | Description |
|---|---|
LINK |
Link to another process |
UNLINK |
Remove link |
MONITOR |
Monitor another process |
DEMONITOR |
Stop monitoring |
TRAP_EXIT |
Enable/disable exit trapping |
EXIT |
Send exit signal to process |
EXIT_SELF |
Exit current process |
IS_ALIVE |
Check if process is alive |
PROCESS_INFO |
Get process information |
Data Structures
Maps:
| Instruction | Description |
|---|---|
MAP_NEW |
Create empty map |
MAP_GET |
Get value by key |
MAP_SET |
Set key-value pair |
MAP_DELETE |
Delete key |
MAP_KEYS |
Get all keys |
MAP_SIZE |
Get number of entries |
Arrays:
| Instruction | Description |
|---|---|
ARRAY_NEW |
Create new array |
ARRAY_GET |
Get element by index |
ARRAY_SET |
Set element at index |
ARRAY_PUSH |
Add element to end |
ARRAY_POP |
Remove and return last element |
ARRAY_LENGTH |
Get array length |
Exception Handling
| Instruction | Description |
|---|---|
TRY_CATCH |
Begin try block with catch offset |
END_TRY |
End try block (no exception) |
CATCH |
Exception handler entry point |
THROW |
Throw exception |
RETHROW |
Rethrow to outer handler |
Networking
TCP:
| Instruction | Description |
|---|---|
TCP_CONNECT |
Connect to host:port |
TCP_SEND |
Send data |
TCP_RECEIVE |
Receive data |
TCP_CLOSE |
Close connection |
TCP_LISTEN |
Create server socket bound to host:port |
TCP_ACCEPT |
Accept incoming connection |
UDP:
| Instruction | Description |
|---|---|
UDP_BIND |
Create and bind UDP socket to host:port |
UDP_CONNECT |
Create UDP socket connected to remote host:port |
UDP_SEND |
Send data over connected UDP socket |
UDP_SEND_TO |
Send data to specific address |
UDP_RECEIVE |
Receive data (returns address info and data) |
UDP_CLOSE |
Close UDP socket |
UNIX Sockets:
| Instruction | Description |
|---|---|
UNIX_CONNECT |
Connect to UNIX domain socket path |
UNIX_SEND |
Send data over UNIX socket |
UNIX_RECEIVE |
Receive data from UNIX socket |
UNIX_CLOSE |
Close UNIX socket |
UNIX_LISTEN |
Create UNIX domain server socket |
UNIX_ACCEPT |
Accept connection on UNIX server |
Generic Socket:
| Instruction | Description |
|---|---|
SOCKET_INFO |
Get socket type and info |
SOCKET_CLOSE |
Close any socket type |
I/O
| Instruction | Description |
|---|---|
PRINT_LINE |
Print value to stdout |
READ_LINE |
Read line from stdin |
Examples
Factorial Calculator
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Compute factorial of 5 using a loop
instructions = [
# result = 1 (local 0)
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(1_i64)),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(0_u64)),
# n = 5 (local 1)
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(5_i64)),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(1_u64)),
# Loop: while n > 0
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(1_u64)),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(0_i64)),
VM::Instruction.new(VM::Code::GREATER_THAN),
VM::Instruction.new(VM::Code::JUMP_UNLESS, VM::Value.new(9_i64)), # Exit loop
# result = result * n
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_u64)),
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(1_u64)),
VM::Instruction.new(VM::Code::MULTIPLY),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(0_u64)),
# n = n - 1
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(1_u64)),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(1_i64)),
VM::Instruction.new(VM::Code::SUBTRACT),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(1_u64)),
# Loop back
VM::Instruction.new(VM::Code::JUMP, VM::Value.new(-13_i64)),
# Print result
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_u64)),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
process = engine.process_manager.create_process(instructions: instructions)
engine.processes.push(process)
engine.run
HTTP Client (Get Your IP Address)
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
instructions = [
# Connect to icanhazip.com:80
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("icanhazip.com")),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(80_i64)),
VM::Instruction.new(VM::Code::TCP_CONNECT),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(0_i64)), # Store socket
# Send HTTP request
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_i64)),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new(
"GET / HTTP/1.1\r\nHost: icanhazip.com\r\nConnection: close\r\n\r\n"
)),
VM::Instruction.new(VM::Code::TCP_SEND),
VM::Instruction.new(VM::Code::POP),
# Receive response
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_i64)),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(2048_i64)),
VM::Instruction.new(VM::Code::TCP_RECEIVE),
VM::Instruction.new(VM::Code::BINARY_TO_STRING),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(1_i64)),
# Parse body (skip HTTP headers)
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(1_i64)),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("\r\n\r\n")),
VM::Instruction.new(VM::Code::STRING_INDEX),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(4_i64)),
VM::Instruction.new(VM::Code::ADD),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(2_i64)),
# Extract and print IP
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(1_i64)),
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(2_i64)),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(50_i64)),
VM::Instruction.new(VM::Code::STRING_SUBSTRING),
VM::Instruction.new(VM::Code::STRING_TRIM),
VM::Instruction.new(VM::Code::PRINT_LINE),
# Cleanup
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_i64)),
VM::Instruction.new(VM::Code::TCP_CLOSE),
VM::Instruction.new(VM::Code::POP),
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
process = engine.process_manager.create_process(instructions: instructions)
engine.processes.push(process)
engine.run
Ping-Pong Between Processes
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Create both processes first to get addresses
pong_process = engine.process_manager.create_process(instructions: [] of VM::Instruction)
engine.processes.push(pong_process)
ping_process = engine.process_manager.create_process(instructions: [] of VM::Instruction)
engine.processes.push(ping_process)
# Build the message to send - include ping's actual address
ping_message = VM::Value.new({
"from" => VM::Value.new(ping_process.address.to_i64),
"message" => VM::Value.new("ping"),
})
# Pong process: receives "ping", sends back "pong"
pong_instructions = [
# Receive message - will be a map {from: address, message: "ping"}
VM::Instruction.new(VM::Code::RECEIVE),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(0_u64)), # Store full message
# Get sender address from message
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_u64)),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("from")),
VM::Instruction.new(VM::Code::MAP_GET),
VM::Instruction.new(VM::Code::STORE_LOCAL, VM::Value.new(1_u64)), # Store sender address in local 1
# Print received message
VM::Instruction.new(VM::Code::LOAD_LOCAL, VM::Value.new(0_u64)),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("message")),
VM::Instruction.new(VM::Code::MAP_GET),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Pong received: ")),
VM::Instruction.new(VM::Code::SWAP),
VM::Instruction.new(VM::Code::STRING_CONCATENATE),
VM::Instruction.new(VM::Code::PRINT_LINE),
# Send "pong" back to sender using their address
VM::Instruction.new(VM::Code::SEND, VM::Value.new({ping_process.address, VM::Value.new("pong")})),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Pong done!")),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::PUSH_SYMBOL, VM::Value.new(:normal)),
VM::Instruction.new(VM::Code::EXIT_SELF_PROCESS),
]
# Ping process: sends "ping" to pong, waits for reply
ping_instructions = [
# Print starting message
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Ping starting...")),
VM::Instruction.new(VM::Code::PRINT_LINE),
# Send message to pong (SEND doesn't push to stack, no POP needed)
VM::Instruction.new(VM::Code::SEND, VM::Value.new({pong_process.address, ping_message})),
# Wait for reply
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Ping waiting for reply...")),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::RECEIVE),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Ping received: ")),
VM::Instruction.new(VM::Code::SWAP),
VM::Instruction.new(VM::Code::STRING_CONCATENATE),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::PUSH_SYMBOL, VM::Value.new(:normal)),
VM::Instruction.new(VM::Code::EXIT_SELF_PROCESS),
]
# Set the instructions on the processes
pong_process.instructions = pong_instructions
ping_process.instructions = ping_instructions
engine.run
Supervised worker
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Create supervisor with restart strategy
supervisor = engine.create_supervisor(
strategy: VM::Supervisor::RestartStrategy::OneForOne,
max_restarts: 5,
restart_window: 10.seconds
)
# Worker that might fail
worker_instructions = [
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Worker starting...")),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Worker stopping...")),
VM::Instruction.new(VM::Code::PRINT_LINE),
VM::Instruction.new(VM::Code::PUSH_SYMBOL, VM::Value.new(:normal)),
VM::Instruction.new(VM::Code::EXIT_SELF_PROCESS),
]
# Permanent - Always restart (even on normal exit) - use for long-running services
# Transient - Only restart on abnormal exit - use for task workers
# Temporary - Never restart - use for one-shot tasks
# Define child specification
worker_specification = VM::Supervisor::Child::Specification.new(
id: "worker",
instructions: worker_instructions,
restart: VM::Supervisor::RestartType::Transient, # Only restart on abnormal exit
max_restarts: 3,
restart_window: 5.seconds
)
# Add worker to supervisor and run
supervisor.add_child(worker_specification)
engine.run
Configuration
The engine can be configured via the Configuration object:
engine = VM::Engine.new
engine.configuration.max_stack_size = 1000 # Maximum stack depth
engine.configuration.max_mailbox_size = 10000 # Maximum mailbox messages
engine.configuration.iteration_limit = 1000000 # Max execution iterations
engine.configuration.execution_delay = 0.001 # Delay between iterations
engine.configuration.deadlock_detection = true # Enable deadlock detection
engine.configuration.enable_message_acknowledgments = false # Message acknowledgments
engine.configuration.auto_reactivate_processes = true
Debugging
Process Inspection
# Get JSON representation of process state
json = engine.inspect_process(process.address)
puts json
# => {"address":1,"state":"ALIVE","counter":5,"stack":["42"],...}
Debugger
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Attach debugger
debugger = engine.attach_debugger do |process, instruction|
puts "Break: Process <#{process.address}> at #{process.counter}"
puts "Stack: #{process.stack.map(&.to_s)}"
print "debug (c/s/q)> "
case gets.try(&.chomp)
when "s" then VM::Engine::Debugger::Action::Step
when "q" then VM::Engine::Debugger::Action::Abort
else VM::Engine::Debugger::Action::Continue
end
end
# Break at instruction 0
debugger.add_breakpoint_at(0_u64)
# Simple program
instructions = [
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(10_i64)),
VM::Instruction.new(VM::Code::PUSH_INTEGER, VM::Value.new(20_i64)),
VM::Instruction.new(VM::Code::ADD),
VM::Instruction.new(VM::Code::PRINT_LINE), # Breakpoint here
VM::Instruction.new(VM::Code::HALT_PROCESS),
]
process = engine.process_manager.create_process(instructions: instructions)
engine.processes << process
engine.run
Fault Tolerance Statistics
stats = engine.fault_tolerance_statistics
puts stats
# => {links: 2, monitors: 1, trapping: 0, supervisors: 1, crash_dumps: 0}
Advanced Topics
Custom Instruction Handlers
Extend the VM with custom instructions:
require "jelly"
alias VM = Jelly::VirtualMachine
engine = VM::Engine.new
# Add custom instruction handler with a block
engine.on_instruction(VM::Code::DUPLICATE) do |process, instruction|
process.counter += 1
process.stack.push(VM::Value.new("custom result"))
VM::Value.new
end
process = engine.process_manager.create_process(instructions: [
VM::Instruction.new(VM::Code::PUSH_STRING, VM::Value.new("Hello, World!")),
VM::Instruction.new(VM::Code::DUPLICATE),
VM::Instruction.new(VM::Code::PRINT_LINE)
])
engine.processes.push(process)
engine.run
Pattern Matching in Receive
Use RECEIVE_SELECT to selectively receive messages matching a pattern:
# Only receive messages with type: "request"
pattern = VM::Value.new({"type" => VM::Value.new("request")})
VM::Instruction.new(VM::Code::RECEIVE_SELECT, pattern)
Delayed Messages
Schedule messages to be delivered in the future:
# Send "timeout" to target process in 5 seconds
tuple = {target_address, VM::Value.new("timeout"), 5.0}
VM::Instruction.new(VM::Code::SEND_AFTER, VM::Value.new(tuple))
Use Cases
- Distributed Systems Simulation - Model and test distributed algorithms
- Educational Tool - Learn about VMs, concurrency, and fault tolerance
- Game Scripting - Lightweight scripting engine with built-in concurrency
- Protocol Prototyping - Rapidly prototype network protocols
- Agent-Based Modeling - Simulate autonomous agents with message passing
Contributing
- Fork it (https://github.com/grkek/jelly/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Giorgi Kavrelishvili - creator and maintainer
jelly
- 16
- 0
- 0
- 0
- 0
- about 4 hours ago
- May 14, 2025
MIT License
Fri, 23 Jan 2026 12:47:50 GMT