{"id":22787496,"url":"https://github.com/acquitelol/elle","last_synced_at":"2025-04-15T23:39:40.299Z","repository":{"id":229243467,"uuid":"776203469","full_name":"acquitelol/elle","owner":"acquitelol","description":"A procedural programming language built in Rust and QBE","archived":false,"fork":false,"pushed_at":"2024-11-29T09:16:27.000Z","size":703,"stargazers_count":21,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"rewrite","last_synced_at":"2024-11-29T21:56:39.488Z","etag":null,"topics":["c-style-lang","compiler","educational","elle","experimental","language","lexer","lexical-analysis","procedural","qbe","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/acquitelol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-22T22:23:43.000Z","updated_at":"2024-11-29T09:16:30.000Z","dependencies_parsed_at":"2024-03-23T00:40:53.412Z","dependency_job_id":"0d473493-a27b-4c11-b8be-ebaeea838b06","html_url":"https://github.com/acquitelol/elle","commit_stats":null,"previous_names":["acquitelol/elle"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acquitelol%2Felle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acquitelol%2Felle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acquitelol%2Felle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acquitelol%2Felle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acquitelol","download_url":"https://codeload.github.com/acquitelol/elle/tar.gz/refs/heads/rewrite","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229313606,"owners_count":18053714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-style-lang","compiler","educational","elle","experimental","language","lexer","lexical-analysis","procedural","qbe","rust"],"created_at":"2024-12-12T00:57:38.433Z","updated_at":"2025-04-15T23:39:40.287Z","avatar_url":"https://github.com/acquitelol.png","language":"Rust","funding_links":[],"categories":["⭐ Top Extensions","Uncategorized"],"sub_categories":["Uncategorized"],"readme":"# ₊˚ Elle ♡︎\n\n### A procedural programming language built in Rust and QBE\n\n‎ ‎ ╱|、\n\u003cbr /\u003e\n(˚ˎ 。7\n\u003cbr /\u003e\n|、˜〵\n\u003cbr /\u003e\nじしˍ,)ノ\n\u003cbr /\u003e\n\n### ♡ **How is this better than C?**\n\n- It's not. It never will be. As it stands, this is a project developed by a single person, me. I am neither smart enough nor efficient enough to mimic an enterprise programming language compiler such as clang.\n- Elle does, however, provide deferring, generic types, methods on structs (allowing for OOP-like semantics), pseudo-namespaces, function call metadata, custom allocators, a built-in GC, type inference, and more. There are still many issues with the parser, compiler, and there is a huge lack of optimisations, but you may view these features as an improvement to C.\n\n### ✩ _If you like this project, consider giving it a star!_ ✩\n\n### ♡ **Hello, World!**\n\nWriting a hello world program in Elle is super easy:\n\n```rs\nuse std/io;\n\nfn main() {\n    io::println(\"Hello world!\");\n}\n```\n\nLet's dissect the code:\n\n- The `fn` keyword declares the statement as a function declaration.\n- The word `main` is the function's name and defines the function as the entry point of our program.\n- The function call `io::println` is a function which prints all of its arguments using their formatter.\n\n- Simple enough! ♡\n\n\u003chr /\u003e\n\n### ♡ **Variable declarations**\n\n- Variables can be declared in 3 ways:\n\n  - Using their type (useful for inference of information based on the left-hand side like generics)\n  - Using let (useful for inferring based on the right-hand side)\n  - Using the walrus operator `:=` (cannot be used with a type, equivalent to `let`)\n\n- Example:\n\n```rs\nlet a = 0; // a is inferred to be i32 because 0 is i32\ni64 a = 5; // 5 is inferred to be i64 because a is i64\na := 0;    // acts the same as `let a = 0;`\n```\n\nThis is especially useful for dynamic array declarations:\n\n```rs\narr := [i64;];\ni64[] arr = [];\n```\n\n\u003chr /\u003e\n\n### ♡ **If statements**\n\n- An if statement is an expression that evaluates a block if the condition is non-zero, with an optional `else` block which is evaluated if the condition **is** zero.\n\n- You can define an `if` statement and then an optional `else` statement\n- If statement conditions can be wrapped in `()` but this is not mandatory\n- Example:\n\n```rs\na := 0;\n\nif expression {\n    a += 1;\n} else {\n    a -= 1;\n}\n```\n\n```rs\na := 1;\n\nif a == 1 {\n    $println(\"hello world\");\n} else if a == 2 {\n    $println(\"foo bar baz\");\n} else {\n    $println(\"test\");\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **While loops**\n\n- A while loop is an expression that evaluates the block specified **only** if the condition is non-zero, otherwise breaks and continues execution on the primary branch.\n\n- Even though you can loop via recursion, the while loop primitive may be simpler to understand and use in many cases, therefore it is provided in Elle.\n- While loop expressions can be wrapped in `()` but this is not mandatory\n- There is no `do while` or `finally` functionality at the time of writing this.\n- Example:\n\n```rs\nwhile expression {\n    // do code\n}\n```\n\n- You also have access to block scoped variables inside of this loop. This means you can create a pseudo `for loop` with the following code:\n\n```rs\nlet i = 0;\n\nwhile i \u003c 10 {\n    io::println(i);\n    i += 1;\n}\n```\n\nPlease keep in mind that you also have access to the `break` and `continue` keywords while inside of a loop, which break execution early or continue to the next iteration respectively.\n\n\u003chr /\u003e\n\n### ♡ **For loops**\n\n- A for loop is an expression that has 3 main parts:\n\n1. Variable declaration - Declaring an iterator to be used in the loop\n2. Condition - The condition to break out of the loop\n3. Variable step - The amount that the variable should increase on each iteration.\n\nEssentially, the loop creates the variable defined in (1), and evaluates the block (code) specified, aswell as (3), until the condition defined in (2) is false (zero), when it returns to the main branch and continues execution.\n\n- For loop expressions can be wrapped in `()` but this is not mandatory\n- Basic example of a for loop that prints the digits 0-9 to the stdout:\n\n```rs\nfor i32 i = 0; i \u003c 10; i += 1 {\n    io::println(i);\n}\n```\n\n- More advanced example:\n\n```rs\nuse std/io;\n\nfn fact(i64 n) -\u003e i64 {\n    if n \u003c= 1 {\n        return 1;\n    }\n\n    return n * fact(n - 1);\n}\n\nfn get_e() {\n    f64 res = 0.0;\n\n    for i64 i = 0; i \u003c 50; i += 1 {\n        res += 1.0 / fact(i);\n    }\n\n    return res;\n}\n\nfn main() {\n    f64 e = get_e();\n    $dbg(e);\n}\n```\n\nPlease keep in mind that you also have access to the `break` and `continue` keywords while inside of a loop, which break execution early or continue to the next iteration respectively.\n\n\u003chr /\u003e\n\n### ♡ **Foreach loops**\n\n- A foreach loop is an expression that has 2 main parts:\n\n1. Variable declaration - Declaring a variable for each element\n2. Iterator - The iterator value (which must have a `__len__` function defined on its type)\n\n- Example:\n\n```rs\nfor x in [\"a\", \"b\", \"c\"] {\n    io::println(x);\n}\n```\n\n- Any iterable type can be used as an iterator:\n\n```rs\nfor c in \"hello world\" {\n    $dbg(c);\n}\n\nfor i in 0..100 {\n    $dbg(i);\n}\n```\n\nYou can also access the current index during a `foreach` loop, no enumeration necessary:\n\n```rs\nfor x in [1, 2, 3] {\n    $dbg(#i(x), x);\n}\n```\n\nYou can also assign to this variable if you need to (such as stepping by 2):\n\n```rs\nfor x in [1, 2, 3, 4] {\n    $dbg(#i(x), #i(x) + 1, x);\n    #i(x) += 1; // Will now increment by 2 because there is an implicit increment each iteration\n}\n```\n\nPlease keep in mind that you also have access to the `break` and `continue` keywords while inside of a loop, which break execution early or continue to the next iteration respectively.\n\n\u003chr /\u003e\n\n### ♡ **Standalone blocks**\n\n- A standalone block is somewhat equivalent to an `if true` statement, although they are not implemented exactly the same internally. It creates a block of code that is executed on a seperate \"branch\" to the main code in the function. This means that if you run something like `defer` inside of a standalone block it would call that when the _standalone block_ leaves scope, not the function itself.\n\nHere's a simple example:\n\n```rs\nfn main() {\n    let a = 0;\n\n    {\n        a += 1;\n        // If we do *something* here like calling defer then\n        // the defer would run when this block leaves its scope\n    }\n}\n```\n\nThis block has a different scope, which means you can declare variables with the same name but a different type in it. You can learn more about this in the `Variable Shadowing` section.\n\nAnd it is relatively clear how this code is essentially equal to:\n\n```rs\nfn main() {\n    let a = 0;\n\n    if true {\n        a += 1;\n        // If we do *something* here like calling defer then\n        // the defer would run when this block leaves its scope\n    }\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Variable Shadowing**\n\nVariable shadowing is when the variable defined in the previous scope is accessible in the current scope.\n\nFor example:\n\n```rs\nfn main() {\n    x := 1;\n\n    {\n        // should x exist here? yes\n        // whats its value? its 1\n        $assert(x == 1, nil);\n\n        // what if we redeclare it?\n        x = 2;\n\n        // now whats its value? its 2\n        $assert(x == 2, nil);\n\n        // what if we declare a new x?\n        x := 3;\n\n        // now whats its value? its 3\n        $assert(x == 3, nil);\n    }\n\n    // now the scope ended, what should this x be?\n    // well it should be 2 because the x in this scope was redeclared to 2\n    // the newly-declared x in that scope doesnt exist in this scope\n    $assert(x == 2, nil);\n}\n```\n\nMore complex example\n\n```rs\nfn main() {\n    x := \"foo\"; // x is \"foo\"\n\n    x := 1; // x is 2, it changes type!\n\n    {\n        x = 2; // x is 2\n        x := \"a\"; // x is \"a\", now string again\n\n        {\n            x = \"b\"; // x is \"b\"\n            // note: no := usage, so modifies previous scope's x\n        }\n\n        // x is \"b\" here\n    }\n\n    // x is 2 here\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Function Metadata**\n\n- Elle can provide you with extra metadata using the `ElleMeta` struct.\n\nThis is done by ensuring the 0th argument of your function has the type `ElleMeta`.\n\u003cbr /\u003e\nThe compiler will automatically supply the struct to you when the function is called, you do not need to manually pass it to the function.\n\nThis struct is not defined in Elle code, however its equivalent structure may look like:\n\n```rs\nstruct ElleMeta {\n    string *exprs; // An array of every argument's expression passed to the function as a string\n    string *types; // An array of the type of every argument supplied to the function\n    i32 arity;     // The number of arguments. This does NOT include the ElleMeta argument.\n    string caller; // The caller of the function as a string\n    string file;   // The file where the function was called from\n    i32 line;      // The line number of the function call + 1\n    i32 column;    // The column number of the function call + 1\n};\n```\n\n\u003e [!IMPORTANT]\n\u003e You do not need to supply the structure yourself. This is automatically managed by the compiler.\n\nThis means that here:\n\n```rs\nfn square(i32 a) {\n    return a * a;\n}\n\nfn main() {\n    i32 res = square(5);\n}\n```\n\n`square` will not be passed `ElleMeta`.\n\nHowever, here:\n\n```rs\nfn square(ElleMeta meta, i32 a) {\n    return a * a + meta.arity;\n}\n\nfn main() {\n    i32 res = square(5);\n}\n```\n\n`square` will be passed `ElleMeta`. Please notice how it is NOT passed by the caller. It is automatically passed by the compiler if it is required.\n\n\u003chr /\u003e\n\n### ♡ **Allocators**\n\nElle has a moderately complicated allocator system. Here's how it works:\n\n- By default:\n  - garbage collection\n- Using the `--nogc` flag at compilation:\n  - arena-based allocation\n\n#### **Changing the allocator:**\n\n```rs\n#set_allocator(MyAllocator::new());\n```\n\n#### **Resetting to the default allocator:**\n\n```rs\n#set_allocator(#env.default_allocator);\n```\n\nOR\n\n```rs\n#reset_allocator();\n```\n\n(These are equivalent expressions.)\n\n\u003e [!IMPORTANT]\n\u003e Make sure you don't forget to free any memory leftover when switching allocator! `#set_allocator` does **not** call the `free_self` method on the previous allocator when switching allocator, to allow for programs designed like this:\n\n```rs\nfn main() {\n    arena := ArenaAllocator::new();\n    #set_allocator(arena);\n    x := [1, 2, 3]; // x is allocated through the ArenaAllocator\n    #reset_allocator();\n    $println(x); // allocates via default allocator\n    #set_allocator(arena);\n    #env.allocator.free_self(); // frees the arenas\n    #reset_allocator(); // go back to default allocator\n}\n```\n\n#### **What should an allocator have defined on it?**\n\n- Allocators should have the following methods defined on them:\n  - `MyAllocator::new()` (preferrably allocating the allocator structure itself via `mem::malloc`)\n  - `MyAllocator::alloc(MyAllocator *self, i32 size) -\u003e void *` (size in bytes to allocate, should return `void *`)\n  - `MyAllocator::realloc(MyAllocator *self, void *ptr, i32 new_size) -\u003e void *` (new_size in bytes. should return `void *`)\n  - `MyAllocator::free(MyAllocator *self, void *ptr)` (frees a specific object passed by pointer, may be omitted if permitted by the allocation model, will become `noop`)\n  - `MyAllocator::free_self(MyAllocator *self)` (destructor for the allocator itself including all of its allocations, **NOT** objects created by it)\n\n#### **Disabling allocation altogether:**\n\n- You can pass the `--noalloc` flag during compilation. Keep in mind that, while this will no longer define allocators, this means you won't be able to use almost any of the Elle standard library, as all of it depends on these allocators. This flag goes well with the `--nostd` flag.\n\n\u003chr /\u003e\n\n### ♡ **Dynamic memory allocation**\n\n- Elle has a notion of a `#env` directive which gives you an `ElleEnv *`.\n\nThis structure is also not defined in Elle code (like `ElleMeta`), but its equivalent structure may look like:\n\n```rs\nstruct ElleEnv {\n    ArbitraryAllocator *allocator;\n    TAllocator *allocator;\n};\n```\n\n(where `TAllocator` is either `GCAllocator` or `ArenaAllocator` depending on your compilation configuration.)\n\nThe allocator is completely abstracted away from you, which means that depending on the allocator, certain methods may not be set. They will be set to a `noop` function instead which returns `nil`.\n\nTypically, you should be safe to assume that you have `#alloc` and `#realloc`. In specific environments you can also assume you have `#free`, but this usually set to a `noop`.\n\nBy default, memory deallocation is managed by the compiler via garbage collection. You can disable this by adding the `--nogc` flag, which will switch to using an `ArenaAllocator` model instead. If you prefer to manually manage memory altogether, you can either:\n\n```rs\n// Add this flag to your compilation command\n// which completely stops custom allocators.\n\n// You can now use mem::malloc, mem::free, etc\n//\n// Keep in mind that you will not be able to\n// use most standard library features with\n// allocators disabled.\n--noalloc\n```\n\nOR\n\n```rs\n// Import the heap allocator\nuse std/allocators/heap;\n\n// And use it in your main function\n#set_allocator(HeapAllocator::new());\n\n// Now #alloc will call malloc, keeps type QOL features\nptr := #alloc(i32, 5); // same as mem::malloc(#size(i32) * 5)\n#free(ptr); // same as mem::free(ptr);\n```\n\n\u003e [!IMPORTANT]\n\u003e Standard library functions do not free their memory because of the assumption of an auto-freeing allocator. If you use standard library functions with manual memory management, expect memory leaks.\n\n\u003chr /\u003e\n\nExample of using dynamic memory allocation:\n\n```rs\nstruct Foo {\n    i32 a;\n};\n\nfn Foo::new(i32 a) {\n    foo := #alloc(Foo);\n    foo.a = a;\n    return foo;\n}\n\nfn main() {\n    let foo = Foo::new(10);\n    $dbg(foo);\n}\n```\n\nAnother example:\n\n```rs\nfn main() {\n    // allocate space for 10 integers\n    i32 *numbers = #alloc(i32, 10);\n    numbers[1] = 39;\n\n    $dbg(numbers[1]); // 39\n    // dont need to free it\n}\n```\n\nKeep in mind that you can also use the libc standard manual memory management functions, like `malloc`, `realloc`, and `free`. These methods are defined in `std/libc/mem`. These allocations will **not** be freed automatically because the garbage collector isn't tracking them.\n\nThe compiler also provides you handy builtins for easy and quick allocation: `#alloc` and `#realloc`. As these builtins take a _type_ and not the _size of a type_ they can actually evaluate to exactly `T *` instead of `void *` when called. This means you can write this:\n\n```rs\nlet x = #alloc(i32, 5); // x -\u003e i32 *\n```\n\nwithout needing to explicitly convert anywhere.\n\n`#alloc` uses the form of `#alloc(T, size?)` where `T` is any type and `size?` is an optional count (similar to `calloc` behavior), which can be omitted to form just `#alloc(T)`.\n\n`#realloc` uses the form of `#realloc(ptr, T, size?)` where `ptr` is any expression that evaluates to a pointer, `T` is any type and `size?` is an optional count (similar to `calloc` behavior), which can be omitted to form just `#realloc(ptr, T)`.\n\nExample usage:\n\n```rs\nuse std/prelude;\n\nstruct Foo {\n    i32 a;\n};\n\nfn Foo::new(i32 a) {\n    let foo = #alloc(Foo);\n    foo.a = a;\n    return foo;\n}\n\nfn main() {\n    let foo = Foo::new(6);\n    $dbg(foo);\n}\n```\n\nUsing these directives, you can turn a verbose expression such as:\n\n```rs\nMachine *machine = #env.allocator.alloc(#size(Machine));\n```\n\ninto the (much) cleaner:\n\n```rs\nmachine := #alloc(Machine);\n```\n\n\u003chr /\u003e\n\n### ♡ **Variadic Functions**\n\n- A variadic function is a function that can take in a variable amount of arguments. This works similar to C except that Elle provides you with mechanisms to make this much nicer to use, both as the producer and consumer of the function.\n\nHere's a basic example of a variadic function which takes in any amount of arguments and returns their sum:\n\n```rs\nfn add(ElleMeta meta, ...args) {\n    res := 0;\n\n    for i := 0; i \u003c meta.arity; i += 1 {\n        res += args.yield(i32);\n    }\n\n    return res;\n}\n```\n\nAt the call-site, using this function is easy. It can be done like this:\n\n```rs\nfn main() {\n    res := add(1, 2, 3, 4);\n    io::println(res);\n}\n```\n\nExamples that contain variadic functions include [`variadic.le`](https://github.com/acquitelol/elle/blob/rewrite/examples/tests/variadic.le).\n\n\u003chr /\u003e\n\n### ♡ **Arrays**\n\nThere are 2 kinds of arrays in Elle: _dynamic_ and _static_.\n\nDynamic arrays are allocated on the heap, and are designed to grow or shrink, allowing you to push and pop values. They also have far more utility methods on them compared to static arrays. These kinds of arrays are created with the following syntax:\n\n```bnf\narray = \"[\" [type \";\"] [elements] \"]\" ;\nelements = expression {\",\" expression} ;\n```\n\nStatic arrays are allocated on the stack, and are designed to be static in size. These arrays have basically no utility methods on them, and decay to just a pointer (`#[1, 2, 3]` -\u003e `i32 *`) but are faster. They're declared with the following syntax:\n\n```bnf\narray = \"#\" \"[\" [elements] \"]\" ;\nelements = expression {\",\" expression} ;\n```\n\nBoth implement the `__len__` method, which means this is valid:\n\n```rs\nfor x in [1, 2, 3] {\n    $dbg(x);\n}\n\nfor x in #[1, 2, 3] {\n    $dbg(x);\n}\n```\n\nDynamic arrays have special sugar when being typed:\n\n```rs\ni64[] x = [];\n\n// OR\n\nlet x = Array::new\u003ci64\u003e();\n\n// ... equivalent to ...\n\nArray\u003ci64\u003e *x = Array::new();\n\n// ... the most concise form ...\n\nx := [i64;];\n```\n\nStatic arrays do not, but you can still use `let`/`:=`:\n\n```rs\nlet x = #[1, 2, 3]; // x's type is `i32 *`\nx := #[1, 2, 3]; // x's type is `i32 *`\n\n// ... OR if you need the inference ...\n\nf32 *x = #[1, 2, 3]; // 1, 2, 3 are casted to floats\n```\n\nYou can also use `let`/`:=` when declaring dynamic arrays which have values:\n\n```rs\nx := [1, 2, 3]; // x's type is i32[]\ny := [\"a\", \"b\", \"c\"]; // y's type is string[]\n```\n\nYou can also define multi-dimensional arrays:\n\n```rs\ngrid := [\n    [1, 2],\n    [3, 4]\n];\n\ngrid[0][1]; // 2\ngrid[1][0]; // 3\n\n// ... or if you prefer explicit typing ...\n\nchar[][] x = [\n    ['a', 'b'],\n    ['c', 'd']\n];\n\nx[0][0]; // a\nx[1][1]; // d\n```\n\nSpecifically for dynamic arrays, you can initialize them without giving them a value or explicitly using the array contructor of Array::new\u003cT\u003e() to create them:\n\n```rs\nfn main() {\n    x := [i32;]; // x -\u003e i32[]\n    y := [f32; 1, 2, 3]; // 1, 2, 3 inferred as f32 and overall y -\u003e f32[]\n    z := [\"a\", \"b\", \"c\"]; // z -\u003e string[], no explicit type means inferred\n    w := []; // compilation error because T cannot be inferred\n    $dbg(x, y, z, w);\n}\n```\n\nThis syntax essentially has 2 parts: the type and the values. You can specify the type and no values, values and no type, or both. But you must specify at least one most of the time for the compiler to be able to determine a type for the array;\n\nIt's worth noting that the `T[]` type syntax is actually sugar for `Array\u003cT\u003e *`. `T[][]` is equivalent to `Array\u003cArray\u003cT\u003e *\u003e *`.\n\n\u003chr /\u003e\n\n### ♡ **Tuples and triples**\n\nTuples and triples are distinct data structures in Elle. Tuples have 2 items inside, Triples have 3 items.\n\nTuples have special sugar for their types, just like arrays. `(T, U)` is equivalent to `Tuple\u003cT, U\u003e *`. Triples have no sugar, simply `Triple\u003cT, U, V\u003e *`.\n\nTo define a tuple, use `$(x, y)` or `Tuple::new(x, y)`.\n\nTo define a triple, use `$$(x, y, z)` or `Triple::new(x, y, z)`.\n\nYou can put tuples inside of arrays:\n\n```rs\nlet foo = [$(1, \"a\"), $(2, \"b\")];\n\n// ... or if you prefer explicit typing ...\n\n(i32, string)[] foo = [$(1, \"a\"), $(2, \"b\")];\n\n// if you don't wanna put values inside but wanna use the `let` keyowrd you can do this\n\nlet foo = [(i32, string);];\n\n// ... nothing new here\n```\n\n\u003chr /\u003e\n\n### ♡ **Ranges**\n\nRanges are ways you can define the start and end of a \"`range`\" of numbers. There are 2 kinds of ranges in elle: exclusive and inclusive.\n\nExclusive ranges are defined with `x..y`, where `x` and `y` are expressions.\nInclusive ranges are defined with `x..=y`, where `x` and `y` are expressions.\n\nAn exclusive range means that `x` is inclusive but `y` is exclusive. An inclusive range means both are inclusive.\n\nAt the moment, ranges are just aliases for `Array::range(x, y, inclusive)`. This means they are not lazy, they create dynamic arrays of the specified range.\n\nThey can be used in `foreach` loops however, because they're arrays:\n\n```rs\n// 0..10 == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\nfor i in 0..10 {\n    $dbg(i);\n}\n\n// 0..0 == []\nfor i in 0..0 {\n    $dbg(i); // will never run\n}\n\n// 5..=10 == [5, 6, 7, 8, 9, 10]\nfor i in 5..=10 {\n    $dbg(i);\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Lambda functions**\n\nElle allows you to create single-line or multi-line lambda (anonymous) functions.\n\nHere are basic examples of how you can use them:\n\n```rs\nuse std/prelude;\n\nfn main() {\n    let arr = [1, 2, 3].map\u003ci32\u003e(fn(i32 x) x * 2);\n    io::println(arr); // \u003c[2, 4, 6] at 0xdeadbeef\u003e\n}\n```\n\n```rs\nuse std/prelude;\n\nfn main() {\n    let x = fn(i32 x) {\n        let foo = x * 100;\n        return (foo - 10) / 2;\n    };\n\n    $dbg(x(3));\n}\n```\n\nPlease note the following:\n\n- These lambdas do **not** capture surrounding variables\n- They are not automatically passed ElleMeta by the compiler (because there is not enough context to do so)\n- You cannot declare the interface for a lambda on the type level\n\nThis means that these examples won't work:\n\n```rs\nuse std/prelude;\n\nfn main() {\n    let arr = [1, 2, 3];\n    let a = 5;\n\n    // The compiler will throw an error here\n    let arr_doubled = arr.map\u003ci32\u003e(fn(i32 x) x * a);\n    io::println(arr_doubled);\n}\n```\n\n```rs\nuse std/prelude;\n\nfn main() {\n    let arr = [1, 2, 3];\n\n    // The program will segfault here (for now)\n    // due to not being passed ElleMeta\n    let arr_doubled = arr.map\u003ci32\u003e(io::println);\n    io::println(arr_doubled);\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Exact literals**\n\n- An exact literal is an identifier which is not explicitly parsed. As in, you can make and call functions with arbitrary names which may be invalid in Elle but valid in the IR.\n\nYou can create an \"exact literal\" by wrapping the content you wish with \"`\" on both sides of the expression.\n\nHere is a basic example:\n\n```rs\nuse std/io;\n\nfn `add.works`() {\n    $assert(42 + 42 == 84, nil);\n}\n\nfn `mul.works`() {\n    $assert(42 * 2 == 84, nil);\n}\n\nfn main() {\n    `add.works`();\n    `mul.works`();\n\n    io::println(\"All `exact literal` tests have passed!\".color(\"green\").reset());\n}\n```\n\nHere's another example;\n\n```rs\nfn `identity.foo.$.bar`(i32 x) {\n    return x;\n}\n\nfn main() {\n    io::println(`identity.foo.$.bar`(123)); // Valid in the IR but not in Elle functions\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Static buffers**\n\n- A static buffer is a basic allocation of stack memory with a specified size.\n- You can allocate a buffer with the `type buf[size];` syntax.\n\nThis would allocate memory on the stack of that size and give you back a pointer to that type.\n\nFor example:\n\n```rs\nuse std/prelude;\n\nconst i32 ARRAY_SIZE = 40;\n\nfn main() {\n    i32 foo[ARRAY_SIZE]; // foo's type is `i32 *`\n\n    for i in 0..ARRAY_SIZE {\n        foo[i] = (i + 10) * 100;\n    }\n\n    $dbg(foo[33]);\n}\n```\n\nThe size doesn't have to be known at compile time:\n\n```rs\nuse std/prelude;\n\nfn main() {\n    let size = i32::parse(io::input(\"Enter a size -\u003e \"));\n    i32 foo[size]; // foo's type is `i32 *`\n\n    for i in 0..size {\n        foo[i] = (i + 10) * 100;\n    }\n\n    $dbg(foo[size - 1]);\n}\n```\n\nThe type of a static buffer cannot be inferred. You must declare it explicitly.\n\n\u003chr /\u003e\n\n### ♡ **Defer statements**\n\n- A `defer` statement is commonly used to group together memory allocation and deallocation. A simple explanation is that it stores whatever statement is defined inside and inserts it when the current scope is about to be left, ie during a return, a block being exited, or an implicit return due to the function scope being left. `defer` statements are inserted backwards.\n\n\u003e [!IMPORTANT]\n\u003e If you create a `defer` statement which forces the current scope to be left, any other `defer` statements created before it will NOT be inserted. Observe:\n\n```rs\nfn main() {\n    defer $println(\"hi\"); // WILL NOT RUN\n    defer return 1;\n}\n```\n\n\u003chr /\u003e\n\nA very simple example of this is declaring a variable and deferring printing its value, like this:\n\n```rs\nuse std/io;\n\nfn main() {\n    let i = 0;\n\n    // If this were not in a defer statement, then this would print 0\n    // However, it will print 25 instead.\n    // Realistically this code only runs right before the main function leaves scope.\n    defer io::print(i);\n\n    i += 5;\n    i *= i;\n}\n```\n\nYou can see how this only calls `io::print` right before it returns 0, which is indeed _after_ the `i` variable has had changes made to it. This also works if you return in other scopes, such as if statements, while loops, standalone blocks, etc, as stated above. Any defer statements in inner blocks will not be called on any return, rather will only be called when the inner block is about to leave scope.\n\nThis also means that if you, hypothetically, design a program like this\n\n```rs\nuse std/io;\n\nfn main() {\n    let i = 0;\n    defer io::print(i);\n\n    {\n        defer io::print(i);\n        i += 2;\n    }\n\n    i *= i;\n}\n```\n\nThe expected output is 2, then 4.\nThis is because it will call `io::print` once when the standalone block will leave scope, at which point `i` is 2, then it will call `io::print` again when the function itself (`main`) will leave scope, at which point it will be 4 because `i` was squared (`i *= i`).\n\nYou can also write something like this:\n\n```rs\nfn main() {\n    let i = 0;\n    defer io::print(i);\n\n    {\n        defer io::print(i);\n        i += 2;\n\n        {\n            return 0;\n        }\n    }\n\n    i *= i;\n}\n```\n\nHere we expect `i` (`2`) to be printed to the console twice. Why? When the function returns, the scope created by the standalone block is also inherently about to be left. Hence, we also need to call all non-root deferrers here.\n\nThe most useful application of deferring is for memory management, however.\n\nConsider this code:\n\n```rs\nuse std/io\n\nfn main() {\n    let size = 10;\n    i64 *numbers = mem::malloc(size * #size(i64));\n    defer mem::free(numbers);\n\n    for let i = 0; i \u003c size - 1; i += 1 {\n        numbers[i] = i * 2;\n        let res = numbers[i];\n        io::printf(\"numbers[{}] = {}\", i, res);\n    }\n\n    if numbers[2] + 1 * 5 == 10 {\n        // Calls `free` here\n        return 1;\n    }\n\n    // Calls `free` here\n}\n```\n\nWithout deferring, you would have to call `free` at every single place where you return. Not only is this inefficient, but also very easy to forget.\n\nOf course for a function like the above, you are able to determine what path the code will take at compile time, however if you use something like `rand()` you no longer have the ability to do this, so you need to call `free` manually at all points where the function leaves its scope. This is an elegant way to prevent that.\n\n\u003chr /\u003e\n\n### ♡ **Type definitions**\n\n- A type definition is used to differentiate between the scope and size of different variables. You must define a type when declaring variables, taking variables as arguments in a function, and yielding the next value from a variadic argument pointer.\n\nElle's types are quite similar to C in terms of their definition. They can be a recursive pointer type too such as `char **` (An array of strings). Although C has a limit on the number of pointers that a type can have (it is 2 in the C spec), Elle does **not**.\n\nThese are the mappings of types in Elle:\n\n- `void` - A mapping to `word`, usually used for `void *` or function return signatures\n- `bool` - A mapping to `i8`, and works purely as a semantic for boolean literals like `true` or `false` that expand to `1` or `0` respectively.\n- `char` - A mapping to a `byte` representing a character in ASCII.\n- `i8` - A \"byte\", also known as an 8-bit integer.\n- `i16` - A \"short\", also known as a 16-bit signed integer, or half the size of an i32.\n- `i32` - A \"word\", also known as a 32-bit signed integer.\n- `i64` - A signed integer of the size specified by your computer's architecture, up to 64-bit.\n- `f32` - A 32-bit signed floating point number.\n- `f64` - A 64-bit signed floating point number, providing double the precision of `f32`.\n- `fn` - A type that maps to a `byte`. This is intended to be used as a pointer to the first byte of a function definition.\n- `pointer` - Denoted by `\u003ctype\u003e *` -\u003e As pointers are just a number, an address in memory, a pointer in Elle is just an `i64` that holds extra context by holding another type so that it can use its size to calculate an offset when indexing its memory.\n- `string` - A mapping to a `char *`, which is essentially an array of characters, or a \"c-string\".\n- `any` - A mapping to `void *`. This is **TEMPORARY**.\n\n\u003chr /\u003e\n\n### ♡ **Type Conversion / Casting**\n\n- A type conversion consists of converting a variable from one type to another, usually compromising precision if converting to a type with a lower size (f64 -\u003e f32) or having more precision if promoting a type (i32 -\u003e i64).\n\nCasting in Elle is a compiler builtin, hence it uses `#cast(T, expr)`.\n\n\u003cbr /\u003e\n\nHere is an example that casts a float to an integer to add it to another integer:\n\n```rs\nfn main() {\n    f32 a = 1.5;\n    i32 b = #cast(i32, a) + 2;\n}\n```\n\nCasting is not necessary here, because the Elle compiler is smart enough to automatically cast the `f32` to an `i32` when compiling the arithmetic operation, based on a [weight](https://github.com/acquitelol/elle/blob/rewrite/src/compiler/qbe/type.rs#L649-L662) that each type is assigned.\n\n\u003cbr /\u003e\n\nYou can also cast to pointer types, however note that, unlike C, casting to a pointer type when using `mem::malloc` is _not_ necessary because the Elle compiler automatically casts the `void *` into the type of the variable.\n\nThis means you can write:\n\n```rs\nfn main() {\n    f64 *a = mem::malloc(1024 * #size(f64));\n}\n```\n\nand Elle will not complain because implicitly converting `void *` -\u003e `T *` and vice versa is allowed.\n\n\u003e [!IMPORTANT]\n\u003e Strings are different to regular pointers. Even though they are just `char *`, the compiler will not allow you to implicitly cast a `void*` to a `string`. You will need to explicitly cast it.\n\n\u003chr /\u003e\n\n### ♡ **Unary operators**\n\n- A unary operator is a token used as a prefix to a literal or identifer to apply some operation to it, like negating it.\n\nThere are 5 unary operators in Elle:\n\u003cbr /\u003e\n\n- `!` - Logical NOT\n- `~` - Bitwise NOT\n- `\u0026` - Stack address\n- `-` - Negative number\n- `+` - Positive number\n- `*` - Pointer dereference\n\nAny identifier or literal can be prefixed by one of these operators.\n\nExample of using logical `NOT`:\n\n```rs\nuse std/io;\n\nfn main() {\n    let myBool = false;\n\n    if !myBool {\n        io::println(\"Hello world!\");\n    }\n}\n```\n\nExample of using bitwise `NOT`:\n\n```rs\nuse std/io;\n\nfn main() {\n    let a = 1;\n\n    if ~a == -2 {\n        io::println(\"Hello world!\");\n    }\n}\n```\n\nThis can also be used for negative or positive values:\n\n```rs\nconst i64 MAX_SIGNED_LONG = 9_223_372_036_854_775_807;\nconst i64 MIN_SIGNED_LONG = -MAX_SIGNED_LONG - 1;\n```\n\nUsing unary `-` will multiply the expression by -1 while unary `+` will multiply the expression by 1.\n\nThe unary `\u0026` operator is used to get the memory address of a local variable in a function. Here is an example:\n\n```rs\nuse std/io;\n\nfn other(i32 *something) {\n    io::println(*something);\n}\n\npub fn main() {\n    let a = 39;\n    other(\u0026a);\n    return 0;\n}\n```\n\nHere we declare `a` as 39, then we pass the \"address\" of `a` to `other` as a pointer to an `i32`, then this pointer is dereferenced.\n\n\u003chr /\u003e\n\nThe unary `*` operator is used to dereference a pointer to a value:\n\n```rs\nuse std/io;\n\nfn other(i32 *a, string *str) {\n    io::printf(\"(fn other)\\n\\ta = {}\\n\\tstr = {}\", *a, *str);\n    *a = 542;\n}\n\nfn main() {\n    let a = 39;\n    string str = \"Hello world!\";\n\n    other(\u0026a, \u0026str);\n    io::printf(\"(fn main)\\n\\ta = {}\", a);\n}\n```\n\nThe example also implies that you can store values at those dereferenced addresses. You can put as many tokens as you want after the operator. It will yield until:\n\n- it matches a semicolon (`;`)\n- it matches an arithmetic operator\n- it reaches the end of the token vector\n\nThis means that if you want to manipulate the address before it is dereferenced, you can wrap it in `()`.\n\nThis code:\n\n```rs\nio::println(*a + 1);\n```\n\nwill dereference `a` and then add 1 to the result.\n\nThis code, however:\n\n```rs\nio::println(*(a + 1));\n```\n\nwill first add 1 to the address of `a`, and then will dereference that address.\n\n\u003chr /\u003e\n\n### ♡ **Arithmetic operations**\n\n- All arithmetic operations are declared with an expression on the left and right of an operator. This means you can call functions, do other arithmetic operations inside of operations, etc.\n\nThis is the mapping defined by Elle:\n\n- `^` - Xor\n- `*` - Multiply\n- `/` - Divide\n- `+` - Add\n- `-` - Subtract\n- `%` - Modulus\n- `\u0026` - Bitwise And\n- `|` - Bitwise Or\n- `\u003c\u003c` - Shift Left\n- `\u003e\u003e` - Shift Right\n- `\u003c\u003e` - Concatenation (only works on strings)\n\nOperators which exist but can't be used when declaring variables:\n\n- `\u0026\u0026` - Logical And\n- `||` - Logical Or\n- `..` - Exclusive range\n- `..=` - Inclusive range\n\nKeep in mind that you can also use these operators when doing a variable declaration.\nThis means the following code is valid:\n\n```rs\nuse std/io;\n\nfn main() {\n    let a = 1;\n    a ^= 1; // a is now 0\n    io::println(a);\n}\n```\n\nAnd of course, this works for every arithmetic operator, not just `^`.\n\nElle follows the standard [order of operations](https://github.com/acquitelol/elle/blob/rewrite/src/lexer/enums.rs#L115-L136) described by mathematics (typically defined as BIDMAS or PEMDAS), which means you can also wrap expressions in `()` to evaluate them before other expressions that may have a higher precedence.\n\nExample of a program that calculates the xor (`^`) and sum (`+`) of some values:\n\n```rs\nuse std/io;\n\nfn main() {\n    i32 a = 1 + (5 ^ 2); // Xor has a lower precedence than addition\n\n    // We're expecting this to be 8 because\n    //  5 ^ 2 = 7 and 7 + 1 = 8, however\n    // without the brackets it would be 4\n    // because it would evaluate to 6 ^ 2 = 4\n    io::println(a);\n}\n```\n\nHere's another example, using the string concatenation operator:\n\n```rs\nuse std/io; // std/io contains std/string so we don't need to import it\n\nfn main() {\n    string a = \"a\" \u003c\u003e \"b\";\n    a \u003c\u003e= \"c\"; // Concatenation can be done declaratively\n    $dbg(a); // Expected: (string) a = \"abc\"\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Constants**\n\n- A constant is a value that cannot be redeclared. In Elle, constants can only be defined at the top level of files, and vice versa too, where the top level of files can _only_ be constants and functions. You cannot define normal variables at the top level.\n- Constants can be public, declared using the `pub` keyword.\n- Constants that create pointers (such as string literals) are referenced as the first statement of each function to bring them in scope.\n\nConsider this example that uses constants:\n\n```rs\nuse std/io;\n\nconst i32 WIDTH = 100;\nconst i32 HEIGHT = 24;\nconst i32 SIZE = WIDTH * HEIGHT;\n\npub fn main() {\n    io::println(SIZE);\n    return 0;\n}\n```\n\nIn the above code, all of the constants are technically function definitions that return the value after the `=` sign. However, when they're referenced, the function is automatically called. Therefore, you dont need to type `SIZE()` or similar, you can just directly reference `SIZE` as if it was a constant.\n\nIt is labelled as a \"`constant`\", because although it can return a different value (it can call any function), it cannot be redeclared.\n\n\u003chr /\u003e\n\n### ♡ **Non-base-10 literals**\n\n- These are literal numbers which are not declared in base 10.\n\nThese may include:\n\n- Hex - 0xFFFFFF\n- Octal - 0o777777\n- Binary - 0b111111\n- Scientific - 2.1e3\n\nBasic example:\n\n```rs\nuse std/io;\n\nfn main() {\n    i64 a = 0xDEADBEEF;\n    i32 b = 0o273451456;\n    i32 c = 0b111010011011111010010100101;\n    i64 d = 1.2e9;\n    f64 e = 2.7182818e2;\n\n    $dbg(a, b, c, d, e);\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Imports/modules**\n\nElle's module system works in the following way:\n\n- Elle will look in the `~/.local/include/elle/` folder for modules\n- Elle will look in the current working directory for modules\n\nThe syntax for importing is as follows:\n\n```rs\nuse path/to/module;\n```\n\nwhere, in the directory where the file is importing from, there is a `./path/to/module.le` file.\n\nThe syntax to export a symbol from your current file is as follows:\n\n```rs\n// ./module.le\npub const i32 myFavouriteNumber = 7;\n\npub fn foo() {\n    return 1;\n}\n```\n\nwhich you can then import\n\n```rs\nuse std/io;\nuse module;\n\nfn main() {\n    io::println(foo() + myFavouriteNumber);\n}\n```\n\nYou can also enable a modifier globally for a module.\n\nFor example, by default everything in a module is private, but you can use the `pub` keyword to make it public.\n\n```rs\n// by default all private\n\nfn foo() {} // foo is implicitly private\nfn bar() {} // bar is implicitly private\npub fn baz() {} // baz is explicitly public\n```\n\nHowever, you can make everything in a module public by default, and then mark something as private with `!pub`:\n\n```rs\nglobal pub; // every function in the module is public\n\n!pub fn foo() {} // foo is explicitly private\n!pub fn bar() {} // bar is explicitly private\nfn baz() {} // baz is implicitly public\n```\n\nSimilarly, by default every function in a module has a definition, unless you use the `external` keyword:\n\n```rs\n// by default every method is defined unless specified with the `external` keyword\nfn foo() {} // implicitly defined, requires a body\nfn bar() {} // implicitly defined, requires a body\nexternal fn bar(); // explicitly external, so requires just `;` and throws if you try to provide a body\n```\n\nYou can make every function in a module be external by default. This is useful for headers of functions whose bodies are defined elsewhere, and prevents the repetition of `external fn` so much:\n\n```rs\nglobal external;\n\nfn foo(); // implicitly external, requires just `;`\nfn bar(); // implicitly external, requires just `;`\n!external fn bar() {} // explicitly defined, so requires a body\n```\n\nFinally, you can group together global specifiers:\n\n```rs\nglobal pub, external;\n\nfn foo(); // implicitly public and external\n!pub fn bar(); // explicitly private and external\n!pub !external fn baz() {} // explicitly private and defined\n```\n\n\u003chr /\u003e\n\n### ♡ **Structs**\n\nStructs are allocations in memory with a defined layout. In Elle, these are defined using the `struct` keyword.\n\nExample:\n\n```rs\nstruct Bar {\n    f32 myFloat;\n};\n\nstruct Foo {\n    i32 a;\n    Bar bar;\n    f64 baz;\n};\n```\n\nYou can then create these structures like this:\n\n```rs\nfn main() {\n    foo := Foo {\n        a = 12,\n        bar = Bar {\n            myFloat = 10.2\n        },\n        baz = 3.141592\n    };\n\n    io::println(foo.bar.myFloat);\n}\n```\n\nIf taking a pointer to them from another function, you can do so like this:\n\n```rs\nuse std/io;\n\nfn other(Foo *foo) {\n    foo.baz = 17.98;\n    io::println(foo.a);\n}\n\nfn main() {\n    foo := ; // create Foo\n    other(\u0026foo);\n}\n```\n\n\u003e [!NOTE]\n\u003e There is no equivalent of the `a-\u003eb` operator in Elle. Any pointer to a struct will automatically be dereferenced before processing any fields in the struct.\n\u003e You can still manually dereference the struct pointer manually if you like, but it will have no difference compared to directly using dot notation.\n\u003e This means that the following code will accurately update the value inside the struct Foo:\n\n```rs\nuse std/io;\n\nstruct Foo {\n    i32 a;\n};\n\nfn other(Foo *foo) {\n    foo.a = 5;\n}\n\nfn main() {\n    Foo foo = Foo { a = 100 };\n    other(\u0026foo);\n    io::println(foo.a); // foo.a is now 5 not 100\n}\n```\n\nYou can also define methods on structs (and primitive types):\n\n```rs\nuse std/io;\n\nstruct Foo {\n    i32 a;\n};\n\nfn Foo::add(Foo self, Foo other) {\n    return Foo { a = self.a + other.a };\n}\n\nfn main() {\n    Foo foo1 = Foo { a = 10 };\n    Foo foo2 = Foo { a = 30 };\n\n    Foo res1 = foo1.add(foo2);\n    Foo res2 = Foo::add(foo1, foo2);\n\n    $dbg(res1.a, res2.a);\n}\n```\n\nYou can define it like this to create instance methods:\n\n```bnf\ninstance_method = \"fn\" namespace \"::\" name \"(\" [args] \")\"\nargs = arg {\",\" arg} ;\narg = type name ;\n```\n\nYou can then either call them like this:\n\n```rs\nlet foo = Foo::new();\nfoo.bar();\n```\n\nor like this:\n\n```rs\nlet foo = Foo::new();\nFoo::bar(foo);\n```\n\n\u003cbr /\u003e\nIn this case, `foo1.add(foo2)` is an identical expression to `Foo::add(foo1, foo2)`\n\u003cbr /\u003e\nFor more examples, please view [vectors.le](https://github.com/acquitelol/elle/blob/rewrite/std/vectors.le)\n\nYou may also specify that `self` is a `\u003cty\u003e *` instead of a `\u003cty\u003e` if you require editing it in-place:\n\n```rs\nuse std/io;\n\nstruct Foo {\n    i32 a;\n};\n\nfn Foo::divideBy(Foo *self, i32 num) {\n    self.a /= num;\n}\n\nfn main() {\n    Foo foo = Foo { a = 10 };\n    foo.divideBy(2);\n\n    $dbg(foo.a); // foo.a = 5\n}\n```\n\nThe compiler will automatically pass the **address** of `foo` instead of `foo` itself to the function.\n\u003cbr /\u003e\nIn the case of a method that takes in a `self` _pointer_, the equivalent expression to `foo1.divideBy(2)` is `Foo::divideBy(\u0026foo1, 2)`.\n\n\u003chr /\u003e\n\n### ♡ **Generics**\n\n- Elle allows you to create generic structs and functions which may hold any inner type.\n\nFor example, here's a generic function which allows you to pass both integers and floats:\n\n```rs\nfn add\u003cT\u003e(T x, T y) {\n    return x + y;\n}\n\nfn main() {\n    add(1, 2);\n    add(1.2, 1.3);\n}\n```\n\nNotice how seamless using the generic was? Elle was able to infer 2 things here: T is whatever type `x` and `y` are, and the return type is _also_ T. This means, even though you can, you usually don't _need_ to explicitly specify all the generics. This is a more verbose but still correct way to do it:\n\n```rs\nfn add\u003cT\u003e(T x, T y) -\u003e T {\n    return x + y;\n}\n\nfn main() {\n    add\u003ci32\u003e(1, 2);\n    add\u003cf32\u003e(1.2, 1.3);\n}\n```\n\nGeneric structs are created as follows:\n\n```rs\nstruct Foo\u003cT\u003e {\n    T a;\n};\n\nfn main() {\n    Foo\u003ci32\u003e x = Foo { a = 1 };\n    Foo\u003cstring\u003e y = Foo { a = \"hello world!\" };\n}\n```\n\nIn this struct, the `a` field can be of _any_ type. Note that for structs, you cannot explicitly declare their inner type. You must do so via inference. Elle will infer the inner type based on the struct's variable declaration most of the time. Take the example above, where we declare `Foo\u003ci32\u003e x = Foo { a = 1 };`. The Elle compiler sees that the type of the left hand side and right hand side are both of Foo, however it sees that the right hand side is a struct declaration of a generic struct, so it uses the left hand side to infer the inner types of the right hand side.\n\nThis allows for almost rust-like declarations of generic structs and their methods:\n\n```rs\nuse std/io;\n\nstruct Foo\u003cT, U\u003e {\n    T a;\n    U b;\n};\n\nfn Foo::new\u003cT, U\u003e(T a, U b) -\u003e Foo\u003cT, U\u003e {\n    return Foo { a = a, b = b };\n}\n\nfn Foo::double_all\u003cT, U\u003e(Foo\u003cT, U\u003e *self) {\n    self.a *= 2;\n    self.b *= 2;\n}\n\nfn Foo::get_a\u003cT, U\u003e(Foo\u003cT, U\u003e self) -\u003e T {\n    return self.a;\n}\n\nfn Foo::get_b\u003cT, U\u003e(Foo\u003cT, U\u003e self) -\u003e U {\n    return self.a;\n}\n\nfn main() {\n    Foo\u003ci32, f32\u003e foo = Foo::new(10, 1.2);\n    foo.double_all();\n    $dbg(foo.get_a());\n    $dbg(foo.get_b(), foo.b);\n}\n```\n\nFrom this you can get a quick grasp of how to use generics effectively. The struct uses 2 generics, and as all methods require to define the `self` argument's type, this means that you need to type \u003cT, U\u003e on every function that takes a Foo\u003cT, U\u003e. This is slightly verbose, and in the future I may allow for syntax to simplify it in the future.\n\n\u003chr /\u003e\n\n### ♡ **Command line arguments (argc/argv)**\n\n- You can optionally accept an array of strings (`string[]`) as the 0th argument in your main function, which will cause the compiler to create this array out of `argc` and `argv`.\n\nIf your main function does not accept this array, the array will not be created.\n\nHere is an example of how you can use it:\n\n```rs\nfn main(string[] args) {\n    let program = args.remove(0);\n\n    for arg in args {\n        if arg == \"foo\" {\n            io::printf(\"i received a foo in my {}!\", program);\n        }\n    }\n}\n```\n\nKeep in mind that to use this, you must have the dynamic array module imported. You can either manually import `std/collections/array`, or import `std/prelude` which imports the array module inside.\n\n\u003chr /\u003e\n\n### ♡ **Sigils (identifier prefixes)**\n\n- As you might have noticed, Elle has a notion of \"sigils\" which are used as prefixes to the names of various things to give them a special meaning.\n\nThese are the current sigils:\n\nThe `$x` sigil (stdlib alias):\n\n- Used to alias a common standard library function.\n\nThe `#x` sigil (directive):\n\n- Used to denote a compiler built-in.\n\nThe `@x` sigil (attribute):\n\n- Used to denote a tag that can be placed on a function or struct.\n\nFor more information on stdlib alises, directives and attributes, please read below the below chapters.\n\n\u003e `\u0026` is not really a sigil, but it can be included here anyway. The `\u0026x` expr gives you the address of `x`. You can read more about this in the Unary Operators chapter.\n\n\u003chr /\u003e\n\n### ♡ **Standard library aliases**\n\n- Used to alias a common standard library function which should be easily accessible but also shouldn't pollute the global namespace.\n- Examples of this include:\n  - `io::dbg(...)` -\u003e `$dbg(...)`\n  - `io::panic(...)` -\u003e `$panic(...)`\n  - `Tuple::new(...)` -\u003e `$(...)`\n  - `Triple::new(...)` -\u003e `$$(...)`\n- Note that this is created as an **alias** of the original function. This means you can call `io::dbg` instead of `$dbg`, for example.\n\n\u003chr /\u003e\n\n### ♡ **Directives**\n\n- These are compiler builtins you can call to get a result at compile-time.\n\nThe current existing directives are:\n\n- `#len(static_array_expr)` - Gives you the length of a static array\n- `#size(T)` - Gives you the size of type T in bytes\n- `#i(ident)` - Gives you the iterator in a foreach loop given the current element\n- `#env` - Gives you a `ElleEnv *` which is a global environment structure\n- `#alloc(T, size?)` - Allows you to allocate a specific type using the current allocator\n- `#realloc(ptr_expr, T, size?)` - Allows you to reallocate a pointer with a specific type using the current allocator\n- `#free(ptr_expr)` - Frees a pointer using the current allocator. If the allocator didn't define a `free` method, this does nothing.\n- `#set_allocator(allocator_expr)` - Sets the current allocator to the one specified by `allocator_expr`\n- `#reset_allocator()` - Sets the current allocator back to `#env.default_allocator`. **Does not call `#env.allocator.free_self`.**\n- `#cast(T, cast_expr)` - Uses a set of rules to convert `cast_expr` to type `T`. If it fails, it will throw a compile-time error.\n\n\u003chr /\u003e\n\n### ♡ **Attributes**\n\n- These are tags you can put on functions to specify extra functionality\n\nThe current existing attributes are:\n\n- Alias - Allows you to specify an alias for external functions `@alias(name) || @alias(namespace::name)`\n- Volatile - Allows you to specify that Elle should not discard this function if it is unused. `@volatile`\n- Format - Puts every argument through its formatter before passing it to the function `@fmt`\n- NoFormat - Specifies that a struct should not have a format function automatically generated for it `@nofmt`\n- Manual - Will prevent including an automatic \"dummy\" jump at the end of functions that do not return `@manual`\n\n\u003cbr /\u003e\n\nThe `@manual` attribute is useful for functions written in pure IR, where you are returning from the interface of the IR not the language itself.\n\n\u003chr /\u003e\n\nExample of attribute usage:\n\n```rs\n// Attributes go BEFORE the return type\n// The alias attribute will be purposefully ignored\n// because this function is not external\nfn add(i32 x, i32 y) @alias(foo) @volatile -\u003e i32 {\n    return x + y;\n}\n\n// The volatile attribute will be purposefully ignored\n// because external functions do not generate IR\nexternal fn printf(string formatter, ...) @alias(formatted_print) @volatile;\n```\n\nIf you specify an alias attribute on a non-external function, you will only be warned, an error will **not** be thrown. Keep in mind that external functions do not generate IR, so the @volatile attribute will have no effect on them.\n\n\u003chr /\u003e\n\n### ♡ **Formatters**\n\nElle allows you to specify how your structs should be formatted. By default, structs will automatically have a format function generated for them by the compiler. If you want to make your own, simply create it as a struct method:\n\n```rs\nstruct Foo\u003cT\u003e {\n    T a;\n    T b;\n};\n\nfn Foo::__fmt__\u003cT\u003e(Foo\u003cT\u003e self, i32 nesting) {\n    return string::format(\"{}\", self.a + self.b);\n}\n```\n\nSome things to keep in mind:\n\n- The format function _must_ return a string.\n- The format function takes a `nesting` argument. This is used to determine the depth of nested structs when printed.\n\nIf an automatically generated struct's format method is too much bloat and you need the size of your executable to be small, you can specify that a struct should not generate an automatic formatting method with the `@nofmt` attribute:\n\n```rs\nstruct Foo\u003cT\u003e @nofmt {\n    T a;\n    T b;\n};\n```\n\nIf you try to print Foo\u003cT\u003e however, you will get a compiler error.\n\nTo create functions that use these formattings, you can specify the @fmt attribute:\n\n```rs\nuse std/io;\n\nfn foo(ElleMeta meta, ...args) @fmt {\n    for i32 i = 0; i \u003c meta.arity; i += 1 {\n        string arg = args.yield(string); // The formatter will return a string\n        // Do something with it like printing it\n        io::println(arg);\n    }\n}\n\nfn main() {\n    foo(1, \"hi\", true);\n}\n```\n\nTo the compiler, this signals that every argument should be ran through its formatter. The equivalent code without `@fmt` is:\n\n```rs\nuse std/io;\n\nfn foo(ElleMeta meta, ...args) {\n    for i32 i = 0; i \u003c meta.arity; i += 1 {\n        string arg = args.yield(string);\n        io::println(arg);\n    }\n}\n\nfn main() {\n    // Keep in mind __fmt__ *must* return a string.\n    // The compiler will throw an error if it doesn't.\n    foo(i32::__fmt__(1, 0), string::__fmt__(\"hi\", 0), bool::__fmt__(true, 0));\n}\n```\n\nYou can also define a function which formats everything _except_ the arguments you specify. This is especially useful for formatting instance methods defined on structs:\n\n```rs\nuse std/prelude;\n\nstruct Foo {\n    i32 x;\n};\n\n// ElleMeta is already not formatted\nfn Foo::format(ElleMeta meta, @nofmt Foo *self, ...args) @fmt {\n    res := \"{}\\n\".format(self.x);\n\n    // account for `self`\n    for i in 0..meta.arity - 1 {\n        res \u003c\u003e= args.yield(string);\n        res \u003c\u003e= i \u003c meta.arity - 2 ? \"\\n\" : \"\";\n    }\n\n    return res;\n}\n\nfn main() {\n    foo := Foo { x = 39 };\n    $dbg(foo.format(1.2, \"bar\", \"baz\"));\n}\n```\n\n\u003chr /\u003e\n\n### ♡ **Objects and linking**\n\nYou may specify that Elle should emit an Object file instead of an executable by passing the `-c` flag.\n\nIf you want to use an object file in your project, you can do so like this:\n\n```rs\n// foo.le\n//\n// `add` must be public so that it is exported\n// and must be volatile to prevent DCE from removing it\npub fn add(i32 a, i32 b) @volatile {\n    return a + b;\n}\n```\n\nThen, compile it into an object file:\n\n```console\n$ ellec -c foo.le\n```\n\nwhich will emit foo.o\n\nFinally, use it:\n\n```rs\n// main.le\nuse std/io;\n\nexternal fn add(i32 a, i32 b) -\u003e i32;\n\nfn main() {\n    io::println(add(23, 16));\n}\n```\n\nand compile it:\n\n```console\n$ ellec main.le foo.o \u0026\u0026 ./main\n```\n\n\u003chr /\u003e\n\n### ♡ **External symbols**\n\n- An external symbol is a definition for a function or constant that was defined elsewhere (such as in C) and is implicitly defined in Elle. This is used to give definition and context to functions that were not defined in Elle but you wish to use in when writing Elle code.\n  \u003cbr/\u003e\n\nYou can do this with the following example:\n\n```rs\nexternal fn printf(string formatter, ...);\n```\n\nIt essentially tells Elle where it should put the variadic argument starter. You could exclude this, if you like, but you will have to explicitly declare where the variadic arguments begin, because Elle no longer has this context.\n\nYou can also make these statements public:\n\n```rs\npub external fn fprintf(FILE *fd, string formatter, ...);\n```\n\nIn fact the order of prefixes before `fn` is not enforced, you can write `external pub fn` and achieve the same result.\n\nYou may also alias exported functions, and allow them to be accessible through a pseudo-namespace:\n\n```rs\nnamespace raylib;\npub external fn InitWindow(i32 width, i32 height, string title) @alias(raylib::init_window);\n\n// You can now call raylib::init_window() and it will internally reference the InitWindow symbol\n```\n\n**Technical note:** This declaration does not emit any IR code. This means that all these definitions do is provide more information and context to the compiler. They do not change the output of the program directly.\n\n\u003chr /\u003e\n\n### ♡ If you have any questions, please **[raise an issue](https://github.com/acquitelol/elle/issues/new) :3**\n\nAll contributions to this project are welcome and I love talking about this stuff!\n\n\u003chr /\u003e\n\n### ♡ **How to run**\n\n- Ensure you have [Rust](https://www.rust-lang.org/), [Cargo](https://crates.io/) and the [QBE](https://c9x.me/compile/) compiler backend.\n\n  ```terminal\n    $ git clone https://github.com/acquitelol/elle\n  ```\n\n  ```terminal\n    $ cd elle\n  ```\n\n  ```console\n    $ make\n  ```\n\n  to install the compiler and standard library (installs into ~/.local/ by default)\n\n  **OR**\n\n  ```console\n    $ make compile-release\n  ```\n\n  to get only a compiler executable and not install anything (does not require root)\n\n  - **You're done!**\n\n#### ♡ You can now run `ellec` to get a help message of how to use the compiler!\n\nTry compiling a simple example!\n\n```console\n  $ ellec ./examples/hello.le \u0026\u0026 ./hello\n```\n\nTry compiling an example with libraries!\n\n```\n  $ ellec ./examples/graphics/ball.le -z -lraylib \u0026\u0026 ./ball\n```\n\n\u003chr /\u003e\n\n### ♡ **Licensing**\n\n- Please read [LICENSE.md](https://github.com/acquitelol/elle/blob/rewrite/LICENSE.md)\n- Copyright © 2024 Rosie ([acquitelol](https://github.com/acquitelol))\n\n\u003chr /\u003e\n\n\u003ca href=\"#top\"\u003e⇡ Back to top️!\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facquitelol%2Felle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facquitelol%2Felle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facquitelol%2Felle/lists"}