100 Helloslanguages
Home / Languages / Sol

Sol

2025
general-purposeimperativeobject-oriented
docker run --rm --platform="linux/amd64" 100hellos/sol:latest

Sol 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!")
end

Language 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 Bool

Functions

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
end

Value 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
end

Enums

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
end

Option & 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)
end

Try-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)
end

Closures

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
   }
end

Traits

trait Valued
   def value() -> Int
end

class Coin
   impl Valued

   @cents Int

   def value() -> Int
      -> @cents
   end
end

Traits 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()
end

Operator Overloading

class Point
   @x Int
   @y Int

   def.op ==(other Self) -> Bool
      -> @x == other.x && @y == other.y
   end
end

Self Type

Self refers to the implementing type in traits and classes:

trait Clonable
   def clone() -> Self
end

Generics

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
end

infer 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 Int
trait Container
   infer Element

   def get() -> Element
end

class Shelf
   infer Element
   impl Container

   @value Element

   def get() -> Element
      -> @value
   end
end

Type Aliases

alias Number = Int
alias Coord = Point

Modules & 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::*
end

Modules 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::Http

Arrays

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)
end

Extern 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.sl

Coding Guide

Language Version

Sol (compiled via MLIR/LLVM)

Execution Model

  • Compiled language using the sol compiler
  • Code is compiled to a native binary via MLIR/LLVM, then executed
  • Programs use a def main entry 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) and infer (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 = 10 or x: 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"),
   }
end

Caveats

  • Fragments replace the entire source file, so include all definitions and a def main ... end entry point
  • def main does 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

Connections

influenced by
rubyrustmojoswiftkoka

Container Info

image100hellos/sol:latest
build scheduleSunday
fragletno