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.
241 lines
8.2 KiB
Plaintext
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{};
|
|
}
|