Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions compiler/spec/0001-Introduction.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
# Introduction

- [Completeness](#completeness)
- [Core Principles](#core-principles)
Osprey is a functional programming language designed for safety, performance, and expressiveness.

Osprey is a modern functional programming language designed for elegance, safety, and performance. It emphasizes:
## Core Features

- **Named arguments** for multi-parameter functions to improve readability
- **Strong type inference** to reduce boilerplate while maintaining safety
- **String interpolation** for convenient text formatting
- **Pattern matching** for elegant conditional logic
- **Immutable-by-default** variables with explicit mutability
- **Fast HTTP servers and clients** with built-in streaming support
- **WebSocket support** for real-time two-way communication
- Named arguments for multi-parameter functions
- Hindley-Milner type inference with strong static typing
- Pattern matching for all conditional logic
- Immutable-by-default with explicit mutability
- Algebraic effects with compile-time safety
- Result types for all error cases (no exceptions or panics)
- Built-in HTTP/WebSocket support with streaming
- Lightweight fiber-based concurrency

## Completeness
## Design Principles

**Note**: The Osprey language and compiler are under active development. This specification represents design goals and planned features. The spec is the authoritative source for syntax and behavior.
- **Safety**: Make illegal states unrepresentable through static verification
- **Simplicity**: One idiomatic way to accomplish each task
- **Performance**: LLVM compilation with Rust interop for performance-critical code
- **Functional**: Referential transparency, immutable data structures, pure functions
- **Type Safety**: Strong static typing with Hindley-Milner inference; `any` type requires explicit declaration
- **No Exceptions**: All error cases return Result types, enforced at compile time
- **ML Heritage**: Syntax and semantics inspired by ML family languages

## Core Principles
## Development Status

- Elegance (simplicity, ergonomics, efficiency), safety (fewer footguns, security at every level), performance (uses the most efficient approach and allows the use of Rust interop for extreme performance)
- No more than 1 way to do anything
- ML style syntax by default
- Make illegal states unrepresentable. There are no exceptions or panics. Anything than can result in an error state returns a result object
- Referential transparency
- Simplicity
- Interopability with Rust for high performance workloads
- Interopability with Haskell (future) for fundamental correctness
- Static/strong typing. Nothing should be "any" unless EXPLICITLY declared as any
- Minimal ceremony. No main function necessary for example.
- **Fast HTTP performance** as a core design principle
- **Streaming by default** for large responses to prevent memory issues
This specification is the authoritative source for Osprey syntax and behavior. The language and compiler are under active development; implementation status is noted where relevant.
39 changes: 13 additions & 26 deletions compiler/spec/0002-LexicalStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,43 +79,30 @@ let pair = [x, y] // Fixed size: 2 elements

### Arithmetic Operators

All arithmetic operators are type-preserving and return `Result` types to handle errors (overflow, underflow, division by zero).
All arithmetic operators return `Result` types to handle overflow, underflow, and division by zero.

**Integer Arithmetic:**
**Integer Operations:**
- `+` Addition: `(int, int) -> Result<int, MathError>`
- `-` Subtraction: `(int, int) -> Result<int, MathError>`
- `*` Multiplication: `(int, int) -> Result<int, MathError>`
- `/` Division: `(int, int) -> Result<float, MathError>` - Auto-promotes to float (10 / 3 = 3.333...)
- `%` Modulo: `(int, int) -> Result<int, MathError>` - Returns remainder (10 % 3 = 1)
- `/` Division: `(int, int) -> Result<float, MathError>` — always returns float
- `%` Modulo: `(int, int) -> Result<int, MathError>`

**Floating-Point Arithmetic:**
- `+` Addition: `(float, float) -> Result<float, MathError>`
- `-` Subtraction: `(float, float) -> Result<float, MathError>`
- `*` Multiplication: `(float, float) -> Result<float, MathError>`
- `/` Division: `(float, float) -> Result<float, MathError>` - IEEE 754 division (10.0 / 3.0 = 3.333...)
- `%` Modulo: `(float, float) -> Result<float, MathError>` - IEEE 754 remainder
**Floating-Point Operations:**
- `+`, `-`, `*`, `/`, `%`: `(float, float) -> Result<float, MathError>`

**Type Safety:**
- No automatic type promotion: cannot mix int and float in operations
- Use `toFloat(int)` to convert int to float: `toFloat(10) / 3.0`
- Use `toInt(float)` to truncate float to int: `toInt(3.7) = 3`
- No automatic type promotion between int and float
- Use `toFloat(int)` and `toInt(float)` for explicit conversion
- Division `/` always returns float, even for integer operands

**Examples:**
```osprey
// Integer arithmetic
let sum = 5 + 3 // Result<int, MathError> - Success(8)
let quotient = 10 / 3 // Result<float, MathError> - Success(3.333...) - Auto-promotes to float!
let remainder = 10 % 3 // Result<int, MathError> - Success(1)
let sum = 5 + 3 // Result<int, MathError>
let quotient = 10 / 3 // Result<float, MathError> - returns 3.333...
let remainder = 10 % 3 // Result<int, MathError> - returns 1

// Floating-point arithmetic
let precise = 10.0 / 3.0 // Result<float, MathError> - Success(3.333...)
let area = 3.14 * 2.5 // Result<float, MathError> - Success(7.85)

// Division always returns float
let intDiv = 10 / 2 // Result<float, MathError> - Success(5.0) - Float result!
let mixedDiv = 10 / 3 // Result<float, MathError> - Success(3.333...)

// Error cases
let precise = 10.0 / 3.0 // Result<float, MathError>
let divZero = 10 / 0 // Result<float, MathError> - Error(DivisionByZero)
```

Expand Down
141 changes: 26 additions & 115 deletions compiler/spec/0003-Syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,43 +249,30 @@ let max = match a > b {
}
```

#### List Access (Safe)
#### List Access

```
list_access := expression '[' INT ']' // Returns Result<T, IndexError>
```

🚨 **CRITICAL SAFETY GUARANTEE**: List access **ALWAYS** returns `Result<T, IndexError>` - **NO PANICS, NO NULLS, NO EXCEPTIONS**
List access always returns `Result<T, IndexError>` for bounds safety and must be handled with pattern matching:

**MANDATORY PATTERN MATCHING REQUIRED:**
```osprey
let numbers = [1, 2, 3, 4]

// ✅ CORRECT: Pattern matching required
let firstResult = numbers[0] // Returns Result<Int, IndexError>
let firstResult = numbers[0] // Result<int, IndexError>
match firstResult {
Success { value } => print("First: ${value}")
Error { message } => print("Index out of bounds: ${message}")
Error { message } => print("Index error: ${message}")
}

// ✅ CORRECT: Inline pattern matching
// Inline pattern matching
let second = match numbers[1] {
Success { value } => value
Error { _ } => -1 // Default value for out-of-bounds
}

// ✅ CORRECT: Bounds-safe iteration
let commands = ["echo hello", "echo world"]
match commands[0] {
Success { value } => {
print("Executing: ${value}")
spawnProcess(value)
}
Error { message } => print("No command at index 0: ${message}")
Error { _ } => -1
}
```

**FUNDAMENTAL SAFETY PRINCIPLE**: Array access can fail (index out of bounds), therefore it MUST return Result types to enforce explicit error handling and prevent runtime crashes.

#### Field Access

Field access uses dot notation to access fields of record types:
Expand All @@ -311,54 +298,33 @@ print("Age: ${person.age}")
sendEmail(to: person.name, subject: "Hello")
```

#### Field Access Rules and Restrictions
#### Field Access Rules

**✅ ALLOWED - Field Access on Record Types:**
```osprey
type User = { id: Int, name: String, email: String }
let user = User { id: 1, name: "Alice", email: "alice@example.com" }

let userId = user.id // Valid: direct field access
let userName = user.name // Valid: direct field access
let userEmail = user.email // Valid: direct field access
```
Field access is allowed on record types:

**❌ FORBIDDEN - Field Access on `any` Types:**
```osprey
fn processAnyValue(value: any) -> String = {
// ERROR: Cannot access fields on 'any' type
let result = value.name // Compilation error
return result
}

// CORRECT: Use pattern matching for 'any' types
fn processAnyValue(value: any) -> String = match value {
person: { name } => person.name // Extract field via pattern matching
user: User { name } => name // Type-specific pattern matching
_ => "unknown"
}
type User = { id: int, name: string }
let user = User { id: 1, name: "Alice" }
let userId = user.id // Valid
let userName = user.name // Valid
```

**❌ FORBIDDEN - Field Access on Result Types:**
```osprey
type Person = {
name: String
} where validatePerson
Field access requires pattern matching for:
- **`any` types**: Extract fields through structural patterns
- **Result types**: Unwrap Result before accessing fields
- **Union types**: Match variant before accessing fields

fn validatePerson(person: Person) -> Result<Person, String> = match person.name {
"" => Error("Name cannot be empty")
_ => Success(person)
```osprey
// any type - use pattern matching
fn processAny(value: any) -> string = match value {
person: { name } => person.name
_ => "unknown"
}

let personResult = Person { name: "Alice" } // Returns Result<Person, String>

// ERROR: Cannot access field on Result type
let name = personResult.name // Compilation error

// CORRECT: Use pattern matching on Result types
// Result type - unwrap first
match personResult {
Ok { value } => print("Name: ${value.name}") // Access field after unwrapping
Err { error } => print("Construction failed: ${error}")
Success { value } => print("Name: ${value.name}")
Error { message } => print("Error: ${message}")
}
```

Expand Down Expand Up @@ -569,62 +535,7 @@ let wrong: int = {
}
```

#### Performance Characteristics

Block expressions are zero-cost abstractions:
- **Compile-time scoping**: All variable scoping resolved at compile time
- **No runtime overhead**: Blocks compile to sequential instructions
- **Stack allocation**: Local variables allocated on the stack
- **Optimized away**: Simple blocks with no local variables are optimized away

#### Best Practices

**Use block expressions when:**
- You need local variables for complex calculations
- Breaking down complex expressions into readable steps
- Implementing complex match arm logic
- Creating temporary scopes to avoid variable name conflicts

**Avoid block expressions when:**
- A simple expression would suffice
- The block only contains a single expression
- Creating unnecessary nesting levels

**Good Examples:**
```osprey
// Good: Complex calculation with intermediate steps
let result = {
let base = getUserInput()
let squared = base * base
let doubled = squared * 2
squared + doubled
}

// Good: Complex match logic
let response = match request.method {
POST => {
let body = parseBody(request.body)
let validated = validateData(body)
processCreation(validated)
}
_ => "Method not allowed"
}
```

**Bad Examples:**
```osprey
// Bad: Unnecessary block for simple expression
let bad = {
42
}
// Better: let bad = 42

// Bad: Single operation doesn't need block
let also_bad = {
x + y
}
// Better: let also_bad = x + y
```
Block expressions are zero-cost abstractions: scoping is resolved at compile time, and simple blocks are optimized away. See [Block Expressions](0008-BlockExpressions.md) for complete details on semantics and usage patterns.

### Match Expressions

Expand Down
Loading
Loading