YREA-SLS/stack_lang_spec.md

24 KiB

Stack Language Specification v0.5

1. Overview

A statically-typed, stack-based language with pure postfix notation combining the execution model of HP's RPL, the type system of C and Rust, and modern array operations from Uiua.

Design Principles

  • Everything is postfix - no exceptions
  • Stack-based execution (no local variables)
  • Static typing with type inference
  • Manual heap memory management
  • Types define what things are, traits define how things act
  • All constructs are implicitly generic
  • Every operator is defined by a trait

2. Lexical Structure

2.1 Comments

// Single-line comment

2.2 Identifiers

Regular Identifiers

  • Start with letter or underscore: [a-zA-Z_][a-zA-Z0-9_]*
  • Case-sensitive
  • When encountered, identifiers are executed immediately

Identifier Literals

  • Prefix with :: to push the identifier itself onto the stack instead of executing it
  • Syntax: ::name pushes the identifier name as a value
  • Example: ::Addable pushes the identifier Addable onto the stack
  • Example: ::Point pushes the identifier Point onto the stack

2.3 Literals

Integer Literals

42          // i64 (default)
42:i32      // Annotate as i32
0xFF        // hexadecimal
0b1010      // binary

Floating Point Literals

3.14        // f64 (default)
3.14:f32    // Annotate as f32

String Literals

"hello world"
"escape sequences: \n \t \\ \""

Escape Sequences

  • \n - Newline (LF)
  • \r - Carriage return (CR)
  • \t - Tab
  • \\ - Backslash
  • \" - Double quote
  • \' - Single quote
  • \0 - Null character
  • \xNN - Hexadecimal byte (e.g., \x41 for 'A')
  • \u{NNNN} - Unicode code point (e.g., \u{1F600} for 😀)

Boolean Literals

true
false

Array Literals

[1 2 3 4 5]              // array of i32
[1.0 2.0 3.0]            // array of f64
[[1 2] [3 4]]            // 2D array

Type Tuples

(T T -- T)               // Function signature: two inputs of type T, one output of type T
(i32 f64 -- String)      // Takes i32 and f64, returns String
(-- bool)                // No inputs, returns bool
(Point --)               // Takes Point, no outputs

Type tuples represent stack effects and are used in function signatures to specify what a function consumes from and produces to the stack.

Token Strings

{ code here }            // TokenString - lexed but not parsed/executed

Token strings contain lexed tokens that are not parsed or executed until an operator causes them to be:

  • trait operator parses the TokenString as a trait definition
  • fn operator parses the TokenString as a function definition
  • impl operator parses the TokenString as a trait implementation
  • eval operator parses and executes the TokenString immediately
  • Control flow operators (if, while, match, etc.) parse their TokenString arguments as code blocks

Within TokenStrings used for trait definitions and implementations, the :: prefix should not be used. The context (trait definition or implementation) determines how identifiers are interpreted. For function bodies and eval contexts, :: may be used to create identifier literals.

TODO: (FOR HUMAN) Should :: be required for identifier literals in functions?

3. Type System

3.1 Primitive Types

  • i8, i16, i32, i64 - Signed integers
  • u8, u16, u32, u64 - Unsigned integers
  • f32, f64 - Floating point
  • bool - Boolean
  • char - Single character (UTF-8)
  • ptr - Raw pointer (generic over pointed type)

3.2 Types vs Traits

Types define the concrete structure and memory layout:

Point struct              // Point is a type
Rectangle struct          // Rectangle is a type

Traits define behavioral contracts - how things act:

{ ... } ::Addable trait   // Addable is a trait
{ ... } ::Drawable trait  // Drawable is a trait

Key Distinction:

  • A value has a type (what it is structurally)
  • A value implements a trait (how it behaves)
  • Types are concrete; traits are interfaces
  • Functions can be generic over traits
  • Functions can have types and traits defined as return types
  • Every operator must be backed by a trait

3.3 Generic Constructs

Functions, structs, and unions can be generic over type parameters. Type parameters must be constrained by traits when operations are performed on them:

// Function generic over any type T with Multiplyable constraint
(T -- T) ::Multiplyable { dup * } ::square fn

// Struct generic over field type T
(T T --) { x: y: } ::Point struct

