Soplang 3.0.0 is available. It brings a Rust compiler, Cranelift JIT execution, LLVM compilation to standalone executables, clearer collection APIs, and Somali diagnostics that point back to the original source.
The purpose of Soplang stays the same: let people learn and express programming concepts through Somali. What changes in this release is the implementation supporting that language, how programs run, and how we check their behavior.
This post covers the move from v2 to v3, the features available today, the benchmark comparison with Python, and the work planned for the rest of the 3.x line.
What Changes from Soplang 2?
Soplang 2 was a Python-hosted interpreter. It already supported dynamic and typed variables, functions, classes, inheritance, collections, control flow, and an interactive shell. Those concepts form the starting point for v3.
Soplang 3 makes the Rust compiler the supported implementation. Source passes through lexing, parsing, import resolution, semantic analysis, and a shared intermediate representation before reaching one of two native-code backends.
| Area | Soplang 2 | Soplang 3 |
|---|---|---|
| Execution | Python interpreter walks the program's syntax tree | Rust compiler lowers programs to native code through Cranelift or LLVM |
| Running a file | Interpreter execution | Cranelift JIT compiles and runs the program in the process |
| Standalone build path | The main file runner executes through the interpreter | --build emits LLVM IR and links a native executable with the runtime |
| Interactive use | Interpreter-backed REPL | Persistent compiled session retaining functions, classes, and globals |
| Functions and objects | Existing function and class model | Compiled functions, captured closures, anonymous callbacks, and parent calls across both backends |
| Diagnostics | Interpreter and parser error reporting | Original files, lines, columns, and source carets through JIT and standalone execution |
| Standard library | Earlier collection names and contracts | More shared names, explicit mutation/combination operations, and stricter validation |
| Distribution | Python-hosted implementation and its distribution tooling | Native Rust compiler packages with matching runtime libraries and native platform checks |
For people running source with soplang program.sop, the command remains familiar. Running the installed v3 compiler does not require a separate Python installation. Python is still used for packaging and benchmark tooling.
One Language, Two Ways to Run It
The v3 release includes both Cranelift JIT and LLVM AOT.
Cranelift for files and the REPL
soplang hello.sop soplang
Running a file compiles it with Cranelift and executes the generated code immediately. Starting Soplang without a file opens the REPL. Its compiled session keeps functions, classes, and global values between inputs.
This is the default path for experimenting, learning, and running scripts. Neither Rust nor clang needs to be installed to use the packaged JIT or REPL.
LLVM for standalone executables
soplang --build hello.sop -o hello ./hello
The build path lowers the same program to textual LLVM IR. System clang compiles that IR to an object, and rustc links it with the Soplang runtime. The output contains compiled program functions and the runtime needed to run them.
A generated executable runs without the Soplang CLI or the original source files beside it. It still targets a particular operating system and architecture and retains that platform's ordinary system-library requirements.
Building an executable requires clang, a platform linker, and the exact Rust toolchain recorded in the package. Official v3.0.0 packages use Rust 1.94.0. The Docker image includes that build toolchain.
Both backends share the language frontend, intermediate representation, and runtime. Dynamic operations use the tagged runtime; operations whose numeric types are known can use native integer and floating-point instructions. Remaining type checks and conversions still have a cost, which shows up in the benchmarks later in this post.
Somali Syntax Remains the Starting Point
Save this as hello.sop:
door magac = "Ayaan"
abn tiro = 21
hawl laban(x) {
celi x * 2
}
qor("Salaan, " + magac)
qor(laban(tiro))It prints:
Salaan, Ayaan 42
door declares a dynamic variable, abn declares an integer variable, hawl defines a function, celi returns a value, and qor prints it. V3 preserves this combination of Somali vocabulary and familiar programming structures.
The type system combines semantic checks with runtime validation. Typed writes are checked and numeric values are normalized to the annotated representation. For example, a fractional value cannot silently become an integer variable. Built-in annotations cover abn integers, jajab floating-point values, qoraal strings, bool, teed lists, and walax objects. Class names can also be used as annotations. --strict enables stricter semantic checks.
Optional semicolons let programs use either newline-oriented statements or explicit statement separators.
Control Flow and Imports
Conditionals use haddii and contextual kale branches. Numeric loops use kuceli, while loops use intay, and switches use dooro. jooji exits a loop and soco continues to its next iteration.
abn wadar = 0
kuceli (i 1 ilaa 5) {
wadar = wadar + i
}
qor(wadar)This prints 15: the loop includes both endpoints. Numeric loops also support an explicit step for descending iteration. File imports use keen; imported code is resolved before either backend compiles the program.
Functions, Closures, and Classes in the Compiled Runtime
V3 supports anonymous functions and captured environments. A function can return a closure that remembers a value from the surrounding call:
hawl samee(factor) {
celi hawl(x) { celi x * factor }
}
door laban = samee(2)
door tirooyin = [1, 2, 3]
qor(tirooyin.aaddin(laban))The result is [2, 4, 6]. The same closure representation supports callbacks used for transformation, filtering, folding, and predicates in both execution paths.
Classes and inheritance also run through both compiled backends. Constructors use the preferred name dhis, and waalid calls an ancestor's implementation:
qaab Xayawaan {
hawl dhis(magac) { nafta.magac = magac }
hawl cod() { celi nafta.magac }
}
qaab Bisad dhaxal Xayawaan {
hawl dhis(magac) { waalid.dhis(magac) }
hawl cod() { celi waalid.cod() + ": Mia" }
}
Xayawaan x = cusub Bisad("Bilan")
qor(x.cod())This prints Bilan: Mia. A variable annotated with the parent class can hold a subclass instance. Invalid class assignments and inheritance cycles are rejected.
Functions, methods, closures, built-ins, and constructors enforce argument counts. Global updates made by functions and callbacks are visible to later top-level reads, including replacement of collection and callable values.
A More Consistent Standard Library
A large part of the v3 work concerns the names and contracts people learn. Related operations now share names across collection types, and common calls have shorter preferred spellings.
| API | Meaning |
|---|---|
hore() / dambe() | First and last elements on lists and Unicode characters on strings |
hel(position, default?) | Read a list item, string character, or object value with an optional default |
saar(...) | Remove a list item or object entry |
geli(value) | Append exactly one list item, including a nested list |
midee(other) | Return a new shallow combined list or object |
ururi(callback, initial) | Fold a list with a callback and initial value |
xiriiri(values) | Join values using the receiving string as the separator |
furayaal() / qiimayaal() / lammaanayaal() | Object keys, values, and key-value pairs |
yar(...) / weyn(...) | Numeric minimum and maximum |
jibbaar(a, b) / xidid(x) | Power and square root |
The shared names reduce how much vocabulary someone must learn when moving between lists, objects, and strings. Each operation still has a documented contract for its receiver and arguments.
door tirooyin = [10, 20] tirooyin.geli(30) qor(tirooyin.hel(5, 0)) qor(tirooyin.saar(1)) qor(tirooyin.midee([40]))
This prints 0, 20, and [10, 30, 40]. The lookup supplies a default, removal returns the removed value, and combination returns a new list.
Lists and strings share integer slice bounds, negative offsets, clamping, and empty results for reversed bounds. Slice ends are excluded. String indexing counts Unicode scalar values, and string endpoints work with non-ASCII text. Objects support computed string keys and bracket assignment.
Callback-based collection operations iterate over a shallow snapshot, so a callback can change the source collection. Callback failures stop iteration and propagate to a surrounding catch block.
Earlier spellings listed as compatibility aliases remain supported. The migration guide explains preferred names and the behavioral changes to check in existing code.
Errors That Point Back to the Program
Somali error messages now travel through the compiled pipeline with their source context: the original file, line, column, source line, and caret at the relevant token.
This includes errors in imported files and runtime errors from LLVM-built executables. The executable retains relevant source lines for diagnostics even when the source file is no longer present. Caret rendering accounts for tabs, combining marks, and wide characters; reported columns count Unicode scalar values.
Runtime failures can be caught with tijaabi and qabo:
tijaabi {
qor(1 / 0)
} qabo (khalad) {
qor("Xisaabtaas lama samayn karo.")
}The example prints the catch message. Uncaught failures exit with status 1 and write diagnostics to stderr. The older fasax spelling remains accepted. --no-color and NO_COLOR=1 produce plain output for terminals and tooling.
The repository includes 63 intentional error examples with expected diagnostic snapshots, checked through both backends.
Benchmarking Python, Soplang 2, and Soplang 3
We measured the v3 compiler against the original v2 interpreter and Python on ten equivalent workloads. The comparison records four execution modes:
- CPython running a Python file.
- The original Soplang v2 file-execution pipeline.
- Soplang v3 running through Cranelift JIT.
- An executable built by Soplang v3 through LLVM.
The v2 and v3 modes use the same .sop files. Python uses equivalent explicit loops, recursion, list operations, and dictionary operations. Expected output is fixed for every workload.
Measurement setup
- Host: Intel Core i7-1185G7, Linux x86_64.
- Python: CPython 3.14.7, with the GIL enabled and experimental JIT disabled. The same Python executable runs the baseline and hosts v2.
- V2 source: The original
v2.0.0tag. That tag reports2.0.0-betainternally; the dataset records its exact source revision. - V3 source: The v3.0.0 release compiler, built in Cargo release mode. LLVM executables use optimization level 2.
- Sampling: 25 measured runs and 5 warmups for each workload and mode, yielding 1,000 measured executions. Every output is validated.
- Scheduling: Sequential processes, interleaved engine order using a fixed seed, pinned to Linux logical CPU 2.
The v2 launcher calls the tag's original file runner and includes normal package imports. It omits interactive shell initialization and history access. Every measurement starts a fresh process. Warmups populate filesystem and Python bytecode caches; no samples are discarded as outliers.
Results
These are median process wall times in milliseconds; lower is better. They include process startup, source reading and parsing, execution, and output capture. JIT compilation is included. LLVM compilation is measured separately.
| Workload | Python 3.14.7 | Soplang 2 | V3 JIT | V3 LLVM |
|---|---|---|---|---|
| Startup | 11.194 | 52.342 | 2.846 | 1.972 |
| Integer sum, 100k | 13.960 | 222.053 | 42.774 | 41.791 |
| Typed integer sum, 100k | 13.791 | 280.631 | 83.952 | 83.268 |
| Typed float sum, 100k | 15.390 | 321.467 | 100.254 | 102.938 |
| Nested loops, 200 × 200 | 13.115 | 129.016 | 22.305 | 21.196 |
| Recursive Fibonacci, 25 | 20.877 | 1265.457 | 171.612 | 142.999 |
| Function calls, 20k | 15.821 | 205.235 | 26.083 | 23.973 |
| List append/index, 5k | 12.164 | 80.566 | 9.740 | 8.161 |
| Object creation, 5k | 15.321 | 98.817 | 25.334 | 23.677 |
| String concatenation, 5k | 12.075 | 66.099 | 13.142 | 11.718 |
The chart includes all workloads and shows interquartile ranges. Its horizontal axis is logarithmic.
What the numbers show
V3 has lower median times than v2 throughout this suite. Across the nine compute workloads, the v2/v3 median-time ratio is 3.2 to 8.3× for JIT and 3.1 to 9.9× for LLVM. Startup is reported as its own workload.
Python remains faster on most compute workloads. V3 has lower whole-program latency for startup and list append/index. The LLVM string result is close to Python, and their interquartile ranges overlap.
The typed integer and float loops also expose work still to do. In these fixtures, declaring the accumulator's type does not produce a faster program. The generated pipeline retains checks and conversions around typed writes; profiling those paths is a useful next step. Native arithmetic is one part of program performance.
These results help us choose what to improve. They measure one workstation and these specific workloads. Startup contributes substantially to short Python runs, so the list latency result alone does not establish a faster list inner loop. Memory use, sustained allocation, and long-running services need separate measurements.
The LLVM builds observed in this run took about 0.11 to 0.15 seconds per workload. Those are single build observations, reported separately from the execution samples. Building adds an initial cost; a standalone executable can then run repeatedly without JIT compilation.
The benchmark report links the full statistics, raw CSV/JSON observations, source and binary hashes, and reproduction instructions.
What We Verified Before Release
The release was gated by native Linux x86_64, Windows x64, macOS ARM64, and container jobs. Each native platform ran 102 tests in debug and 102 in release. The suites include all 28 numbered examples, the 63 diagnostic examples, and dedicated LLVM integration coverage.
The distribution checks also exercised the installed products:
- Extracted packages ran away from the source checkout.
- JIT and LLVM programs exercised imports, closures, classes, and inheritance.
- Linux Debian installation and removal were tested.
- The Windows installer was checked for PATH updates, file associations, and cleanup during removal.
- Downloaded release packages matched the combined SHA-256 manifest.
- The published Docker Hub image passed version, JIT, and LLVM build checks.
The release verification run records the native and container results. Release testing gives us a documented baseline; more programs, platforms, and long-running workloads will keep extending that coverage.
Install and Try Soplang 3
The v3.0.0 release page contains these packages and a combined SHA256SUMS file:
| Platform | Packages and requirements |
|---|---|
| Linux x86_64 | Tar archive and Debian package; glibc 2.39 or later |
| Windows x64 | ZIP archive and installer; the installer is unsigned |
| macOS Apple Silicon | App archive; the bundle declares macOS 14.8.9 or later; not notarized |
Keep the extracted package intact so --build can find its matching runtime libraries. JIT execution and the REPL work without installing Rust or clang. For native builds, follow the toolchain requirements recorded in the package.
Docker provides the compiler and build toolchain together:
docker run --rm soplang/soplang:3.0.0 --version docker run --rm -v "$PWD:/scripts:ro" soplang/soplang:3.0.0 hello.sop
After installing a native package, run the example from this post with:
soplang hello.sop soplang --build hello.sop -o hello ./hello
The Windows executable can be named hello.exe when selecting its output path. For setup details, see the installation guide.
Moving Existing Programs to V3
Start with the migration guide and the language reference. Check the supported spellings used by older code as well as the behavior it relies on. In particular, code written with the v2 tag's declaration keywords may need these spelling updates:
| V2 tag spelling | Preferred v3 spelling | Role |
|---|---|---|
fasalka | qaab | Class declaration |
ka_dhaxal | dhaxal | Inheritance |
ka_keen | keen | Source import |
isku_day | tijaabi | Try block |
Use the documented compatibility aliases where they apply, and check other older spellings against the current reference.
Some corrections intentionally make validation stricter: argument counts are enforced, fractional slice bounds are rejected, and invalid typed writes keep the previous value intact. Logical operators short circuit. Functions and callbacks observe current global values. These are useful changes to test with your own programs while migrating.
Collection search conventions also remain specific to the receiver: list search misses return maran, while string search misses return -1 for compatibility. The reference records these contracts, including which calls mutate a collection and which return a copy.
What Comes Next in the 3.x Line
The next work follows the dependencies of the runtime and standard library:
| Planned release | Focus |
|---|---|
| 3.1 | Runtime memory reclamation, long-running stability, richer compiled diagnostics, structured errors, cleanup blocks, string forms, and more general callable expressions |
| 3.2 | Iteration protocols, more argument forms, and additional data types and collection operations |
| 3.3 | General file I/O, paths, structured data, and networking after resource handling is in place |
The current release supports source imports through keen. General file I/O and HTTP/network APIs remain planned work. Other current limits include the eight-value compiled call boundary, which includes implicit receiver or closure environment arguments where applicable, and runtime memory that still needs a reclamation design for sustained workloads.
The roadmap keeps this work on 3.x releases, with readiness determined by implementation, documentation, and tests. No version 4 transition is scheduled.
Build with Us
Soplang 3 gives the project a compiled foundation while preserving the reason we started: making programming concepts accessible through Somali.
Try an example, migrate a small program, check a diagnostic, or reproduce a benchmark. Reports with a small program, expected behavior, and the compiler version help us turn the next round of work into concrete improvements.
Soplang is open source under the MIT license. The repository, release, and examples are available to explore and build on.
Writing about Somali language technology, data, and AI from the team in Mogadishu.