erick-alcachofa f3cc5b90c8
chore: Fix naming of wiki source file
Signed-off-by: erick-alcachofa <erick@artichoke.dev>
2025-10-02 00:26:35 -06:00

7.4 KiB

The artichoke Programming Language: A Technical Overview

1. Introduction

artichoke is a statically-typed, general-purpose programming language designed with an emphasis on performance, safety, and expressive syntax. It combines low-level control over memory with modern, high-level features like generics, algebraic data types, and integrated error handling. This document provides an overview of the language's features as defined by its core grammar.

Is highly inspired by C, C++, Rust, and mostly Zig.

2. Basic Syntax & Structure

Modules, Imports, and Aliases

artichoke code is organized into modules. The import statement is used to bring symbols from other modules into the current scope.

  • Importing a specific element: import my_module::some_function;
  • Importing all direct elements of a module: import std::*;
  • Importing an entire submodule: import std::memory;

The using keyword creates a local, more convenient alias for a type, function, or module name.

using mem = std::memory;
using FileHandle = std::fs::File;

Comments

The language uses C-style block comments.

/* This is a multi-line
   comment. */

3. The Type System

artichoke's type system is strong and static, with a rich set of features for defining complex data structures.

Type Qualifiers

Qualifiers modify the type to their immediate right, allowing for precise and complex type definitions.

  • * (Pointer): Creates a pointer to a type. Pointers cannot be null.
  • $ (Mutable): Marks a type as mutable. This is used for function parameters, local variables, and struct fields to allow modification.
  • ? (Optional): Marks a type as nullable. An optional type can hold either a value of its underlying type or null.
  • [] (Slice): A "fat pointer" representing a view into a contiguous sequence of elements. It contains both a pointer to the data and a length.

These qualifiers can be combined. For example, *$?int defines a pointer to a mutable optional integer.

Generics

Generics allow for writing flexible, reusable code that can operate on multiple types. They are defined using <typename T>.

/* A generic struct */
struct Point<typename T> {
    x: T,
    y: T
}

/* A generic function */
fn scale<typename T>(lhs: *Point<T>, rhs: T) -> Point {
    /* ... */
}

4. Declarations

Variables

Variables are declared using the let (mutable) and def (immutable/constant) keywords.

  • Type inference is supported when the type can be determined from the initializer.
  • Variables must be initialized with either a type, a value, or both.
/* Mutable variable with explicit type */
let x: i32 = 10;

/* Immutable variable with type inference */
def do_you_get_it = meaning_of_life();

Structs

Structs are composite data types that group together variables under one name. They support generics.

struct Rectangle {
    top: Point<i32>,
    bot: Point<i32>
}

Initialization: Structs can be initialized using positional or named fields, but not a mix of both.

/* Positional initialization */
def top_left = Point<i32>{ 0, 10 };

/* Named-field initialization */
def top_right = Point<i32>{ x: 10, y: 10 };

Enums (Tagged Unions)

Enums define a type that can be one of several different variants. Variants can optionally hold data.

enum AssetType {
    Texture,
    Model,
    Sound,
}

enum Result<typename T, typename E> {
    Ok(T),
    Err(E)
}

Initialization: Enum variants are accessed using scope resolution (::).

def my_asset = AssetType::Texture;
def success = Result<i32, string>::Ok(100);

Functions

Functions are defined with the fn keyword. The return type is specified after the parameter list with ->.

fn meaning_of_life() -> i32 {
    return 42;
}

Member Functions (this parameter)

If the first parameter of a function is declared with the this keyword, it can be called using "member function" syntax.

/* Definition */
fn add<typename T>(this *$Point<T>, other: *Point<T>) {
    this->x += other->x;
    this->y += other->y;
}

/* Can be called in two ways: */
/* Member function syntax */
my_point.add(&other_point);

/* Normal function syntax */
add(&my_point, &other_point);

5. Control Flow

if/else Statements

artichoke supports C-style if/else and else if chains. It also integrates a powerful unwrapping feature for handling Result and optional (?) types.


/* Standard if/else */
if (argc < 2) {
    return Result::Err(-1);
}

/* Unwrapping a Result */
if (foo()) |ok| {
    /* `ok` holds the success value */
}
else |err| {
    /* `err` holds the error value */
}

Loops

The language provides a comprehensive set of looping constructs.

  • C-Style for: for (let i \= 0; i \< 10; i \+= 1\) { ... }
  • Range-based for: for (let e := arrSlice) { ... }
  • while Loop: Can optionally have an else block that executes when the loop condition is no longer met.
  • Iterator while: Supports unwrapping Result/optional types, executing as long as the value is valid.
  • do-while Loop: Guarantees the body executes at least once.
  • Infinite loop: loop { ... }

Loop Labels and Control

Loops can be labeled. The break and continue statements can optionally specify a label to control nested loops.

outer_loop := while (condition) {
    inner_loop := for (...) {
        break outer_loop;
    }
}

6. Expressions and Operators

Pointer and Member Access

  • & (Address-of): Gets a pointer to a variable.
  • * (Dereference): Accesses the value a pointer points to.
  • . (Member Access): Accesses a member of a struct value.
  • -> (Pointer Member Access): Dereferences a pointer and accesses a member (p->x is shorthand for (*p).x).

Slice Operators

Slices have a dedicated set of operators for manipulation.

  • [start:end] (Slicing): Creates a new slice from an existing one.
  • .* (Pointer Access): Gets the underlying raw pointer of the slice.
  • .# (Length Access): Gets the number of elements in the slice.
  • .[length] (Slice from Pointer): Creates a slice from a raw pointer and a length.

Assignment

The language supports simple (=) and compound assignment (+=, *=, etc.) operators.

7. Advanced Features

Resource Management (defer and errdefer)

artichoke uses defer for deterministic resource management.

  • defer: Schedules an expression or code block to be executed when the current scope is exited. Deferred calls are executed in Last-In, First-Out (LIFO) order.
  • errdefer: Similar to defer, but the code is only executed if the scope is exited due to a function returning an error (an Err variant of a Result).
defer call_cleanup();

errdefer {
    log("An error occurred!");
}

Reflection (.@)

The language provides a compile-time reflection mechanism via the .@ operator. It can be applied to values, types, and static members to query metadata.

  • On values: my_variable.@type
  • On types: Point<u32>.@size, Point<u32>.@alignment
  • On static members: Point<u32>::x.@offset
/* Gets size in bytes */
def size_bytes = Point<u32>.@size;

/* Gets string representation of the type */
def point_name = Point<u32>.@typename;