diff --git a/next/_ext/lexer.py b/next/_ext/lexer.py index f9ccee04..eddb2feb 100644 --- a/next/_ext/lexer.py +++ b/next/_ext/lexer.py @@ -25,7 +25,11 @@ class MoonBitLexer(RegexLexer): (r"\$\|", token.String, "string.multiline"), (r"0(b|B)[01]+", token.Number.Bin), (r"0(o|O)[0-7]+", token.Number.Oct), - (r"0(x|X)[0-9a-fA-F]+", token.Number.Hex), + (r"0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.[0-9a-fA-F][0-9a-fA-F_]*(P|p)(\+|\-)?[0-9][0-9]*", token.Number.Float), + (r"0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.?(P|p)(\+|\-)?[0-9][0-9]*", token.Number.Float), + (r"0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.[0-9a-fA-F][0-9a-fA-F_]*", token.Number.Float), + (r"0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.", token.Number.Float), + (r"0(x|X)[0-9a-fA-F][0-9a-fA-F_]*", token.Number.Hex), (r"\d(_|\d)*U?L", token.Number.Integer.Long), (r"\d(_|\d)*U?", token.Number.Integer), (r"\d+(.\d+)?", token.Number), diff --git a/next/language/ffi-and-wasm-host.md b/next/language/ffi-and-wasm-host.md index 36d14118..d4538a7f 100644 --- a/next/language/ffi-and-wasm-host.md +++ b/next/language/ffi-and-wasm-host.md @@ -1,12 +1,17 @@ # Foreign Function Interface(FFI) +What we've introduced is about describing pure computation. In reality, you'll need +to interact with the real world. However, the "world" is different for each backend (C, JS, Wasm, WasmGC) +and is sometimes based on runtime ([Wasmtime](https://wasmtime.dev/), Deno, Browser, etc.). + You can use foreign function in MoonBit through FFI to interact with the hosting runtime when embedded inside the browser or command line applications through [Wasmtime](https://wasmtime.dev/) or similar projects. -⚠ Warning: MoonBit is still in early stage, so the content may be outdated. +## Init function -## FFI +For WebAssembly backend, it is compiled as [start function](https://webassembly.github.io/spec/core/syntax/modules.html#start-function), meaning that it will be executed **before** the instance is available, and the FFIs that relies on the instance's exportations can not be used at this stage; +for JavaScript backend, it means that it will be executed during the importation stage. -### Declare Foreign Reference +## Declare Foreign Reference You can declare a foreign reference type like this: @@ -16,7 +21,11 @@ type Canvas_ctx This will be a type that represents a reference to a foreign object, a `CanvasRenderingContext2D` object held by the hosting JavaScript runtime in this example. -### Declare Foreign Function +## Declare Foreign Function + +You can either import a function with module name and function name or writing an inline function. + +### Import function You can declare a foreign function like this: @@ -26,11 +35,15 @@ fn cos(d : Double) -> Double = "Math" "cos" It's similar to a normal function definition except that the function body is replaced with two strings. -For WasmGC backend, these two strings are used to identify the specific function from a Wasm import object, the first string is the module name, and the second string is the function name. For JS backend, these two strings are used to call a static function in the global namespace. The example above becomes similar to `const cos = (d) => Math.cos(d)`. +For Wasm(GC) backend, these two strings are used to identify the specific function from a Wasm import object, the first string is the module name, and the second string is the function name. + +For JS backend, these two strings are used to call a static function in the global namespace. The example above becomes similar to `const cos = (d) => Math.cos(d)`. + +### Inline function You can also declare inline functions where the function body is replaced with one string. -For WasmGC backend, you may declare it as a Wasm function without name (which will be generated afterwards): +For Wasm(GC) backend, you may declare it as a Wasm function without name (which will be generated afterwards): ```moonbit extern "wasm" fn abs(d : Double) -> Double = @@ -46,7 +59,7 @@ extern "js" fn abs(d : Double) -> Double = After declaration, you can use foreign functions like regular functions. -For multi-backend project, you may implement backend specific code in the files that ends with `.wasm.mbt` `.wasm-gc.mbt` and `.js.mbt`. +For multi-backend project, you may implement backend specific code in the files that ends with `.wasm.mbt` `.wasm-gc.mbt` and `.js.mbt`. Check out [link options]() for details. You may also declare a foreign function that will be invoked upon a foreign object by using the foreign reference type like this: @@ -56,7 +69,7 @@ fn begin_path(self: Canvas_ctx) = "canvas" "begin_path" and apply it to a previously owned reference normally such as `context.begin_path()`. -### Exported functions +## Export functions Functions that are not methods nor polymorphic functions can be exported if they are public and if the link configuration appears in the `moon.pkg.json` of the package: @@ -64,22 +77,13 @@ Functions that are not methods nor polymorphic functions can be exported if they { "link": { "wasm": { - "exports": [ - "add", - "fib:test" - ] + "exports": [ "add", "fib:test" ] }, "wasm-gc": { - "exports": [ - "add", - "fib:test" - ] + "exports": [ "add", "fib:test" ] }, "js": { - "exports": [ - "add", - "fib:test" - ], + "exports": [ "add", "fib:test" ], "format": "esm" } } @@ -90,13 +94,11 @@ Each backend has a separate definition. For JS backend, a `format` option is use The example above will export function `add` and `fib`, and the function `fib` will be exported with the name of `test`. -For WasmGC backend, the `_start` function should always be called to initialize all the global instances defined in MoonBit program. - -### Use compiled Wasm +## Use compiled Wasm To use the compiled Wasm, you need to initialize the Wasm module with the host functions so as to meet the needs of the foreign functions, and then use the exported functions provided by the Wasm module. -#### Provide host functions +### Provide host functions To use the compiled Wasm, you must provide **All** declared foreign functions in Wasm import object. diff --git a/next/language/fundamentals.md b/next/language/fundamentals.md index dcba979a..48183759 100644 --- a/next/language/fundamentals.md +++ b/next/language/fundamentals.md @@ -31,7 +31,9 @@ MoonBit also supports numeric literals, including decimal, binary, octal, and he To improve readability, you may place underscores in the middle of numeric literals such as `1_000_000`. Note that underscores can be placed anywhere within a number, not just every three digits. -- There is nothing surprising about decimal numbers. +- Decimal numbers can have underscore between the numbers. + + By default, an int literal is signed 32-bit number. For unsigned numbers, a postfix `U` is needed; for 64-bit numbers, a postfix `L` is needed. ```{literalinclude} /sources/language/src/builtin/top.mbt :language: moonbit @@ -70,9 +72,27 @@ To improve readability, you may place underscores in the middle of numeric liter :end-before: end number 4 ``` -#### Overloaded int literal +- A floating-point number literal is 64-bit floating-point number. To define a float, type annotation is needed. + + ```{literalinclude} /sources/language/src/builtin/top.mbt + :language: moonbit + :dedent: + :start-after: start number 6 + :end-before: end number 6 + ``` + + A 64-bit floating-point number can also be defined using hexadecimal format: + + ```{literalinclude} /sources/language/src/builtin/top.mbt + :language: moonbit + :dedent: + :start-after: start number 7 + :end-before: end number 7 + ``` + +#### Overloaded literal -When the expected type is known, MoonBit can automatically overload integer literal, and there is no need to specify the type of number via letter postfix: +When the expected type is known, MoonBit can automatically overload literal, and there is no need to specify the type of number via letter postfix: ```{literalinclude} /sources/language/src/builtin/top.mbt :language: moonbit @@ -130,7 +150,7 @@ Multi-line strings do not support interpolation by default, but you can enable i ### Char -`Char` is an integer representing a Unicode code point. +`Char` represents a Unicode code point. ```{literalinclude} /sources/language/src/builtin/top.mbt :language: moonbit @@ -207,9 +227,56 @@ You can use `numbers[x]` to refer to the xth element. The index starts from zero :end-before: end array 2 ``` +There are `Array[T]` and `FixedArray[T]`: + +- `Array[T]` can grow in size, while +- `FixedArray[T]` has a fixed size, thus it needs to be created with initial value. + +``````{warning} +A common pitfall is creating `FixedArray` with the same initial value: + +```{literalinclude} /sources/language/src/builtin/top.mbt +:language: moonbit +:dedent: +:start-after: start array pitfall +:end-before: end array pitfall +``` + +This is because all the cells reference to the same object (the `FixedArray[Int]` in this case). One should use `FixedArray::makei()` instead which creates an object for each index. + +```{literalinclude} /sources/language/src/builtin/top.mbt +:language: moonbit +:dedent: +:start-after: start array pitfall solution +:end-before: end array pitfall solution +``` +`````` + +When the expected type is known, MoonBit can automatically overload array, otherwise +`Array[T]` is created: + +```{literalinclude} /sources/language/src/builtin/top.mbt +:language: moonbit +:start-after: start array 3 +:end-before: end array 3 +``` + +#### ArrayView + +Analogous to `slice` in other languages, the view is a reference to a +specific segment of collections. You can use `data[start:end]` to create a +view of array `data`, referencing elements from `start` to `end` (exclusive). +Both `start` and `end` indices can be omitted. + +```{literalinclude} /sources/language/src/operator/top.mbt +:language: moonbit +:start-after: start view 1 +:end-before: end view 1 +``` + ### Map -MoonBit provides a hash map data structure that preserves insertion orde called `Map` in its standard library. +MoonBit provides a hash map data structure that preserves insertion order called `Map` in its standard library. `Map`s can be created via a convenient literal syntax: ```{literalinclude} /sources/language/src/builtin/top.mbt @@ -259,6 +326,14 @@ Local functions can be named or anonymous. Type annotations can be omitted for l :end-before: end local functions 1 ``` +There's also a form called **matrix function** that make use of [pattern matching](#pattern-matching): + +```{literalinclude} /sources/language/src/functions/top.mbt +:language: moonbit +:start-after: start local functions 3 +:end-before: end local functions 3 +``` + Functions, whether named or anonymous, are _lexical closures_: any identifiers without a local binding must refer to bindings from a surrounding lexical scope. For example: ```{literalinclude} /sources/language/src/functions/top.mbt @@ -294,7 +369,7 @@ The expression `add3(1, 2, 7)` returns `10`. Any expression that evaluates to a ### Labelled arguments -Functions can declare labelled argument with the syntax `label~ : Type`. `label` will also serve as parameter name inside function body: +**Top-level** functions can declare labelled argument with the syntax `label~ : Type`. `label` will also serve as parameter name inside function body: ```{literalinclude} /sources/language/src/functions/top.mbt :language: moonbit @@ -400,7 +475,7 @@ Autofill arguments are very useful for writing debugging and testing utilities. ### Conditional Expressions -A conditional expression consists of a condition, a consequent, and an optional else clause. +A conditional expression consists of a condition, a consequent, and an optional `else` clause or `else if` clause. ```{literalinclude} /sources/language/src/controls/top.mbt :language: moonbit @@ -409,24 +484,68 @@ A conditional expression consists of a condition, a consequent, and an optional :end-before: end conditional expressions 1 ``` -The else clause can also contain another if-else expression: +The curly brackets around the consequent are required. + +Note that a conditional expression always returns a value in MoonBit, and the return values of the consequent and the else clause must be of the same type. Here is an example: + +```{literalinclude} /sources/language/src/controls/top.mbt +:language: moonbit +:dedent: +:start-after: start conditional expressions 3 +:end-before: end conditional expressions 3 +``` + +The `else` clause can only be omitted if the return value has type `Unit`. + +### Match Expression + +The `match` expression is similar to conditional expression, but it uses [pattern matching](#pattern-matching) to decide which consequent to evaluate and extracting variables at the same time. ```{literalinclude} /sources/language/src/controls/top.mbt :language: moonbit :dedent: -:start-after: start conditional expressions 2 -:end-before: end conditional expressions 2 +:start-after: start match 1 +:end-before: end match 1 ``` -Curly brackets are used to group multiple expressions in the consequent or the else clause. +If a possible condition is omitted, the compiler will issue a warning, and the program will terminate if that case were reached. -Note that a conditional expression always returns a value in MoonBit, and the return values of the consequent and the else clause must be of the same type. Here is an example: +### Guard Statement + +The `guard` statement is used to check a specified invariant. +If the condition of the invariant is satisfied, the program continues executing +the subsequent statements and returns. If the condition is not satisfied (i.e., false), +the code in the `else` block is executed and its evaluation result is returned (the subsequent statements are skipped). ```{literalinclude} /sources/language/src/controls/top.mbt :language: moonbit :dedent: -:start-after: start conditional expressions 3 -:end-before: end conditional expressions 3 +:start-after: start guard 1 +:end-before: end guard 1 +``` + +#### Guarded Let + +The `let` statement can be used with [pattern matching](#pattern-matching). However, `let` statement can only handle one case. And `guard let` can solve this issue. + +In the following example, `getProcessedText` assumes that the input `path` points to resources that are all plain text, +and it uses the `guard` statement to ensure this invariant. Compared to using +a `match` statement, the subsequent processing of `text` can have one less level of indentation. + +```{literalinclude} /sources/language/src/controls/top.mbt +:language: moonbit +:start-after: start guard 2 +:end-before: end guard 2 +``` + +When the `else` part is omitted, the program terminates if the condition specified +in the `guard` statement is not true or cannot be matched. + +```{literalinclude} /sources/language/src/controls/top.mbt +:language: moonbit +:dedent: +:start-after: start guard 3 +:end-before: end guard 3 ``` ### While loop @@ -568,6 +687,15 @@ MoonBit supports traversing elements of different data structures and sequences `for .. in` loop is translated to the use of `Iter` in MoonBit's standard library. Any type with a method `.iter() : Iter[T]` can be traversed using `for .. in`. For more information of the `Iter` type, see [Iterator](#iterator) below. +`for .. in` loop also supports iterating through a sequence of integers, such as: + +```{literalinclude} /sources/language/src/controls/top.mbt +:language: moonbit +:dedent: +:start-after: start for loop 10 +:end-before: end for loop 10 +``` + In addition to sequences of a single value, MoonBit also supports traversing sequences of two values, such as `Map`, via the `Iter2` type in MoonBit's standard library. Any type with method `.iter2() : Iter2[A, B]` can be traversed using `for .. in` with two loop variables: @@ -620,41 +748,6 @@ A functional loop consumes arguments and returns a value. It is defined using th :end-before: end for loop 9 ``` -### Guard Statement - -The `guard` statement is used to check a specified invariant. -If the condition of the invariant is satisfied, the program continues executing -the subsequent statements and returns. If the condition is not satisfied (i.e., false), -the code in the `else` block is executed and its evaluation result is returned (the subsequent statements are skipped). - -```{literalinclude} /sources/language/src/controls/top.mbt -:language: moonbit -:dedent: -:start-after: start guard 1 -:end-before: end guard 1 -``` - -The `guard` statement also supports pattern matching: in the following example, -`getProcessedText` assumes that the input `path` points to resources that are all plain text, -and it uses the `guard` statement to ensure this invariant. Compared to using -a `match` statement, the subsequent processing of `text` can have one less level of indentation. - -```{literalinclude} /sources/language/src/controls/top.mbt -:language: moonbit -:start-after: start guard 2 -:end-before: end guard 2 -``` - -When the `else` part is omitted, the program terminates if the condition specified -in the `guard` statement is not true or cannot be matched. - -```{literalinclude} /sources/language/src/controls/top.mbt -:language: moonbit -:dedent: -:start-after: start guard 3 -:end-before: end guard 3 -``` - ## Iterator An iterator is an object that traverse through a sequence while providing access @@ -771,6 +864,15 @@ If you already have some variable like `name` and `email`, it's redundant to rep :end-before: end struct 3 ``` +If there's no other struct that has the same fields, it's redundant to add the struct's name when constructing it: + +```{literalinclude} /sources/language/src/data/top.mbt +:language: moonbit +:dedent: +:start-after: start struct 5 +:end-before: end struct 5 +``` + #### Struct Update Syntax It's useful to create a new struct based on an existing one, but with some fields updated. @@ -951,33 +1053,53 @@ MoonBit supports type alias via the syntax `typealias Name = TargetType`: :end-before: end typealias 1 ``` -unlike all other kinds of type declaration above, type alias does not define a new type, +Unlike all other kinds of type declaration above, type alias does not define a new type, it is merely a type macro that behaves exactly the same as its definition. So for example one cannot define new methods or implement traits for a type alias. +```{tip} Type alias can be used to perform incremental code refactor. + For example, if you want to move a type `T` from `@pkgA` to `@pkgB`, you can leave a type alias `typealias T = @pkgB.T` in `@pkgA`, and **incrementally** port uses of `@pkgA.T` to `@pkgB.T`. The type alias can be removed after all uses of `@pkgA.T` is migrated to `@pkgB.T`. +``` ## Pattern Matching -We have shown a use case of pattern matching for enums, but pattern matching is not restricted to enums. For example, we can also match expressions against Boolean values, numbers, characters, strings, tuples, arrays, and struct literals. Since there is only one case for those types other than enums, we can pattern match them using `let` binding instead of `match` expressions. Note that the scope of bound variables in `match` is limited to the case where the variable is introduced, while `let` binding will introduce every variable to the current scope. Furthermore, we can use underscores `_` as wildcards for the values we don't care about, use `..` to ignore remaining fields of struct or elements of array. +Pattern matching allows us to match on specific pattern and bind data from data structures. + +### Simple Patterns + +We can pattern match expressions against + +- literals, such as boolean values, numbers, chars, strings, etc +- constants +- structs +- enums +- arrays +- maps +- JSONs + +and so on. We can define identifiers to bind the matched values so that they can be used later. ```{literalinclude} /sources/language/src/pattern/top.mbt :language: moonbit :dedent: -:start-after: start pattern 1 -:end-before: end pattern 1 +:start-after: start simple pattern 1 +:end-before: end simple pattern 1 ``` +We can use `_` as wildcards for the values we don't care about, and use `..` to ignore remaining fields of struct or enum, or array (see [array pattern](#array-pattern)). + ```{literalinclude} /sources/language/src/pattern/top.mbt :language: moonbit -:start-after: start pattern 2 -:end-before: end pattern 2 +:dedent: +:start-after: start simple pattern 2 +:end-before: end simple pattern 2 ``` -There are some other useful constructs in pattern matching. For example, we can use `as` to give a name to some pattern, and we can use `|` to match several cases at once. A variable name can only be bound once in a single pattern, and the same set of variables should be bound on both sides of `|` patterns. +We can use `as` to give a name to some pattern, and we can use `|` to match several cases at once. A variable name can only be bound once in a single pattern, and the same set of variables should be bound on both sides of `|` patterns. ```{literalinclude} /sources/language/src/pattern/top.mbt :language: moonbit @@ -986,8 +1108,26 @@ There are some other useful constructs in pattern matching. For example, we can :end-before: end pattern 3 ``` +### Array Pattern + +For `Array`, `FixedArray` and `ArrayView`, MoonBit allows using array pattern. + +Array pattern have the following forms: + +- `[]` : matching for an empty data structure +- `[pa, pb, pc]` : matching for known number of elements, 3 in this example +- `[pa, ..]` : matching for known number of elements, followed by unknown number of elements +- `[.., pa]` : matching for known number of elements, preceded by unknown number of elements + +```{literalinclude} /sources/language/src/pattern/top.mbt +:language: moonbit +:start-after: start pattern 2 +:end-before: end pattern 2 +``` + ### Range Pattern For builtin integer types and `Char`, MoonBit allows matching whether the value falls in a specific range. + Range patterns have the form `a.. Option[V]` for some type `K` and `V`. -- Currently, the key part of map pattern must be a constant +- To match a data type `T` using map pattern, `T` must have a method `op_get(Self, K) -> Option[V]` for some type `K` and `V` (see [method and trait](./methods.md)). +- Currently, the key part of map pattern must be a literal or constant - Map patterns are always open: unmatched keys are silently ignored - Map pattern will be compiled to efficient code: every key will be fetched at most once ### Json Pattern -When the matched value has type `Json`, literal patterns can be used directly: +When the matched value has type `Json`, literal patterns can be used directly, together with constructors: ```{literalinclude} /sources/language/src/pattern/top.mbt :language: moonbit @@ -1043,6 +1183,55 @@ Generics are supported in top-level function and data type definitions. Type par ## Special Syntax +### Pipe operator + +MoonBit provides a convenient pipe operator `|>`, which can be used to chain regular function calls: + +```{literalinclude} /sources/language/src/operator/top.mbt +:language: moonbit +:dedent: +:start-after: start operator 4 +:end-before: end operator 4 +``` + +### Cascade Operator + +The cascade operator `..` is used to perform a series of mutable operations on +the same value consecutively. The syntax is as follows: + +```{literalinclude} /sources/language/src/operator/top.mbt +:language: moonbit +:dedent: +:start-after: start operator 5 +:end-before: end operator 5 +``` + +`x..f()..g()` is equivalent to `{x.f(); x.g(); x}`. + +Consider the following scenario: for a `StringBuilder` type that has methods +like `write_string`, `write_char`, `write_object`, etc., we often need to perform +a series of operations on the same `StringBuilder` value: + +```{literalinclude} /sources/language/src/operator/top.mbt +:language: moonbit +:dedent: +:start-after: start operator 6 +:end-before: end operator 6 +``` + +To avoid repetitive typing of `builder`, its methods are often designed to +return `self` itself, allowing operations to be chained using the `.` operator. +To distinguish between immutable and mutable operations, in MoonBit, +for all methods that return `Unit`, cascade operator can be used for +consecutive operations without the need to modify the return type of the methods. + +```{literalinclude} /sources/language/src/operator/top.mbt +:language: moonbit +:dedent: +:start-after: start operator 7 +:end-before: end operator 7 +``` + ### TODO syntax The `todo` syntax (`...`) is a special construct used to mark sections of code that are not yet implemented or are placeholders for future functionality. For example: diff --git a/next/language/introduction.md b/next/language/introduction.md index 3269c4ad..3fd8a0f1 100644 --- a/next/language/introduction.md +++ b/next/language/introduction.md @@ -7,46 +7,6 @@ A MoonBit program consists of top-level definitions including: - constant definitions and variable bindings - `init` functions, `main` function and/or `test` blocks. -## Program entrance - -There is a specialized function called `init` function. The `init` function is special in two aspects: - -1. There can be multiple `init` functions in the same package. -2. An `init` function can't be explicitly called or referred to by other functions. Instead, all `init` functions will be implicitly called when initializing a package. Therefore, `init` functions should only consist of statements. - -```{literalinclude} /sources/language/src/main/top.mbt -:language: moonbit -:start-after: start init -:end-before: end init -``` - -For WebAssembly backend, it means that it will be executed **before** the instance is available, meaning that the FFIs that relies on the instance's exportations can not be used at this stage; -for JavaScript backend, it means that it will be executed during the importation stage. - -There is another specialized function called `main` function. The `main` function is the main entrance of the program, and it will be executed after the initialization stage. - -```{literalinclude} /sources/language/src/main/top.mbt -:language: moonbit -:start-after: start main -:end-before: end main -``` - -The previous two code snippets will print the following at runtime: - -```bash -1 -2 -``` - -Only packages that are `main` packages can define such `main` function. Check out [build system tutorial](/toolchain/moon/tutorial) for detail. - -```{literalinclude} /sources/language/src/main/moon.pkg.json -:language: json -:caption: moon.pkg.json -``` - -The two functions above need to drop the parameter list and the return type. - ## Expressions and Statements MoonBit distinguishes between statements and expressions. In a function body, only the last clause should be an expression, which serves as a return value. For example: @@ -61,10 +21,10 @@ Expressions include: - Value literals (e.g. Boolean values, numbers, characters, strings, arrays, tuples, structs) - Arithmetical, logical, or comparison operations -- Accesses to array elements (e.g. `a[0]`) or struct fields (e.g `r.x`) or tuple components (e.g. `t.0`) +- Accesses to array elements (e.g. `a[0]`), struct fields (e.g `r.x`), tuple components (e.g. `t.0`), etc. - Variables and (capitalized) enum constructors - Anonymous local function definitions -- `match` and `if` expressions +- `match`, `if`, `loop` expressions, etc. Statements include: @@ -72,12 +32,16 @@ Statements include: - Local variable bindings - Assignments - `return` statements -- Any expression whose return type is `Unit` +- Any expression whose return type is `Unit`, (e.g. `ignore`) + +A code block can contain multiple statements and one expression, and the value of the expression is the value of the code block. ## Variable Binding A variable can be declared as mutable or immutable using `let mut` or `let`, respectively. A mutable variable can be reassigned to a new value, while an immutable one cannot. +A constant can only be declared at top level and cannot be changed. + ```{literalinclude} /sources/language/src/variable/top.mbt :language: moonbit ``` @@ -88,4 +52,57 @@ Variables, functions should start with lowercase letters `a-z` and can contain l It is recommended to name them with snake_case. Constants, types should start with uppercase letters `A-Z` and can contain letters, numbers, and other non-ascii unicode chars. -It is recommended to name them with PascalCase or SCREAMING_SNAKE_CASE. \ No newline at end of file +It is recommended to name them with PascalCase or SCREAMING_SNAKE_CASE. + +## Program entrance + +### `init` and `main` +There is a specialized function called `init` function. The `init` function is special: + +1. It has no parameter list nor return type. +2. There can be multiple `init` functions in the same package. +3. An `init` function can't be explicitly called or referred to by other functions. +Instead, all `init` functions will be implicitly called when initializing a package. Therefore, `init` functions should only consist of statements. + +```{literalinclude} /sources/language/src/main/top.mbt +:language: moonbit +:start-after: start init +:end-before: end init +``` + +There is another specialized function called `main` function. The `main` function is the main entrance of the program, and it will be executed after the initialization stage. + +Same as the `init` function, it has no parameter list nor return type. + +```{literalinclude} /sources/language/src/main/top.mbt +:language: moonbit +:start-after: start main +:end-before: end main +``` + +The previous two code snippets will print the following at runtime: + +```bash +1 +2 +``` + +Only packages that are `main` packages can define such `main` function. Check out [build system tutorial](/toolchain/moon/tutorial) for detail. + +```{literalinclude} /sources/language/src/main/moon.pkg.json +:language: json +:caption: moon.pkg.json +``` + +### `test` + +There's also a top-level structure called `test` block. A `test` block defines inline tests, such as: + +```{literalinclude} /sources/language/src/test/top.mbt +:language: moonbit +:start-after: start test 1 +:end-before: end test 1 +``` + +The following contents will use `test` block and `main` function to demonstrate the execution result, +and we assume that all the `test` blocks pass unless stated otherwise. \ No newline at end of file diff --git a/next/language/methods.md b/next/language/methods.md index e9d9206d..40c16366 100644 --- a/next/language/methods.md +++ b/next/language/methods.md @@ -110,84 +110,14 @@ Currently, the following operators can be overloaded: | `_[_]` (get item) | `op_get` | | `_[_] = _` (set item) | `op_set` | | `_[_:_]` (view) | `op_as_view` | - -### Pipe operator - -MoonBit provides a convenient pipe operator `|>`, which can be used to chain regular function calls: - -```{literalinclude} /sources/language/src/operator/top.mbt -:language: moonbit -:dedent: -:start-after: start operator 4 -:end-before: end operator 4 -``` - -### Cascade Operator - -The cascade operator `..` is used to perform a series of mutable operations on -the same value consecutively. The syntax is as follows: - -```{literalinclude} /sources/language/src/operator/top.mbt -:language: moonbit -:dedent: -:start-after: start operator 5 -:end-before: end operator 5 -``` - -`x..f()..g()` is equivalent to `{x.f(); x.g(); x}`. - -Consider the following scenario: for a `StringBuilder` type that has methods -like `write_string`, `write_char`, `write_object`, etc., we often need to perform -a series of operations on the same `StringBuilder` value: - -```{literalinclude} /sources/language/src/operator/top.mbt -:language: moonbit -:dedent: -:start-after: start operator 6 -:end-before: end operator 6 -``` - -To avoid repetitive typing of `builder`, its methods are often designed to -return `self` itself, allowing operations to be chained using the `.` operator. -To distinguish between immutable and mutable operations, in MoonBit, -for all methods that return `Unit`, cascade operator can be used for -consecutive operations without the need to modify the return type of the methods. - -```{literalinclude} /sources/language/src/operator/top.mbt -:language: moonbit -:dedent: -:start-after: start operator 7 -:end-before: end operator 7 -``` - -### Bitwise Operator - -MoonBit supports C-Style bitwise operators. - -| Operator | Perform | -| -------- | ------- | | `&` | `land` | | `\|` | `lor` | | `^` | `lxor` | | `<<` | `op_shl` | | `>>` | `op_shr` | -### View Operator - - - -Analogous to `slice` in other languages, the view is a reference to a -specific segment of collections. You can use `data[start:end]` to create a -view of array `data`, referencing elements from `start` to `end` (exclusive). -Both `start` and `end` indices can be omitted. - -```{literalinclude} /sources/language/src/operator/top.mbt -:language: moonbit -:start-after: start view 1 -:end-before: end view 1 -``` -By implementing `op_as_view` method, you can also create a view for a user-defined type. Here is an example: +By implementing `op_as_view` method, you can create a view for a user-defined type. Here is an example: ```{literalinclude} /sources/language/src/operator/top.mbt :language: moonbit @@ -255,35 +185,19 @@ Without the `Number` requirement, the expression `x * x` in `square` will result :end-before: end trait 6 ``` -MoonBit provides the following useful builtin traits: - - - -```moonbit -trait Eq { - op_equal(Self, Self) -> Bool -} - -trait Compare : Eq { - // `0` for equal, `-1` for smaller, `1` for greater - compare(Self, Self) -> Int -} - -trait Hash { - hash(Self) -> Int -} +### Super trait -trait Show { - // writes a string representation of `Self` into a `Logger` - output(Self, Logger) -> Unit - to_string(Self) -> String -} +A trait can depend on other traits, for example: -trait Default { - default() -> Self -} +```{literalinclude} /sources/language/src/trait/top.mbt +:language: moonbit +:start-after: start super trait 1 +:end-before: end super trait 1 ``` +To implement the super trait, one will have to implement the sub traits, +and the methods defined in the super trait. + ### Invoke trait methods directly Methods of a trait can be called directly via `Trait::method`. MoonBit will infer the type of `Self` and check if `Self` indeed implements `Trait`, for example: @@ -344,7 +258,38 @@ Users can define new methods for trait objects, just like defining new methods f :end-before: end trait object 2 ``` -## Automatically derive builtin traits +## Builtin traits + +MoonBit provides the following useful builtin traits: + + + +```moonbit +trait Eq { + op_equal(Self, Self) -> Bool +} + +trait Compare : Eq { + // `0` for equal, `-1` for smaller, `1` for greater + compare(Self, Self) -> Int +} + +trait Hash { + hash(Self) -> Int +} + +trait Show { + // writes a string representation of `Self` into a `Logger` + output(Self, Logger) -> Unit + to_string(Self) -> String +} + +trait Default { + default() -> Self +} +``` + +### Deriving builtin traits MoonBit can automatically derive implementations for some builtin traits: diff --git a/next/sources/language/src/builtin/top.mbt b/next/sources/language/src/builtin/top.mbt index 66b4fa38..138310e0 100644 --- a/next/sources/language/src/builtin/top.mbt +++ b/next/sources/language/src/builtin/top.mbt @@ -12,13 +12,10 @@ let boolean : Unit = { let number : Unit = { // start number 1 let a = 1234 - let b = 1_000_000 + a - // UInt : 0_U - let unsigned_num = 4_294_967_295U - // Int64 : 0_L - let large_num = 9_223_372_036_854_775_807L - // UInt64 : 0_UL - let unsigned_large_num = 18_446_744_073_709_551_615UL + let b : Int = 1_000_000 + a + let unsigned_num : UInt = 4_294_967_295U + let large_num : Int64 = 9_223_372_036_854_775_807L + let unsigned_large_num : UInt64 = 18_446_744_073_709_551_615UL // end number 1 // start number 2 @@ -33,9 +30,19 @@ let number : Unit = { // start number 4 let hex = 0XA - let another_hex = 0xA + let another_hex = 0xA_B_C // end number 4 + // start number 6 + let double = 3.14 // Double + let float : Float = 3.14 + let float2 = (3.14 : Float) + // end number 6 + + // start number 7 + let hex_double = 0x1.2P3 // (1.0 + 2 / 16) * 2^(+3) == 9 + // end number 7 + // start number 5 let int : Int = 42 let uint : UInt = 42 @@ -160,6 +167,31 @@ test { } // end array 2 +// start array 3 +let fixed_array_1 : FixedArray[Int] = [1, 2, 3] +let fixed_array_2 = ([1, 2, 3] : FixedArray[Int]) +let array_3 = [1, 2, 3] // Array[Int] +// end array 3 + +// start array pitfall +test { + let two_dimension_array = FixedArray::make(10, FixedArray::make(10, 0)) + two_dimension_array[0][5] = 10 + assert_eq!(two_dimension_array[5][5], 10) +} +// end array pitfall + +// start array pitfall solution +test { + let two_dimension_array = FixedArray::makei( + 10, + fn (_i) { FixedArray::make(10, 0) } + ) + two_dimension_array[0][5] = 10 + assert_eq!(two_dimension_array[5][5], 0) +} +// end array pitfall solution + // start map 1 let map : Map[String, Int] = { "x": 1, "y": 2, "z": 3 } // end map 1 diff --git a/next/sources/language/src/controls/top.mbt b/next/sources/language/src/controls/top.mbt index cbc6a2b1..768859cc 100644 --- a/next/sources/language/src/controls/top.mbt +++ b/next/sources/language/src/controls/top.mbt @@ -1,28 +1,21 @@ fn a() -> Int { let x = 1 let y = 1 + let z = 1 let expr1 = 1 let expr2 = 1 + let expr3 = 1 // start conditional expressions 1 if x == y { expr1 - } else { + } else if x == z { expr2 + } else { + expr3 } // end conditional expressions 1 } -fn b() -> Unit { - let x = 1 - let y = 1 - let expr1 = () - // start conditional expressions 2 - if x == y { - expr1 - } - // end conditional expressions 2 -} - fn c() -> Unit { let size = 0 // start conditional expressions 3 @@ -211,13 +204,32 @@ test { } // end for loop 9 -fn f() -> Unit { - let index = 1 - let len = 10 - // start guard 1 - guard index >= 0 && index < len else { abort("Index out of range") } - // end guard 1 +// start for loop 10 +test { + let mut i = 0 + for j in 0..<10 { + i += j + } + assert_eq!(i, 45) + + let mut k = 0 + for l in 0..=10 { + k += l + } + assert_eq!(k, 55) +} +// end for loop 10 + +// start guard 1 +fn guarded_get(array : Array[Int], index : Int) -> Int? { + guard index >= 0 && index < array.length() else { None } + Some(array[index]) +} + +test { + inspect!(guarded_get([1, 2, 3], -1), content="None") } +// end guard 1 fn process(string : String) -> String { string @@ -252,3 +264,17 @@ fn g() -> Unit { // <=> guard let Some(x) = expr else { _ => panic() } // end guard 3 } + +// start match 1 +fn decide_sport(weather : String, humidity : Int) -> String { + match weather { + "sunny" => "tennis" + "rainy" => if humidity > 80 { "swimming" } else { "football" } + _ => "unknown" + } +} + +test { + assert_eq!(decide_sport("sunny", 0), "tennis") +} +// end match 1 \ No newline at end of file diff --git a/next/sources/language/src/data/top.mbt b/next/sources/language/src/data/top.mbt index 009ec9f2..ffdcfa55 100644 --- a/next/sources/language/src/data/top.mbt +++ b/next/sources/language/src/data/top.mbt @@ -9,8 +9,9 @@ struct User { test "struct 1" (t : @test.T) { let println = fn(show) { t.writeln(show) } // start struct 2 - let u = { id: 0, name: "John Doe", email: "john@doe.com" } + let u = User::{ id: 0, name: "John Doe", email: "john@doe.com" } u.email = "john@doe.name" + //! u.id = 10 println(u.id) println(u.name) println(u.email) @@ -22,9 +23,11 @@ let struct_3 : Unit = { // start struct 3 let name = "john" let email = "john@doe.com" - let u = { id: 0, name, email } + let u = User::{ id: 0, name, email } // end struct 3 - + // start struct 5 + let u2 = { id : 0, name, email } + // end struct 5 } test "struct 4" (t : @test.T) { diff --git a/next/sources/language/src/functions/top.mbt b/next/sources/language/src/functions/top.mbt index 4713bb57..f0f74c8a 100644 --- a/next/sources/language/src/functions/top.mbt +++ b/next/sources/language/src/functions/top.mbt @@ -6,7 +6,7 @@ fn foo() -> Int { fn bar() -> Int { let x = 1 - // x + 1 // fail + //! x + 1 x + 2 } // end expression @@ -38,7 +38,7 @@ fn local_1() -> Int { fn inc(x) { // named as `inc` x + 1 } - // anonymous, instantly appplied to integer literal 6 + // anonymous, instantly applied to integer literal 6 (fn(x) { x + inc(2) })(6) } @@ -67,6 +67,13 @@ test { } // end local functions 2 +// start local functions 3 +let extract : (Int?, Int) -> Int = fn { + Some(x), _ => x + None, default => default +} +// end local functions 3 + // start labelled arguments 1 fn labelled_1(arg1~ : Int, arg2~ : Int) -> Int { arg1 + arg2 diff --git a/next/sources/language/src/pattern/top.mbt b/next/sources/language/src/pattern/top.mbt index f7f6be39..b893f300 100644 --- a/next/sources/language/src/pattern/top.mbt +++ b/next/sources/language/src/pattern/top.mbt @@ -39,6 +39,7 @@ enum Arith { fn eval(expr : Arith) -> Int { // start pattern 3 match expr { + //! Add(e1, e2) | Lit(e1) => ... Lit(n) as a => ... Add(e1, e2) | Mul(e1, e2) => ... _ => ... @@ -86,7 +87,48 @@ fn json() -> Unit { // start pattern 6 match json { { "version": "1.0.0", "import": [..] as imports } => ... + { "version": Number(i), "import": Array(imports)} => ... _ => ... } // end pattern 6 } + +// start simple pattern 1 +const ONE = 1 + +fn match_int(x : Int) -> Unit { + match x { + 0 => println("zero") + ONE => println("one") + value => println(value) + } +} +// end simple pattern 1 + +// start simple pattern 2 +struct Point3D { + x : Int + y : Int + z : Int +} + +fn match_point3D(p : Point3D) -> Unit { + match p { + { x: 0, .. } => println("on yz-plane") + _ => println("not on yz-plane") + } +} + +enum Point[T] { + Point2D(Int, Int, name~: String, payload~ : T) +} + +fn match_point[T](p : Point[T]) -> Unit { + match p { + //! Point2D(0, 0) => println("2D origin") + Point2D(0, 0, ..) => println("2D origin") + Point2D(_) => println("2D point") + _ => panic() + } +} +// end simple pattern 2 \ No newline at end of file diff --git a/next/sources/language/src/test/top.mbt b/next/sources/language/src/test/top.mbt index 4e7d3454..a0a34184 100644 --- a/next/sources/language/src/test/top.mbt +++ b/next/sources/language/src/test/top.mbt @@ -2,6 +2,7 @@ test "test_name" { assert_eq!(1 + 1, 2) assert_eq!(2 + 2, 4) + inspect!([1, 2, 3], content="[1, 2, 3]") } // end test 1 diff --git a/next/sources/language/src/trait/top.mbt b/next/sources/language/src/trait/top.mbt index a3b73fd3..6dde334a 100644 --- a/next/sources/language/src/trait/top.mbt +++ b/next/sources/language/src/trait/top.mbt @@ -102,6 +102,17 @@ test { } // end trait 9 +// start super trait 1 +trait Position { + pos(Self) -> (Int, Int) +} +trait Draw { + draw(Self) -> Unit +} + +trait Object : Position + Draw {} +// end super trait 1 + // start trait object 1 trait Animal { speak(Self) -> String diff --git a/next/sources/language/src/variable/top.mbt b/next/sources/language/src/variable/top.mbt index 0172ee78..93980140 100644 --- a/next/sources/language/src/variable/top.mbt +++ b/next/sources/language/src/variable/top.mbt @@ -1,7 +1,10 @@ let zero = 0 +const ZERO = 0 + fn main { + //! const ZERO = 0 let mut i = 10 i = 20 - println(i + zero) + println(i + zero + ZERO) }