| 1 | # Ix Programming Language |
| 2 | |
| 3 | Ix (**I**nferable E**x**pression) is an experimental programming language. |
| 4 | |
| 5 | Key goals of this project are: |
| 6 | |
| 7 | - **Ease of Use**: Use of type inference and simple syntax makes code easy to |
| 8 | read and write. |
| 9 | - **Fast Iteration**: Incremental compilation keeps development productivity |
| 10 | high. |
| 11 | - **Safety**: Compiler errors catch most mistakes at compile-time, and |
| 12 | additional tooling to catch mistakes at runtime. |
| 13 | - **Universal**: One codebase can seamlessly target different web browsers and |
| 14 | native targets. |
| 15 | |
| 16 | It primarily focuses on merging some great points of Rust, Zig, and JavaScript's |
| 17 | language designs: |
| 18 | |
| 19 | - [**Rust**](https://www.rust-lang.org) - Expressive and concise control flow |
| 20 | that is easy to write. |
| 21 | - [**Zig**](https://ziglang.org) - Execute any code at compile time; everything |
| 22 | is an expression. |
| 23 | - [**JavaScript**](https://developer.mozilla.org/en-US/docs/Web/JavaScript) - |
| 24 | Garbage collected values with a versatile type system. |
| 25 | |
| 26 | This manual is a standalone HTML document. Code samples are compiled and tested |
| 27 | as a part of the release cycle. |
| 28 | |
| 29 | ## Hello World |
| 30 | |
| 31 | ```lang:run |
| 32 | let print = @extern("console").log; |
| 33 | fn main { |
| 34 | print("Hello, world!"); |
| 35 | } |
| 36 | ``` |
| 37 | |
| 38 | The `print` function is implemented by getting an external reference to the |
| 39 | `console` JavaScript global, retrieving the `log` property from it, and then |
| 40 | calling it when the program starts. |
| 41 | |
| 42 | > In the future, common APIs will be available in the standard library, such as |
| 43 | > `std.log.info()`. For now, no such library exists. |
| 44 | |
| 45 | ## Module Scope and Declarations |
| 46 | |
| 47 | The top-level |
| 48 | |
| 49 | ``` |
| 50 | let x = 1; |
| 51 | ``` |
| 52 | |
| 53 | ## Primitive Data Types |
| 54 | |
| 55 | | name | ts equivalent | description | |
| 56 | | ---------- | ----------------------------- | ---------------------------------------------- | |
| 57 | | `bool` | `boolean` | either `true` or `false` | |
| 58 | | `float` | `number` | a 64-bit floating point number | |
| 59 | | `int` | `number` | an integer number in signed 32-bit range | |
| 60 | | `bigint` | `bigint` | an integer number with no range limit | |
| 61 | | `string` | `string` | a sequence of text characters | |
| 62 | | `void` | `undefined` / `null` / `void` | detectable lack of a value | |
| 63 | | `opaque` | `unknown` / `any` | could be any value | |
| 64 | | `noreturn` | `never` | control flow prevents this value from existing | |
| 65 | |
| 66 | > **Note**: Unlike JavaScript, the values `undefined` and `null` are merged into |
| 67 | > one concept: [void](#void) |
| 68 | |
| 69 | ### Built-in Constants |
| 70 | |
| 71 | | name | description | |
| 72 | | ------- | ------------------------------------- | |
| 73 | | `true` | boolean true (result of `1 == 1`) | |
| 74 | | `false` | boolean false (result of `1 == 2`) | |
| 75 | | `std` | handle to the standard library module | |
| 76 | |
| 77 | These constants act as reserved names, and it is an error to re-declare them. |
| 78 | Note that primitive data types are also considered built-in constants. |
| 79 | |
| 80 | ```lang:build |
| 81 | fn main { |
| 82 | let opaque = -5.2; |
| 83 | let true = 1 == 0; |
| 84 | } |
| 85 | ``` |
| 86 | |
| 87 | ### Numbers |
| 88 | |
| 89 | Numbers have the following rules: |
| 90 | |
| 91 | - May start with a `.`, but cannot end with `.` |
| 92 | - May contain leading zeros, but transform pass will warn. |
| 93 | - May contain a `_` separator, cannot have two in a row. |
| 94 | - May contain `e` or `E` for exponents. |
| 95 | - May use 0x, 0b, 0o prefixes for hex, binary, octal numbers. |
| 96 | |
| 97 | ### Strings |
| 98 | |
| 99 | Numbers |
| 100 | |
| 101 | ### Multi-line string literals |
| 102 | |
| 103 | ```lang |
| 104 | let bwaa = |
| 105 | \\hello |
| 106 | \\ world |
| 107 | \\end |
| 108 | ; |
| 109 | ``` |
| 110 | |
| 111 | ### Void |
| 112 | |
| 113 | Void is the lack a value. For example, a function that does nothing returns |
| 114 | `void`. |
| 115 | |
| 116 | ```lang |
| 117 | pub fn hello() { |
| 118 | // do nothing |
| 119 | } |
| 120 | ``` |
| 121 | |
| 122 | <!-- TODO: change this section to "Optionals" --> |
| 123 | |
| 124 | ## Variables |
| 125 | |
| 126 | There are two types of variables, `let` and `var`. |
| 127 | |
| 128 | `let` bindings cannot be overridden, and should be thought of as aliases of the |
| 129 | expression within. Referring to these identifiers is equivalent to inlining it |
| 130 | where it was used, aside from side effect duplication. |
| 131 | |
| 132 | ```lang:run |
| 133 | let print = @extern("console").log; |
| 134 | |
| 135 | fn main { |
| 136 | let hello: int = 1; |
| 137 | let world = hello + 2; |
| 138 | let hello = hello * world; |
| 139 | |
| 140 | print(hello); |
| 141 | } |
| 142 | ``` |
| 143 | |
| 144 | `var` bindings create mutable memory locations at runtime. This means they can |
| 145 | be re-assigned to: |
| 146 | |
| 147 | ```lang:run |
| 148 | fn main { |
| 149 | var message = "first message"; |
| 150 | |
| 151 | if @extern("Math").random() > 0.5 { |
| 152 | message = "second message"; |
| 153 | } |
| 154 | |
| 155 | @extern("console").log(message); |
| 156 | } |
| 157 | ``` |
| 158 | |
| 159 | Using `let` is preferred whenever possible, as it will lead to more readable |
| 160 | code, and the compiler is able to optimize it better. The above can be rewritten |
| 161 | as: |
| 162 | |
| 163 | ```lang:run |
| 164 | fn main { |
| 165 | let message = if @extern("Math").random() > 0.5 { |
| 166 | "second message" |
| 167 | } else { |
| 168 | "first message" |
| 169 | }; |
| 170 | |
| 171 | @extern("console").log(message); |
| 172 | } |
| 173 | ``` |
| 174 | |
| 175 | It should be noted that numeric literals are a comptime-only data type, meaning |
| 176 | it cannot be assigned to a `var` without an explicit type annotation. Since |
| 177 | `int` is a strict subset of valid `float`s, the coercion will implicitly be a |
| 178 | float, but a compiler warning will be emitted. |
| 179 | |
| 180 | ```lang |
| 181 | var number = 0; // `number` is `float`, but not explicit |
| 182 | var number: int = 0; // `number` is `int` |
| 183 | var number: float = 0; // `number` is `float` |
| 184 | ``` |
| 185 | |
| 186 | ## Functions |
| 187 | |
| 188 | Functions are declared with the `fn` keyword. Parameter types must be annotated. |
| 189 | The function body is a block, which may use implicit return to return. |
| 190 | |
| 191 | ```lang:run |
| 192 | let print = @extern("console").log; |
| 193 | |
| 194 | fn add(a: int, b: int) { |
| 195 | a + b |
| 196 | } |
| 197 | |
| 198 | fn main { |
| 199 | print("Sum of 2 and 7:", add(2, 7)); |
| 200 | } |
| 201 | ``` |
| 202 | |
| 203 | The `return` expression can be used within functions to return early. |
| 204 | |
| 205 | ```lang |
| 206 | let print = @extern("console").log; |
| 207 | |
| 208 | fn something(a: int) -> bool { |
| 209 | if a == 0 { |
| 210 | return false; |
| 211 | } |
| 212 | |
| 213 | a > 100 |
| 214 | } |
| 215 | |
| 216 | fn main { |
| 217 | print(something(0xFF), something(0)); |
| 218 | } |
| 219 | ``` |
| 220 | |
| 221 | ## Number Literals |
| 222 | |
| 223 | TODO |
| 224 | |
| 225 | ## String Literals |
| 226 | |
| 227 | TODO |
| 228 | |
| 229 | ## JavaScript Interop |
| 230 | |
| 231 | A global variable handle can be retrieved with `@extern`. We've seen this in the |
| 232 | hello world. The type of this value is a special type called `opaque`, which |
| 233 | disables type-checking. |
| 234 | |
| 235 | > **Note**: A future update will improve on the type-safety of `opaque` |
| 236 | |
| 237 | Many JS-exclusive actions are exposed via built-in functions, such as using |
| 238 | `@jsNew` for calling constructors. |
| 239 | |
| 240 | ```lang:run |
| 241 | let print = @extern("console").log; |
| 242 | |
| 243 | fn currentYear() -> int { |
| 244 | @jsNew(@extern("Date")).getFullYear() |
| 245 | } |
| 246 | |
| 247 | fn main { |
| 248 | print(currentYear()); |
| 249 | } |
| 250 | ``` |
| 251 | |
| 252 | A field access on an external handle is allowed to be performed at compile time, |
| 253 | where such access will only crash at runtime when the value is used. |
| 254 | |
| 255 | ## Structs |
| 256 | |
| 257 | To group data together, a `struct` can be used. A struct contains zero or one |
| 258 | fields of runtime-compatible types. |
| 259 | |
| 260 | ```lang:run |
| 261 | let print = @extern("console").log; |
| 262 | |
| 263 | struct Position { |
| 264 | x: int, |
| 265 | y: int, |
| 266 | } |
| 267 | |
| 268 | fn main { |
| 269 | let center = Position{ x: 0, y: -2 }; |
| 270 | print(center.x, center.y); |
| 271 | |
| 272 | // To allow less verbose code, the struct type can be inferred with `.` |
| 273 | let added = addPositions( |
| 274 | .{ x: 1, y: -4 }, |
| 275 | .{ x: 5, y: 8 }, |
| 276 | ); |
| 277 | print("Add result:", added.x, added.y); |
| 278 | } |
| 279 | |
| 280 | fn addPositions(a: Position, b: Position) -> Position { |
| 281 | .{ x: a.x + b.x, y: a.y + b.y } |
| 282 | } |
| 283 | ``` |
| 284 | |
| 285 | To allow optimizations, `struct`s by default do not have a defined |
| 286 | representation in JavaScript. This means passing the struct to an opaque |
| 287 | function is not allowed. |
| 288 | |
| 289 | ```lang:build |
| 290 | let print = @extern("console").log; |
| 291 | |
| 292 | struct Person { |
| 293 | name: string, |
| 294 | age: int, |
| 295 | } |
| 296 | |
| 297 | fn main { |
| 298 | print(Person{ name: "hello", age: 42 }); |
| 299 | } |
| 300 | ``` |