Sol
2025Sol is a compiled programming language with Ruby and Rust-inspired syntax. Memory is managed through compile-time reference counting optimized with in-place reuse.
Hello World
def main
print("Hello World!")
endLanguage Overview
Symbols
# # comment
-> # return from function
? # boolean method suffix (e.g. empty?, some?)
@ # field access within a class body (@name)
.field # field access on an instance (no parens)
.method() # method call on an instance (parens)
#{} # string interpolation ("Hello, #{name}!")
.. # range operator (0..10)
! # try-unwrap operator (propagates Result errors)Variables & Type Inference
name = "Alice" # inferred as String
age = 30 # inferred as Int
active = true # inferred as BoolFunctions
def add(a Int, b Int) -> Int
-> a + b
end
# Default parameter values
def greet(name String, greeting String = "Hello") -> String
-> "#{greeting}, #{name}!"
end-> is the return operator. Functions with no return type return Unit.
Block Syntax
Definitions use end-delimited blocks, while control flow uses braces:
def greet(name String) -> String
-> "Hello, #{name}!"
end
if x > 0 {
print("positive")
}String Interpolation
name = "Sol"
print("Hello, #{name}!")
print("#{2 + 3} is five")All types implement to_string(), which interpolation calls automatically:
arr = [1, 2, 3]
print("items: #{arr}") # "items: [1, 2, 3]"
print(42.to_string()) # "42"Control Flow
# If-else works as both statement and expression
result = if x > 0 { "positive" } else { "negative" }
# While loop
while i < 10 {
i = i + 1
}
# Infinite loop with break and next (Ruby-style continue)
loop {
if done { break }
if skip { next }
}Pattern Matching
match shape {
Circle(r) => r * r * 3,
Rect(w, h) => w * h,
_ => 0
}Match works as an expression and supports data extraction and wildcards. Matching on enums is exhaustive: the compiler requires all variants to be covered, or a _ wildcard to be present.
Classes
class Point
@x Int
@y Int
def new(x Int, y Int)
@x = x
@y = y
end
def magnitude() -> Int
-> @x * @x + @y * @y
end
end
p = Point.new(3, 4)Attribute defaults:
class Config
@timeout Int = 30
@retries Int = 3
end
c = Config.new()Class methods:
class Factory
def.class create() -> Factory
-> Factory.new()
end
endValue types (stack-allocated, no reference counting). Small classes (<=16 bytes) are automatically inlined; class.inline makes it explicit:
class.inline Vec2
@x Int
@y Int
endEnums
enum Shape
Circle(radius Int)
Rect(w Int, h Int)
Point
end
shape = Shape.Circle(5)Enums can carry associated data and have methods:
enum Direction
North
South
East
West
def axis_value() -> Int
-> match self {
North => 10,
South => 20,
East => 30,
West => 40
}
end
endOption & Result
present = Option.Some(42)
absent = Option.None
value = present.unwrap_or(0)
if present.some? { ... }
# Result for error handling
def divide(a Int, b Int) -> Result<Int, String>
if b == 0 { -> Err("division by zero") }
-> Ok(a / b)
endTry-Unwrap Operator
The ! operator unwraps a Result, propagating the error if it fails:
def compute() -> Result<Int, String>
a = divide(10, 2)!
b = divide(20, 4)!
-> Ok(a + b)
endClosures
double = {|x| x * 2 }
double.call(21)
# Closures as parameters
def apply(f {|Int| Int}, x Int) -> Int
-> f.call(x)
end
apply({|n| n + 1 }, 41)Non-local return: -> inside a closure exits the enclosing function, not just the closure, enabling early exit from iteration:
def find_first(arr Array, f {|Int| Int} -> Int) -> Int
i = 0
while i < arr.length() {
f.call(arr[i])
i = i + 1
}
-> 0
end
def main -> Int
-> find_first([1, 2, 3]) { |x|
if x == 2 { -> x }
0
}
endTraits
trait Valued
def value() -> Int
end
class Coin
impl Valued
@cents Int
def value() -> Int
-> @cents
end
endTraits can also include default method implementations and attribute requirements.
Bundles
Bundles compose multiple traits into a single unit:
trait Named
def name() -> String
end
trait Valued
def value() -> Int
end
bundle Entity
includes Named
includes Valued
end
class Item
impl Entity
# must implement both name() and value()
endOperator Overloading
class Point
@x Int
@y Int
def.op ==(other Self) -> Bool
-> @x == other.x && @y == other.y
end
endSelf Type
Self refers to the implementing type in traits and classes:
trait Clonable
def clone() -> Self
endGenerics
Sol has two keywords for type parameterization:
compile defines explicit type parameters, specified at the call site:
class Box
compile Value type
@value Value
def get() -> Value
-> @value
end
end
b = Box[Int].new(42)Compile-time parameters can also be integers:
class Buffer
compile size Int
@data Int
endinfer defines associated types, inferred from usage rather than specified explicitly. Works on classes, enums, traits, and bundles:
enum Box
infer Content
Value(content Content)
Empty
def has_value? -> Int
-> match self {
Value(_) => 1,
Empty => 0
}
end
end
container = Box.Value(42) # Content inferred as Inttrait Container
infer Element
def get() -> Element
end
class Shelf
infer Element
impl Container
@value Element
def get() -> Element
-> @value
end
endType Aliases
alias Number = Int
alias Coord = PointModules & Imports
module Net
class Request
@id Int
end
def build(id Int) -> Request
-> Request.new(id)
end
end
req = Net::build(43)Imports with aliasing, groups, and globs:
module App
use Net::Request
use Net::Response as Resp
use Net::{Request, Response, ping}
use Math::*
endModules can be nested and reopened. :: prefix accesses the global scope.
Directory structure maps to modules automatically. Folder names are converted to PascalCase. If a folder converts to Api but your code declares module API, the code wins:
src/
main.sl # root scope
net_utils/
client.sl # module NetUtils
http/
request.sl # module NetUtils::HttpArrays
numbers = [1, 2, 3, 4, 5]
first = numbers[0]
slice = numbers[1..3]
numbers.push(6)
numbers.pop()
numbers.length()
# Iterators
doubled = numbers.map({|x| x * 2})
total = numbers.reduce(0, {|sum, x| sum + x})
label = numbers.join(", ")Array type shorthand: [Int] is sugar for Array[Int].
Hash Maps
scores = Hash.new()
scores["alice"] = 95
scores["bob"] = 87
scores.get("alice") # Option.Some(95)
scores.get_or("carol", 0) # 0
scores.contains?("bob") # true
scores.length() # 2
scores.keys() # ["alice", "bob"]
scores.values() # [95, 87]
scores.each({|k, v| print("#{k}: #{v}")})Strings
text = "Hello"
text.length()
char = text[0]
sub = text[1..3]
combined = "Hello, " + "World!"
# String operations
"hello, world".split(", ") # ["hello", "world"]
"hello".replace("l", "r") # "herro"
" hello ".trim() # "hello"
"hello".to_uppercase() # "HELLO"
"HELLO".to_lowercase() # "hello"
"hello".starts_with?("he") # true
"hello".contains("ell") # true
"hello".index_of("ll") # Option.Some(2)Ranges
Ranges are zero-copy unless mutated.
r = 0..10
r.length()
r.contains?(5)
r.empty?Boolean Method Naming
Methods ending in ? return Bool:
option.some?
option.none?
result.ok?
range.empty?
string.unique?Recursion
def factorial(n Int) -> Int
if n <= 1 { -> 1 }
-> n * factorial(n - 1)
endExtern Functions (FFI)
def.extern print(str String)Memory Model
Sol uses compile-time reference counting with copy-on-write semantics and in-place reuse. Allocations are freed deterministically. Value types (class.inline and <= 16 bytes) are stack-allocated and skip reference counting entirely.
Low-level types UnsafePointer[T] and Memory[T] are available for manual control when needed.
Hello World
#!/usr/bin/env sh
cd /hello-world
sol run hello-world.slCoding Guide
Language Version
Sol (compiled via MLIR/LLVM)
Execution Model
- Compiled language using the
solcompiler - Code is compiled to a native binary via MLIR/LLVM, then executed
- Programs use a
def mainentry point
Key Characteristics
- Ruby-inspired syntax with
end-delimited blocks - Statically typed with type inference
- Compile-time reference counting with in-place reuse
- Pattern matching with exhaustive enum checking
- Closures with non-local return
- Traits and bundles for polymorphism
- Generics via
compile(explicit) andinfer(associated types) - Case-sensitive
- 3-space indentation by convention
Fragment Authoring
Write a complete Sol program. Your fragment replaces the entire source file and is compiled to a native binary, then executed. Every fragment must include a def main ... end entry point.
Common Patterns
- Print:
print("message") - Variables:
x = 10orx: Int = 10 - String interpolation:
"Hello, #{name}!" - Functions:
def add(a Int, b Int) -> Int ... end - Return:
-> value - If/else:
if x > 0 { "yes" } else { "no" } - While:
while i < 10 { i = i + 1 } - Arrays:
[1, 2, 3] - Hash maps:
h = Hash.new(); h["key"] = "value" - Strings:
"hello".split(","),"hello".replace("l", "r")," hi ".trim() - Iterators:
[1,2,3].map({|x| x * 2}),[1,2,3].reduce(0, {|sum, x| sum + x}) - Classes:
class Point ... end - Enums:
enum Shape ... end - Pattern matching:
match value { ... } - Closures:
{|x| x * 2 } - Boolean methods end in
?
Examples
# Simple output
def main
print("Hello from fragment!")
end
# Variables and string interpolation
def main
name = "Sol"
version = 1
print("#{name} version #{version}")
end
# Functions and return
def add(a Int, b Int) -> Int
-> a + b
end
def main
result = add(5, 10)
print("5 + 10 = #{result}")
end
# If-else expression
def main
x = 42
label = if x > 0 { "positive" } else { "negative" }
print("#{x} is #{label}")
end
# While loop
def main
i = 0
total = 0
while i < 5 {
total = total + i
i = i + 1
}
print("Sum: #{total}")
end
# Arrays
def main
numbers = [10, 20, 30]
print("First: #{numbers[0]}")
print("Length: #{numbers.length()}")
end
# Classes
class Point
@x Int
@y Int
def new(x Int, y Int)
@x = x
@y = y
end
def to_string() -> String
-> "(#{@x}, #{@y})"
end
end
def main
p = Point.new(3, 4)
print("Point: #{p}")
end
# Enums and pattern matching
enum Color
Red
Green
Blue
end
def main
c = Color.Red
name = match c {
Red => "red",
Green => "green",
Blue => "blue"
}
print("Color: #{name}")
end
# Closures
def main
double = {|x| x * 2 }
print("#{double.call(21)}")
end
# Hash maps
def main
scores = Hash.new()
scores["alice"] = 95
scores["bob"] = 87
print("Alice: #{scores.get_or("alice", 0)}")
print("Keys: #{scores.keys()}")
end
# Array iterators
def main
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map({|x| x * 2})
total = numbers.reduce(0, {|sum, x| sum + x})
print("Doubled: #{doubled}")
print("Sum: #{total}")
print("Joined: #{numbers.join(", ")}")
end
# String operations
def main
text = "hello, world"
parts = text.split(", ")
print("Parts: #{parts}")
print("Upper: #{text.to_uppercase()}")
print("Replace: #{text.replace("world", "Sol")}")
end
# Recursion
def factorial(n Int) -> Int
if n <= 1 { -> 1 }
-> n * factorial(n - 1)
end
def main
print("10! = #{factorial(10)}")
end
# TCP echo server
def main
bind_result = TcpServer.bind(9000)
match bind_result {
Ok(server) => {
print("Listening on port 9000")
accept_result = server.accept()
match accept_result {
Err(e) => print("Accept error"),
Ok(conn) => {
buf = UnsafePointer[UInt8].alloc(1024)
read_result = conn.read_into(buf, 1024)
match read_result {
Ok(n) => {
conn.write_bytes(buf, n)
print("Echoed #{n} bytes")
}
Err(e) => print("Read error"),
}
buf.free()
_ = conn.close()
}
}
_ = server.close()
}
Err(e) => print("Bind error"),
}
endCaveats
- Fragments replace the entire source file, so include all definitions and a
def main ... endentry point def maindoes not need a return type- Use
print()for output - String interpolation uses
#{}syntax ->is the return operator- Definitions use
end, control flow uses{} - The code is compiled to a native binary and executed each time