// Union generic over variant type T
(T --) { Some(T) None } ::Option union

Important: Unconstrained generic functions (those that don't perform operations on their type parameters) can omit trait constraints:

// Generic identity - works with any type (no operations performed)
(T -- T) { } ::identity fn

4. Trait System

4.1 Standard Traits

Traits define behavioral contracts. Every operator in the language is backed by one or more traits.

Stack Manipulation Trait

{ 
    (-- Self) push: 
    (Self -- Self Self) dup: 
    (Self -- ) drop: 
    (Self Self -- Self Self) swap: 
    (Self Self -- Self Self Self) over: 
    (Self Self Self -- Self Self Self) rot: 
    (Size -- Self) pick: 
    (Size Size -- ) roll: 
    (-- i64) depth: 
} ::Stackable trait

Size Trait

The Size trait represents types suitable for indexing and sizing operations:

[ ::Addable ::Comparable ::Convertible ] ::Size inher
{ } ::Size trait

Types implementing Size can be used as indices, loop bounds, and array sizes. Standard implementations include all integer types (i8, i16, i32, i64, u8, u16, u32, u64).

Arithmetic Traits

{ (Self Self -- Self) +: (Self Self -- Self) -: } ::Addable trait

{ (Self Self -- Self) *: (Self Self -- Self) /: (Self Self -- Self) %: } ::Multiplyable trait

{ (Self Self -- Self) ^: } ::Exponentiable trait

{ (Self Self -- Self) logb: (Self -- Self) log: (Self -- Self) ln: } ::Logarithmic trait

Comparison Traits

{ (Self Self -- bool) >: (Self Self -- bool) >=: (Self Self -- bool) <: (Self Self -- bool) <=: } ::Orderable trait

{ (Self Self -- bool) ==: (Self Self -- bool) !=: } ::Equatable trait

// Comparable combines ordering and equality
[ ::Orderable ::Equatable ] ::Comparable inher
{ } ::Comparable trait

Logical Operations Traits

{ (Self -- bool) truthy: (Self Self -- Self) and: (Self Self -- Self) or: (Self -- Self) not: } ::Logical trait

Bitwise Operations Traits

{ (Self Self -- Self) bitand: (Self Self -- Self) bitor: (Self Self -- Self) bitxor: (Self -- Self) bitnot: (Self Size -- Self) shl: (Self Size -- Self) shr: } ::Bitwise trait

Container Traits

{ (Self -- i64) length: } ::Sized trait

{ (Self Size -- T) at: } ::Selectable trait

{ (Self Self -- Self) concat: } ::Concatenable trait

{ (Self Size Size -- Self) slice: } ::Sliceable trait

[ ::Sized ::Selectable ::Sliceable ] ::ArrayOf inher
{ } ::ArrayOf trait

String Traits

[ ::Concatenable ] ::String inher
{ (Self Size Size -- Self) substr: (Self Self -- ArrayOf) split: } ::String trait

Conversion Traits

{ (Self Type -- T) as: } ::Convertible trait

{ (Self -- String) str: } ::Stringifiable trait

{ (String -- Self) parse: } ::Parseable trait

TODO: (FOR HUMAN) Type conversion may need to work a different way?

Numeric Composite Trait

The Number trait represents the full suite of numeric operations by inheriting from multiple traits:

[ ::Addable ::Multiplyable ::Exponentiable ::Comparable ::Logarithmic ] ::Number inher
{ } ::Number trait

Meta-Traits

Traits for defining and working with traits themselves:

{ } ::Identifier trait

{ (TokenString Identifier --) trait: (Identifier TokenString Identifier --) impl: (ArrayOf --) inher: } ::Implementable trait

4.2 Trait Definition

Syntax: { function_signatures } ::identifier trait

Traits can be defined with or without method signatures. Empty traits are valid and are typically used when inheriting from other traits to create composite traits.

// Trait with methods
{
    (Self -- ) draw:
} ::Drawable trait

// Trait with multiple methods
{
    (Self Self -- Self) add:
    (Self Self -- Self) sub:
    (Self -- Self) neg:
} ::Numeric trait

// Generic trait
{
    (Self T -- Self) append:
    (Self -- T) pop:
} ::Container trait

// Empty trait (typically used with inheritance)
{ } ::Printable trait

Within the TokenString (the { } block), identifiers like Self, add:, draw: are part of the trait definition syntax and should not use the :: prefix.

4.3 Trait Implementation

Syntax: identifier { method_implementations } ::identifier impl

// Implement Addable for i32
::Addable {
    (Self Self -- Self) {
        // Native addition implementation
    } +:

    (Self Self -- Self) {
        // Native subtraction implementation
    } -:
} ::i32 impl

// Implement Drawable for Rectangle
::Drawable {
    (Self -- ) {
        "Drawing rectangle" print
        dup ::width get print
        ::height get print
    } draw:
} ::Rectangle impl

Note: The following block has been human verified to be syntactically and logically correct.

// Implement Addable for Point
::Addable {
    (Self Self -- Self) {
        over ::x get over ::x get +
        3 pick ::y get 3 pick ::y get +
        Point
    } +:

    (Self Addable -- Self) {
        over ::x get over +
        3 pick ::y get 3 pick +
        Point
    } +:

    (Addable Self -- Self) {
        over over ::x get +
        3 pick 3 pick ::y get +
        Point
    } +:

    (Self Self -- Self) {
        over ::x get over ::x get -
        3 pick ::y get 3 pick ::y get -
        Point
    } -:

    (Self Addable -- Self) {
        over ::x get over -
        3 pick ::y get 3 pick -
        Point
    } -:
} ::Point impl

// Implement Logical for everything
::Logical {
    (Self -- bool) { true } truthy:

    (Self Self -- Self) {
        over truthy { } { swap } if drop
    } and:

    (Self Self -- Self) {
        over truthy { swap } { } if drop
    } or:
} ::Logical impl

// Overload Logical for bool
::Logical {
    (Self -- Self) { } truthy:
} ::bool impl

// Overload Logical for Numeric
::Logical {
    (Self -- bool) { 0 != } truthy:
} ::Number impl

// Overload Logical for Option
::Logical {
    (Self -- bool) { { Some(_) => { true } None => { false } } match } truthy:
} ::Option impl

// Overload Logical for Result
::Logical {
    (Self -- bool) { { Ok(_) => { true } Err(_) => { false } } match } truthy:
} ::Result impl

4.4 Trait Inheritance

Syntax: [ identifier_list ] ::identifier inher { } ::identifier trait

Trait inheritance must be declared before the trait definition. The inheritance declaration is followed by the trait definition itself, which may be empty if the trait only serves to combine inherited traits.

// Number inherits from multiple arithmetic traits
[ ::Addable ::Multiplyable ] ::BasicNumber inher
{ } ::BasicNumber trait

// Full Number inherits everything numeric
[ ::Addable ::Multiplyable ::Exponentiable ::Comparable ::Logarithmic ] ::Number inher
{ } ::Number trait

// Complex inheritance with additional methods
[ ::Drawable ::Transformable ::Collidable ] ::GameObject inher
{
    (Self -- ) update:
    (Self -- ) destroy:
} ::GameObject trait

// Size trait inherits and defines composite behavior
[ ::Addable ::Comparable ::Convertible ] ::Size inher
{ } ::Size trait

4.5 Using Traits in Functions

// Function requiring Drawable trait
(Drawable -- ) {
    draw
} ::draw_twice fn

// Function requiring multiple trait bounds
(Number Number -- Number) {
    dup * swap dup * + // Pythagorean: a² + b²
} ::sum_of_squares fn

5. Stack Operations

5.1 Stack Manipulation

All stack operations are backed by the Stackable trait.

dup         // ( a -- a a ) Duplicate top [Stackable]
drop        // ( a -- ) Remove top [Stackable]
swap        // ( a b -- b a ) Swap top two [Stackable]
over        // ( a b -- a b a ) Copy second to top [Stackable]
rot         // ( a b c -- b c a ) Rotate three items [Stackable]

5.2 Stack Inspection

depth       // ( -- n ) Push stack depth [Stackable]
pick        // ( n -- x ) Copy nth item to top (0 = top) [Stackable]
roll        // ( n times -- ) Rotate n items, times times [Stackable]

Roll Examples:

// Stack: a b c d e
3 1 roll    // Rotate top 3 items once: a b d e c
3 2 roll    // Rotate top 3 items twice: a b e c d
5 1 roll    // Rotate all 5 items once: b c d e a
4 3 roll    // Rotate top 4 items three times: a d e c b

6. Operators (Postfix)

Every operator is backed by a trait and must be implemented for types that use it.

6.1 Arithmetic

3 4 +       // ( a b -- result ) Addition [Addable]
10 3 -      // Subtraction [Addable]
5 6 *       // Multiplication [Multiplyable]
20 4 /      // Division [Multiplyable]
17 5 %      // Modulo [Multiplyable]
2 8 ^       // Exponentiation [Exponentiable]
100 log     // Log base 10 [Logarithmic]
2.718 ln    // Natural logarithm [Logarithmic]

6.2 Comparison

5 3 >       // Greater than [Orderable]
5 3 >=      // Greater or equal [Orderable]
5 3 <       // Less than [Orderable]
5 3 <=      // Less or equal [Orderable]
5 5 ==      // Equal [Equatable]
5 3 !=      // Not equal [Equatable]

6.3 Logical

true false and    // Logical AND [Logical]
true false or     // Logical OR [Logical]
true not          // Logical NOT [Logical]

6.4 Bitwise

0xFF 0x0F bitand     // Bitwise AND [Bitwise]
0xFF 0x0F bitor      // Bitwise OR [Bitwise]
0xFF 0x0F bitxor     // Bitwise XOR [Bitwise]
0xFF bitnot          // Bitwise NOT [Bitwise]
8 2 shl              // Left shift [Bitwise]
8 2 shr              // Right shift [Bitwise]

7. Functions (Postfix Definition)

Functions are defined in postfix notation. The signature and body come before the name.

7.1 Basic Function Definition

Syntax: (inputs -- outputs) trait_constraint { body } ::name fn

// Define a square function (requires Multiplyable)
(T -- T) ::Multiplyable { dup * } ::square fn

// Use it
5 square      // 25

// Multiple inputs and outputs
(Number Number -- Number Number) {
    over over / swap %
} ::divmod fn

10 3 divmod   // 3 1 (quotient remainder)

7.2 Generic Functions with Trait Constraints

Syntax: (type_sig) trait_constraint { body } ::name fn

// Generic identity - works with any type (no operations, no constraint needed)
(T -- T) { } ::identity fn

// Requires Addable
(T T -- T) ::Addable {
    +
} ::add_values fn

// Requires Number
(T -- T) ::Number {
    dup 0 > { } { 0 swap - } if
} ::abs fn

8. Control Flow (Postfix)

8.1 Conditionals

Syntax: condition { then-block } { else-block } if

Control flow operators (if, while, for, match) parse their TokenString arguments as code blocks.

// if-then (else block is empty)
x 0 > { "positive" print } {} if

// if-then-else
x 0 > 
    { "positive" print } 
    { "non-positive" print } 
if

// The condition comes first, then both blocks, then 'if'
a b > 
    { a } 
    { b } 
if

// Nested
x 0 >
    {
        y 0 >
            { "both positive" print }
            { "x positive, y not" print }
        if
    }
    { "x not positive" print }
if

8.2 Loops

While Loop

Syntax: { condition-block } { body-block } while

// Sum 1 to 10
0 1                              // sum counter
    { dup 10 <= }                // condition block
    {                            // body block
        over over +              // Add counter to sum
        swap 1 + swap            // Increment counter
    }
while
drop                             // Drop counter, leave sum

// Infinite loop with break
{ true } {
    // body
    condition { break } {} if
} while

For Loop

Syntax: start end { body-with-counter } for

// The loop variable is implicitly pushed to stack in each iteration
1 10 { 
    // Stack has loop counter on top
    dup print
} for

// More complex
1 100 {
    dup fizzbuzz
} for

8.3 Loop Control

break       // Exit loop
continue    // Skip to next iteration

8.4 Match/Pattern Matching

Syntax: value { pattern => block ... } match

value {
    Some(x) => { x print }
    None => { "Nothing" print }
} match

// With multiple patterns
status {
    Pending => { "Waiting" print }
    Active => { "Running" print }
    Complete => { "Done" print }
} match

9. Data Structures (Postfix)

9.1 Struct Definition

Syntax: (field_types -- ) { field_names } ::name struct

// Define Point struct - generic over coordinate types
(T T --) { x: y: } ::Point struct

// Use with specific types
3.0 4.0 Point       // Creates Point with f64 fields
3 4 Point           // Creates Point with i64 fields

// More complex struct
(T U V --) { 
    width: 
    height: 
    depth: 
} ::Box3D struct

10.0 20.0 30.0 Box3D

9.2 Struct Field Access

Syntax (postfix): struct ::field get or struct value ::field set

point ::x get         // Get x field
point 15.0 ::x set    // Set x field to 15.0

// Chaining
point ::x get 2 * over ::y get +     // (point.x * 2) + point.y

9.3 Union Definition

Syntax: (variant_types -- ) { variants } ::name union

// Option type - generic over T
(T --) { 
    Some(T) 
    None 
} ::Option union

// Result type - generic over T and E
(T E --) { 
    Ok(T) 
    Err(E) 
} ::Result union

// Create union values
42 Option::Some      // Creates Option::Some(42)
Option::None         // Creates Option::None
"success" Result::Ok // Creates Result::Ok("success")
"error" Result::Err  // Creates Result::Err("error")

9.4 Enum Definition

Syntax: { variants } ::name enum

{ 
    Pending 1: // Normally starts at 0
    Active: // Defaults to 2 (one plus the last)
    Complete 0:
} ::Status enum

// Usage
Status::Pending     // Creates Status::Pending
Status::Active      // Creates Status::Active

10. Memory Management (Postfix)

TODO: (FOR HUMAN) Leave out or redo how memory management is done?

10.1 Heap Operations

// Allocate
3.0 4.0 Point alloc     // ( Point -- ptr<Point> )

// Dereference
ptr deref               // ( ptr<T> -- T )

// Store (dereference and update)
new_value ptr store     // ( T ptr<T> -- )

// Free
ptr free                // ( ptr<T> -- )

10.2 Example

// Create heap-allocated point
3.0 4.0 Point alloc     // Returns ptr<Point>
dup ::x get print       // Dereference and print x
free                    // Clean up

11. Array Operations (Postfix)

11.1 Basic Array Operations

// Creation
1 10 range              // Create range array [1..10]

// Shape operations
arr shape               // Get shape
arr 2 3 reshape         // Reshape to 2x3

// Element access
arr 2 at                // Index access
arr 1 3 slice           // Slice array

11.2 Array Combinators

Array combinators take TokenString arguments containing the function bodies to apply. The if, while, and other control structures inside these function bodies parse their own TokenString arguments.

// Map - apply function to each element
[1 2 3 4] { 2 * } map        // [2 4 6 8]

// Filter - keep elements matching predicate
[1 2 3 4 5] { 2 % 0 == } filter   // [2 4]

// Reduce - fold with function
[1 2 3 4] 0 { + } reduce     // 10

// Each - apply to each element (side effects)
[[1 2] [3 4]] { sum print } each

11.3 Array Arithmetic

[1 2 3] [4 5 6] +.      // Element-wise add: [5 7 9]
[1 2 3] [4 5 6] *.      // Element-wise multiply: [4 10 18]
[1 2 3] 2 *.            // Scalar multiply: [2 4 6]

11.4 Array Manipulation

[1 2 3] [4 5 6] concat  // Concatenate: [1 2 3 4 5 6]
[1 2 3] reverse         // [3 2 1]
[[1 2] [3 4]] transpose // [[1 3] [2 4]]
[1 2 3 4] 2 window      // [[1 2] [2 3] [3 4]]

12. Eval Operator (Postfix)

Execute code dynamically at runtime. The eval operator parses and executes its TokenString argument immediately.

// Evaluate string as code
"2 3 +" eval            // Pushes 5

// Build and execute code
"(T -- T) ::Multiplyable { dup * } ::square fn" eval
5 square                // 25

// Dynamic dispatch
operation_name " get" concat eval

13. Standard Library Concepts

TODO: (FOR HUMAN) How are imports done? Is everything automatically in scope?

13.1 I/O

"Hello" print           // Print to stdout
"Enter name: " input    // Read from stdin
"file.txt" read         // Read file contents
"data" "file.txt" write // Write to file

13.2 String Operations

"hello" " world" concat     // Concatenate: "hello world"
"hello" length              // 5
"hello" 1 3 substr          // "el"
"a,b,c" "," split           // ["a" "b" "c"]
["a" "b"] "," join          // "a,b"

13.3 Type Conversion

TODO: (FOR HUMAN) Type conversion may need to work a different way?

42 f64 as               // Convert i32 to f64
"123" i32 parse         // Parse string to i32
3.14 str as             // Convert to string

14. Complete Examples

14.1 Trait Implementation Example

// Define the Addable trait
{
    (Self Self -- Self) +:
    (Self Self -- Self) -:
} ::Addable trait

// Implement for i32
::Addable {
    (Self Self -- Self) {
        // Native addition
    } +:
    
    (Self Self -- Self) {
        // Native subtraction
    } -:
} ::i32 impl

// Implement for Point
::Addable {
    (Self Self -- Self) {
        over ::x get over ::x get +
        swap ::y get swap ::y get +
        Point
    } +:
    
    (Self Self -- Self) {
        over ::x get over ::x get -
        swap ::y get swap ::y get -
        Point
    } -:
} ::Point impl

14.2 Trait Inheritance Example

// Define base traits
{ (Self Self -- Self) +: (Self Self -- Self) -: } ::Addable trait
{ (Self Self -- Self) *: (Self Self -- Self) /: } ::Multiplyable trait
{ (Self Self -- Self) ^: } ::Exponentiable trait
{ (Self Self -- Self) logb: (Self -- Self) log: (Self -- Self) ln: } ::Logarithmic trait

// Number inherits from multiple traits
[ ::Addable ::Multiplyable ::Exponentiable ::Comparable ::Logarithmic ] ::Number inher
{ } ::Number trait

14.3 Logarithm Usage

// Calculate log base 10
100 log print       // 2.0
1000 log print      // 3.0

// Calculate natural logarithm
2.718 ln print      // ~1.0
7.389 ln print      // ~2.0

// Combine with other operations
10 3 ^ log print    // 3.0 (log of 1000)

14.4 Factorial

(T -- T) ::Number {
    dup 1 
        { drop 1 } 
        { dup 1 - factorial * } 
    <= if
} ::factorial fn

5 factorial print       // 120

14.5 FizzBuzz

(T -- ) ::Number {
    dup 15 % 0 ==
        { drop "FizzBuzz" print }
        {
            dup 3 % 0 ==
                { drop "Fizz" print }
                {
                    dup 5 % 0 ==
                        { drop "Buzz" print }
                        { print }
                    if
                }
            if
        }
    if
} ::fizzbuzz fn

1 100 { fizzbuzz } for

14.6 Using Roll

// Stack: 1 2 3 4 5
3 1 roll    // Rotate top 3 once: 1 2 4 5 3
3 2 roll    // Rotate top 3 twice: 1 2 5 3 4

// More complex example
10 20 30 40 50
4 2 roll    // Rotate top 4, twice: 10 30 40 50 20

14.7 Array Processing

// Sum of squares of even numbers from 1 to 10
[1 2 3 4 5 6 7 8 9 10]
    { 2 % 0 == } filter     // Keep even numbers
    { dup * } map           // Square each
    0 { + } reduce          // Sum
print                       // 220

14.8 Identifier Literals in Practice

// Push identifier literal to stack
::Point                     // Pushes identifier "Point"

// Use with trait definition
{ (Self -- ) draw: } ::Drawable trait

// Use with struct definition
(T T --) { x: y: } ::Point struct

// Dynamic trait implementation
::MyType { ... } ::MyTrait impl

15. Syntax Summary

Complete Grammar Patterns

Functions: (in -- out) trait_constraint { body } ::name fn

Structs: (types -- ) { fields: } ::name struct

Unions: (types -- ) { Variant(T) ... } ::name union

Enums: { Variant value: ... } ::name enum

Traits: { (sig) method: ... } ::identifier trait

Trait Impl: ::identifier { (sig) { body } method: ... } ::identifier impl

Trait Inheritance: [ identifier_list ] ::identifier inher { } ::identifier trait

If: condition { then } { else } if

While: { condition } { body } while

For: start end { body } for

Match: value { pattern => block ... } match

Identifier Literal: ::name pushes identifier instead of executing


Version: 0.5
Status: Draft Specification