From 0e8908b8a2e38e9be800a41a8a88c3fba4a93941 Mon Sep 17 00:00:00 2001 From: Joel Korpela Date: Sat, 28 Feb 2026 12:58:15 -0800 Subject: [PATCH] Add Sol --- sol/Dockerfile | 22 ++ sol/Makefile | 1 + sol/README.md | 504 +++++++++++++++++++++++++++++++++++++++ sol/files/hello-world.sh | 3 + sol/files/hello-world.sl | 5 + sol/fraglet/fraglet.yml | 14 ++ sol/fraglet/guide.md | 150 ++++++++++++ sol/fraglet/verify.sh | 100 ++++++++ 8 files changed, 799 insertions(+) create mode 100644 sol/Dockerfile create mode 100644 sol/Makefile create mode 100644 sol/README.md create mode 100644 sol/files/hello-world.sh create mode 100644 sol/files/hello-world.sl create mode 100644 sol/fraglet/fraglet.yml create mode 100644 sol/fraglet/guide.md create mode 100755 sol/fraglet/verify.sh diff --git a/sol/Dockerfile b/sol/Dockerfile new file mode 100644 index 0000000..6bb0e87 --- /dev/null +++ b/sol/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1 +# escape=\ +FROM blinknlights/sol-compiler:latest AS sol-compiler + +COPY ./files/hello-world.sl /build/hello-world.sl +RUN mkdir -p /build/sol/core && cp -r /usr/local/lib/sol/core/* /build/sol/core/ +RUN cd /build && sol -o /build/hello hello-world.sl + +FROM 100hellos/000-base:local + +RUN sudo apk add --no-cache gcc musl-dev + +COPY --from=sol-compiler /usr/local/bin/sol /usr/local/bin/sol +COPY --from=sol-compiler /usr/local/lib/libsol_runtime.a /usr/local/lib/libsol_runtime.a +COPY --from=sol-compiler /usr/local/lib/sol /usr/local/lib/sol +COPY --from=sol-compiler /build/hello /hello-world/hello + +COPY --chown=human:human ./files /hello-world +RUN ln -sf /usr/local/lib/sol /hello-world/sol + +COPY --chown=human:human ./fraglet / +ENTRYPOINT [ "/fraglet-entrypoint" ] diff --git a/sol/Makefile b/sol/Makefile new file mode 100644 index 0000000..01156a4 --- /dev/null +++ b/sol/Makefile @@ -0,0 +1 @@ +include ../Makefile.language-container.mk diff --git a/sol/README.md b/sol/README.md new file mode 100644 index 0000000..8a4847b --- /dev/null +++ b/sol/README.md @@ -0,0 +1,504 @@ +# Sol + +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 + +```sol +def main + print("Hello World!") +end +``` + +## Language Overview + +### Symbols + +```sol +# # 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 + +```sol +name = "Alice" # inferred as String +age = 30 # inferred as Int +active = true # inferred as Bool +``` + +### Functions + +```sol +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: + +```sol +def greet(name String) -> String + -> "Hello, #{name}!" +end + +if x > 0 { + print("positive") +} +``` + +### String Interpolation + +```sol +name = "Sol" +print("Hello, #{name}!") +print("#{2 + 3} is five") +``` + +All types implement `to_string()`, which interpolation calls automatically: + +```sol +arr = [1, 2, 3] +print("items: #{arr}") # "items: [1, 2, 3]" +print(42.to_string()) # "42" +``` + +### Control Flow + +```sol +# 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 + +```sol +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 + +```sol +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:** + +```sol +class Config + @timeout Int = 30 + @retries Int = 3 +end + +c = Config.new() +``` + +**Class methods:** + +```sol +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: + +```sol +class.inline Vec2 + @x Int + @y Int +end +``` + +### Enums + +```sol +enum Shape + Circle(radius Int) + Rect(w Int, h Int) + Point +end + +shape = Shape.Circle(5) +``` + +Enums can carry associated data and have methods: + +```sol +enum Direction + North + South + East + West + + def axis_value() -> Int + -> match self { + North => 10, + South => 20, + East => 30, + West => 40 + } + end +end +``` + +### Option & Result + +```sol +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 + 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: + +```sol +def compute() -> Result + a = divide(10, 2)! + b = divide(20, 4)! + -> Ok(a + b) +end +``` + +### Closures + +```sol +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: + +```sol +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 + +```sol +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: + +```sol +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 + +```sol +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: + +```sol +trait Clonable + def clone() -> Self +end +``` + +### Generics + +Sol has two keywords for type parameterization: + +**`compile`** defines explicit type parameters, specified at the call site: + +```sol +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: + +```sol +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: + +```sol +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 +``` + +```sol +trait Container + infer Element + + def get() -> Element +end + +class Shelf + infer Element + impl Container + + @value Element + + def get() -> Element + -> @value + end +end +``` + +### Type Aliases + +```sol +alias Number = Int +alias Coord = Point +``` + +### Modules & Imports + +```sol +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: + +```sol +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 + +```sol +numbers = [1, 2, 3, 4, 5] +first = numbers[0] +slice = numbers[1..3] +numbers.push(6) +numbers.pop() +numbers.length() +``` + +Array type shorthand: `[Int]` is sugar for `Array[Int]`. + +### Strings + +```sol +text = "Hello" +text.length() +char = text[0] +sub = text[1..3] +combined = "Hello, " + "World!" +``` + +### Ranges + +Ranges are zero-copy unless mutated. + +```sol +r = 0..10 +r.length() +r.contains?(5) +r.empty? +``` + +### Boolean Method Naming + +Methods ending in `?` return Bool: + +```sol +option.some? +option.none? +result.ok? +range.empty? +string.unique? +``` + +### Recursion + +```sol +def factorial(n Int) -> Int + if n <= 1 { -> 1 } + -> n * factorial(n - 1) +end +``` + +### Extern Functions (FFI) + +```sol +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. diff --git a/sol/files/hello-world.sh b/sol/files/hello-world.sh new file mode 100644 index 0000000..66bd2d6 --- /dev/null +++ b/sol/files/hello-world.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cd /hello-world +sol -o /tmp/hello hello-world.sl && /tmp/hello diff --git a/sol/files/hello-world.sl b/sol/files/hello-world.sl new file mode 100644 index 0000000..433a3e5 --- /dev/null +++ b/sol/files/hello-world.sl @@ -0,0 +1,5 @@ +# BEGIN_FRAGLET +def main + print("Hello World!") +end +# END_FRAGLET diff --git a/sol/fraglet/fraglet.yml b/sol/fraglet/fraglet.yml new file mode 100644 index 0000000..9c309aa --- /dev/null +++ b/sol/fraglet/fraglet.yml @@ -0,0 +1,14 @@ +# fraglet-entrypoint configuration for Sol container + +fragletTempPath: /FRAGLET + +injection: + codePath: /hello-world/hello-world.sl + match_start: "# BEGIN_FRAGLET" + match_end: "# END_FRAGLET" + +guide: /guide.md + +execution: + path: /hello-world/hello-world.sh + makeExecutable: true diff --git a/sol/fraglet/guide.md b/sol/fraglet/guide.md new file mode 100644 index 0000000..11ee585 --- /dev/null +++ b/sol/fraglet/guide.md @@ -0,0 +1,150 @@ +# Sol Fraglet 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]` +- Classes: `class Point ... end` +- Enums: `enum Shape ... end` +- Pattern matching: `match value { ... }` +- Closures: `{|x| x * 2 }` +- Boolean methods end in `?` + +## Examples +```sol +# 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 + +# Recursion +def factorial(n Int) -> Int + if n <= 1 { -> 1 } + -> n * factorial(n - 1) +end + +def main + print("10! = #{factorial(10)}") +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 diff --git a/sol/fraglet/verify.sh b/sol/fraglet/verify.sh new file mode 100755 index 0000000..06d164e --- /dev/null +++ b/sol/fraglet/verify.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# verify.sh - Smoke tests for sol fraglet support + +set -euo pipefail + +IMAGE="${1:-100hellos/sol:local}" + +# Helper: verify fraglet compiles and runs, output contains expected string +verify_fraglet() { + local expected="$1" + fragletc --image "$IMAGE" - 2>&1 | grep -q "$expected" +} + +echo "Testing default execution..." +docker run --rm "$IMAGE" | grep -q "Hello World!" + +echo "Testing fraglet examples from guide.md..." + +# Example 1: Simple output +verify_fraglet "Hello from fragment!" <<'EOF' +def main + print("Hello from fragment!") +end +EOF + +# Example 2: Variables and string interpolation +verify_fraglet "Sol version 1" <<'EOF' +def main + name = "Sol" + version = 1 + print("#{name} version #{version}") +end +EOF + +# Example 3: Functions +verify_fraglet "5 + 10 = 15" <<'EOF' +def add(a Int, b Int) -> Int + -> a + b +end + +def main + result = add(5, 10) + print("5 + 10 = #{result}") +end +EOF + +# Example 4: If-else expression +verify_fraglet "42 is positive" <<'EOF' +def main + x = 42 + label = if x > 0 { "positive" } else { "negative" } + print("#{x} is #{label}") +end +EOF + +# Example 5: While loop +verify_fraglet "Sum: 10" <<'EOF' +def main + i = 0 + total = 0 + while i < 5 { + total = total + i + i = i + 1 + } + print("Sum: #{total}") +end +EOF + +# Example 6: Enums and pattern matching +verify_fraglet "Color: red" <<'EOF' +enum Color + Red + Green + Blue +end + +def main + c = Color.Red + name = match c { + Red => "red", + Green => "green", + Blue => "blue" + } + print("Color: #{name}") +end +EOF + +# Example 7: Recursion +verify_fraglet "10! = 3628800" <<'EOF' +def factorial(n Int) -> Int + if n <= 1 { -> 1 } + -> n * factorial(n - 1) +end + +def main + print("10! = #{factorial(10)}") +end +EOF + +echo "✓ All tests passed"