artichoke-lang/docs/example.arti
erick-alcachofa 8f15650a42
docs: add comprehensive language overview and syntax example
Signed-off-by: erick-alcachofa <erick@artichoke.dev>

Introduces 'docs/example.arti' as an exhaustive showcase of the
`artichoke` programming language syntax and grammar.

This file serves as a technical specification and "living documentation"
for the language.

Key features demonstrated include:

- Module imports and symbol aliasing (using).
- Generic definitions vs. Turbofish (::<>) instantiations.
- Designated initializers (.field = expr) and member functions (this).
- Tagged enums and explicit error handling via Result<T, E>.
- Pointer (*), mutability ($), and optional (?) type qualifiers.
- Slice syntax, array literals, and specialized suffixes (.*, .#, .[]).
- Advanced control flow: Result/Optional unwrapping (|val|) and labeled
  loops.
- Compile-time reflection using the .@ operator.
2025-12-28 13:43:51 -06:00

241 lines
8.2 KiB
Plaintext

/* ============================================================================
* `artichoke` - Language Overview & Technical Specification
* ============================================================================
* This file serves as an exhaustive showcase of the Artichoke syntax and
* grammar rules as of the 2025 parser-stabilization phase.
*/
/* * --- Imports ---
* Imports allow the compiler to pull symbols from other modules into the
* current scope.
*/
import std::memory; /* Import the entire 'memory' module from 'std' */
import std::*; /* Wildcard: Import all direct child elements of 'std'.
Note: This does not recursively import submodules. */
import my_module::some_function; /* Import a specific function symbol */
import my_module::some_typename; /* Import a specific type symbol */
/* --- Type & Symbol Aliasing ---
* The `using` keyword creates an alias for an existing symbol. This is
* useful for shortening long namespaced paths or creating domain-specific
* names for primitive types.
*/
using mem = std::memory;
using malloc = mem::mem_alloc;
using my_type = my_module::some_typename;
using my_func = my_module::some_function;
/* --- Structs & Generics (Definition) ---
* Generics at the definition level use a "clean" `<typename T>` syntax.
* The Turbofish is NOT used here, only at the call site/instantiation.
*/
struct Point<typename T> {
x: T,
y: T
}
struct Rectangle {
/* Usage of a generic type REQUIRES the Turbofish `::<>`.
This disambiguates between the 'less-than' operator and a generic list
within the Pratt expression parser. */
top: Point::<i32>,
bot: Point::<i32>
}
/* --- Functions ---
* Return types follow the `->` operator.
*/
fn meaning_of_life() -> i32 {
return 42;
}
/* --- Named Initializers ---
* Structs/Objects are initialized using the designated initializer syntax:
* `.field = expression`. This makes the code self-documenting and order-independent.
*/
fn scale<typename T>(lhs: *Point::<T>, rhs: T) -> Point::<T> {
return Point::<T> {
.x = lhs->x * rhs,
.y = lhs->y * rhs
};
}
/* --- Member Functions (Methods) ---
* A function becomes a "member function" if the first parameter follows
* the specific syntax: `this` <type>.
* Note: No colon is required after the `this` keyword.
* These can be called as: `expr.fn_name(params...)`
* or as standard functions: `fn_name(expr, params...)`.
*/
fn add<typename T>(this *Point::<T>, other: *Point::<T>) {
this->x += other->x;
this->y += other->y;
}
/* --- Tagged Enums (Sum Types) ---
* Enums can hold data. The standard library provides a `Result<T, E>`
* for robust, explicit error handling.
*/
enum Result<typename T, typename E> {
Ok(T),
Err(E)
}
/* --- The Main Entry Point ---
* Slices are represented as `[]type`. Argv here is a slice of slices of chars.
*/
fn main(argc: i32, argv: [][]char) -> Result::<void, i32> {
if (argc < 2) {
/* To initialize an enum with data, use an positional initializer. */
return Result::<void, i32>::Err{ -1 };
}
/* --- Variable Declarations ---
* `let` -> Mutable variable (can be reassigned).
* `def` -> Constant variable (immutable after initialization).
* Type inference is supported when the right-hand side is unambiguous.
*/
let x: i32 = 10;
let do_you_get_it = meaning_of_life();
def PI: f64 = 3.14159265358979;
/* --- Pointers, Mutability, and Optionals ---
* The following type qualifiers are used to "extend" the types
* `*` -> Pointer (Non-nullable by default).
* `$` -> Mutability qualifier.
* `?` -> Nullable/Optional qualifier.
* Qualifiers apply to the immediate element to their right.
*/
def ptr: *i32 = &do_you_get_it; /* Const pointer to immutable i32 */
let mutable_pointer: *$i32 = &x; /* Mutable pointer to mutable i32 */
/* A mutable (`let`) pointer (*) to a mutable pointer ($*) to a mutable i32 ($i32) */
let complex_pointer: *$*$i32 = &mutable_pointer;
/* Nullable types allow the `null` literal.
Mixing qualifiers allows for complex types like "Nullable pointer to mutable i32". */
let null_int: ?i32 = null;
def nullable_float: ?f32 = PI;
/* --- Slices and Array Literals ---
* Slices are the primary way to handle contiguous memory.
* `<type> { ... }` is an Type-ObjectInitiated Literal (Anonymous construction).
* If the type is an slice type, then is a standard Array Literal.
*/
let arrSlice: ?[]i32 = []i32 { 2, 4, 6, 8, 10 };
/* --- Slicing Suffixes ---
* `artichoke` supports Python-like slicing with optional boundaries.
*/
let full = arrSlice[:]; /* Full range copy (can be omitted) */
let range = arrSlice[1:3]; /* From index 1 to 2 (exclusive of 3) */
let head = arrSlice[:2]; /* Everything before index 2 */
let tail = arrSlice[2:]; /* Everything from index 2 to the end */
/* --- Specialized Suffix Operators ---
* `.*` -> Unwraps a slice into its raw underlying pointer.
* `.#` -> Retrieves the length of the slice.
* `.[length]` -> Convers a raw pointer into a slice of the given length.
*/
def memPtr = arrSlice.*;
def memLength = arrSlice.#;
def newSlice = memPtr.[memLength];
/* --- Control Flow & Error Unwrapping ---
* If statements can "unwrap" Result types using the `|val|` syntax.
*/
if (foo()) |ok_val| {
/* If foo() returned Ok(T), ok_val is available here as type T */
} else |err_val| {
/* If foo() returned Err(E), err_val is available here as type E */
}
/* --- While patterns ---
* While statements can also "unwrap" Result types using the `|val|` syntax.
* The while look will continue running until an Err result is resturned
* which will be unrapped into the `err_val` variable if specified.
*/
while (foo()) |ok_val| {
/* If foo() returned Ok(T), ok_val is available here as type T and the loop
* will continue */
} else |err_val| {
/* If foo() returned Err(E), err_val is available here as type E */
}
/* Normal condition based while loops are also supported
* The other powerful while pattern that `artichoke` provides is a "iterator"
* based while loop, if the return type of the expression provided at the
* condition returns an optional (`?`), then it can unraps the variable by
* using the `|val|` syntax and will continue until an empty optional value is
* returned.
*/
while (foo.next()) |ok_val| {
/* If foo() returned Ok(T), ok_val is available here as type T and the loop
* will continue */
}
/* --- Match & Switch ---
* `match` -> Used for complex Pattern Matching (Types, Enums).
* `switch` -> Used for Value-based branching.
* Both must be exhaustive or include a default case `_`.
*/
match (foo()) {
Result::<i32, []u8>::Ok |v| -> {
std::io::print("Success!");
}
_ -> { /* Default case */ }
}
/* --- Loops & Labels ---
* Loops can be named using the `name :=` syntax to allow breaking/continuing
* from nested contexts.
* The loop types that `artichoke` provides are
* * `while` loops
* * `do-while` loops
* * `for` loops with a C-style syntax
* * range `for` loops
*/
outer_loop := while (condition) {
inner_loop := for (let i = 0; i < 10; i += 1) {
if (i == 5) { break outer_loop; }
}
}
/* `do-while` loop, no else branch is allowed here */
do {
/* Statements */
} while (true);
/* `for` loop with a C-style syntax */
for (let i = 0; i < 10; i += 1) {
/* Statements */
}
/* range `for` loop, type is deduced from expression */
for (let element := returns_range_function()) {
/* Statements */
}
/* * --- Resource Management ---
* `defer` -> Executes at the end of the scope in reverse order.
* `errdefer` -> Executes only if the function returns an Err variant.
*/
defer cleanup();
errdefer { log_failure(); }
/* * --- Compile-Time Reflection ---
* The `.@` operator provides access to metadata.
*/
/* Returns an struct with compile time information */
def refl_info = foo.@;
/* Specific properties like alighment, size, offset, name are allowed too */
def xalign = Point::<u32>::x.@alignment;
def type_size = Point::<u32>.@size;
return Result::<void, i32>::Ok{};
}