moonscratch 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/moonbit-agent-guide/LICENSE +202 -0
- package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +1126 -0
- package/.agents/skills/moonbit-agent-guide/SKILL.md +1126 -0
- package/.agents/skills/moonbit-agent-guide/ide.md +116 -0
- package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +106 -0
- package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +422 -0
- package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +422 -0
- package/.agents/skills/moonbit-practice/SKILL.md +258 -0
- package/.agents/skills/moonbit-practice/assets/ci.yaml +25 -0
- package/.agents/skills/moonbit-practice/reference/agents.md +1469 -0
- package/.agents/skills/moonbit-practice/reference/configuration.md +228 -0
- package/.agents/skills/moonbit-practice/reference/ffi.md +229 -0
- package/.agents/skills/moonbit-practice/reference/ide.md +189 -0
- package/.agents/skills/moonbit-practice/reference/performance.md +217 -0
- package/.agents/skills/moonbit-practice/reference/refactor.md +154 -0
- package/.agents/skills/moonbit-practice/reference/stdlib.md +351 -0
- package/.agents/skills/moonbit-practice/reference/testing.md +228 -0
- package/.agents/skills/moonbit-refactoring/LICENSE +21 -0
- package/.agents/skills/moonbit-refactoring/SKILL.md +323 -0
- package/.githooks/README.md +23 -0
- package/.githooks/pre-commit +3 -0
- package/.github/workflows/copilot-setup-steps.yml +40 -0
- package/.turbo/turbo-typecheck.log +2 -0
- package/AGENTS.md +91 -0
- package/LICENSE +21 -0
- package/PLAN.md +64 -0
- package/README.mbt.md +77 -0
- package/README.md +84 -0
- package/TODO.md +120 -0
- package/a.png +0 -0
- package/benchmarks/calc.bench.ts +144 -0
- package/benchmarks/draw.bench.ts +215 -0
- package/benchmarks/load.bench.ts +28 -0
- package/benchmarks/render.bench.ts +53 -0
- package/benchmarks/run.bench.ts +8 -0
- package/benchmarks/types.d.ts +15 -0
- package/docs/scratch-vm-specs/eventloop.md +103 -0
- package/docs/scratch-vm-specs/moonscratch-time-separation.md +50 -0
- package/index.html +91 -0
- package/js/AGENTS.md +5 -0
- package/js/a.ts +52 -0
- package/js/assets/AGENTS.md +5 -0
- package/js/assets/base64.test.ts +14 -0
- package/js/assets/base64.ts +21 -0
- package/js/assets/build-asset.test.ts +26 -0
- package/js/assets/build-asset.ts +28 -0
- package/js/assets/create.test.ts +142 -0
- package/js/assets/create.ts +122 -0
- package/js/assets/index.test.ts +15 -0
- package/js/assets/index.ts +2 -0
- package/js/assets/types.ts +26 -0
- package/js/assets/validation.test.ts +34 -0
- package/js/assets/validation.ts +25 -0
- package/js/assets.test.ts +14 -0
- package/js/assets.ts +1 -0
- package/js/index.test.ts +26 -0
- package/js/index.ts +3 -0
- package/js/render/index.test.ts +65 -0
- package/js/render/index.ts +13 -0
- package/js/render/sharp.ts +87 -0
- package/js/render/svg.ts +68 -0
- package/js/render/types.ts +35 -0
- package/js/render/utils.ts +108 -0
- package/js/render/webgl.ts +274 -0
- package/js/sharp-optional.d.ts +16 -0
- package/js/test/helpers.ts +116 -0
- package/js/test/hikkaku-sample.test.ts +37 -0
- package/js/test/rubik-components.input-motion.test.ts +60 -0
- package/js/test/rubik-components.lists.test.ts +49 -0
- package/js/test/rubik-components.operators.test.ts +104 -0
- package/js/test/rubik-components.pen.test.ts +112 -0
- package/js/test/rubik-components.procedures-loops.test.ts +72 -0
- package/js/test/rubik-components.variables-branches.test.ts +57 -0
- package/js/test/rubik-components.visibility-entry.test.ts +31 -0
- package/js/test/test-projects.ts +598 -0
- package/js/test/variable.ts +200 -0
- package/js/test/warp.test.ts +59 -0
- package/js/vm/AGENTS.md +6 -0
- package/js/vm/README.md +183 -0
- package/js/vm/bindings.test.ts +13 -0
- package/js/vm/bindings.ts +5 -0
- package/js/vm/compare-operators.test.ts +145 -0
- package/js/vm/constants.test.ts +11 -0
- package/js/vm/constants.ts +4 -0
- package/js/vm/effect-guards.test.ts +68 -0
- package/js/vm/effect-guards.ts +44 -0
- package/js/vm/factory.test.ts +486 -0
- package/js/vm/factory.ts +615 -0
- package/js/vm/headless-vm.test.ts +131 -0
- package/js/vm/headless-vm.ts +342 -0
- package/js/vm/index.test.ts +28 -0
- package/js/vm/index.ts +5 -0
- package/js/vm/internal-types.ts +32 -0
- package/js/vm/json.test.ts +40 -0
- package/js/vm/json.ts +273 -0
- package/js/vm/normalize.test.ts +48 -0
- package/js/vm/normalize.ts +65 -0
- package/js/vm/options.test.ts +30 -0
- package/js/vm/options.ts +55 -0
- package/js/vm/pen-transparency.test.ts +115 -0
- package/js/vm/program-wasm.ts +322 -0
- package/js/vm/scheduler-render.test.ts +401 -0
- package/js/vm/scratch-assets.test.ts +136 -0
- package/js/vm/scratch-assets.ts +202 -0
- package/js/vm/types.ts +358 -0
- package/js/vm/value-guards.test.ts +25 -0
- package/js/vm/value-guards.ts +18 -0
- package/moon.mod.json +10 -0
- package/package.json +33 -0
- package/scripts/preinstall.ts +4 -0
- package/src/AGENTS.md +6 -0
- package/src/api.mbt +161 -0
- package/src/api_aot_commands.mbt +184 -0
- package/src/api_effects_json.mbt +72 -0
- package/src/api_options.mbt +60 -0
- package/src/api_program_wasm.mbt +1647 -0
- package/src/api_program_wat.mbt +2206 -0
- package/src/api_snapshot_json.mbt +44 -0
- package/src/cmd/AGENTS.md +5 -0
- package/src/cmd/main/AGENTS.md +5 -0
- package/src/cmd/main/main.mbt +29 -0
- package/src/cmd/main/moon.pkg +7 -0
- package/src/cmd/main/pkg.generated.mbti +13 -0
- package/src/json_helpers.mbt +176 -0
- package/src/moon.pkg +65 -0
- package/src/moonscratch.mbt +3 -0
- package/src/moonscratch_wbtest.mbt +40 -0
- package/src/parser_sb3.mbt +890 -0
- package/src/pkg.generated.mbti +479 -0
- package/src/runtime_eval.mbt +2844 -0
- package/src/runtime_exec.mbt +3850 -0
- package/src/runtime_render.mbt +2550 -0
- package/src/runtime_state.mbt +870 -0
- package/src/test/AGENTS.md +3 -0
- package/src/test/projects/AGENTS.md +6 -0
- package/src/test/projects/moon.pkg +4 -0
- package/src/test/projects/moonscratch_compat_test.mbt +642 -0
- package/src/test/projects/moonscratch_core_test.mbt +1332 -0
- package/src/test/projects/moonscratch_runtime_test.mbt +1087 -0
- package/src/test/projects/pkg.generated.mbti +13 -0
- package/src/test/projects/test_support.mbt +35 -0
- package/src/types_effects.mbt +20 -0
- package/src/types_error.mbt +4 -0
- package/src/types_options.mbt +31 -0
- package/src/types_runtime_structs.mbt +254 -0
- package/src/types_vm.mbt +109 -0
- package/tsconfig.json +29 -0
- package/viewer/index.ts +399 -0
- package/viewer/vite.d.ts +1 -0
- package/viewer/worker.ts +161 -0
- package/vite.config.ts +11 -0
|
@@ -0,0 +1,1469 @@
|
|
|
1
|
+
----
|
|
2
|
+
title: MoonBit Project Layouts
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
-
|
|
6
|
+
|
|
7
|
+
# MoonBit Project Layouts
|
|
8
|
+
|
|
9
|
+
You have the ability to detect specific types of MoonBit projects and work with
|
|
10
|
+
them adaptively.
|
|
11
|
+
|
|
12
|
+
MoonBit source files use the `.mbt` extension and interface files `.mbti`. At
|
|
13
|
+
the top-level of a MoonBit project there is a `moon.mod.json` file specifying
|
|
14
|
+
the metadata of the project. The project may contain multiple packages, each
|
|
15
|
+
with its own `moon.pkg.json` file.
|
|
16
|
+
|
|
17
|
+
Here are some typical project layouts you may encounter:
|
|
18
|
+
|
|
19
|
+
- **Module**: When you see a `moon.mod.json` file in the project directory, you
|
|
20
|
+
are already in a MoonBit project.
|
|
21
|
+
A MoonBit _module_ is like a Go module.
|
|
22
|
+
It is a collection of packages, usually corresponding to a repository or project.
|
|
23
|
+
Module boundaries matter for dependency management and import paths.
|
|
24
|
+
A module contains many packages in subdirectories.
|
|
25
|
+
|
|
26
|
+
- **Package**: When you see a `moon.pkg.json` file, but not a `moon.mod.json`
|
|
27
|
+
file, it means you are in a MoonBit package. All subcommands of `moon` will
|
|
28
|
+
still be executed in the directory of the module (where `moon.mod.json` is
|
|
29
|
+
located), not the current package.
|
|
30
|
+
A MoonBit _package_ is the actual compilation unit (like a Go package).
|
|
31
|
+
All source files in the same package are concatenated into one unit.
|
|
32
|
+
The `package` name in the source defines the package, not the file name.
|
|
33
|
+
Imports refer to module + package paths, NEVER to file names.
|
|
34
|
+
|
|
35
|
+
- **Files**:
|
|
36
|
+
A `.mbt` file is just a chunk of source inside a package.
|
|
37
|
+
File names do NOT create modules or namespaces.
|
|
38
|
+
You may freely split/merge/move declarations between files in the same package.
|
|
39
|
+
Any declaration in a package can reference any other declaration in that package, regardless of file.
|
|
40
|
+
|
|
41
|
+
## Coding/layout rules you MUST follow:
|
|
42
|
+
|
|
43
|
+
1. Prefer many small, cohesive files over one large file.
|
|
44
|
+
- Group related types and functions into focused files (e.g. http_client.mbt, router.mbt).
|
|
45
|
+
- If a file is getting large or unfocused, create a new file and move related declarations into it.
|
|
46
|
+
|
|
47
|
+
2. You MAY freely move declarations between files inside the same package.
|
|
48
|
+
- Moving a function/struct/trait between files does not change semantics, as long as its name and pub-ness stay the same.
|
|
49
|
+
- It is safe to refactor by splitting or merging files inside a package.
|
|
50
|
+
|
|
51
|
+
3. File names are purely organizational.
|
|
52
|
+
- Do NOT assume file names define modules, and do NOT use file names in type paths.
|
|
53
|
+
- Choose file names to describe a feature or responsibility, not to mirror type names rigidly.
|
|
54
|
+
|
|
55
|
+
4. When adding new code:
|
|
56
|
+
- Prefer adding it to an existing file that matches the feature.
|
|
57
|
+
- If no good file exists, create a new file under the same package with a descriptive name.
|
|
58
|
+
- Avoid creating giant “misc” or “util” files.
|
|
59
|
+
|
|
60
|
+
5. Tests:
|
|
61
|
+
- Place tests in dedicated test files (e.g. \*\_test.mbt) within the appropriate package.
|
|
62
|
+
- It is fine—and encouraged—to have multiple small test files.
|
|
63
|
+
|
|
64
|
+
## `.mbti` Files - Package Interface Documentation
|
|
65
|
+
|
|
66
|
+
MoonBit interface files (`pkg.generated.mbti`) are compiler-generated summaries of each package's public API surface. They provide a formal, concise overview of all exported types, functions, and traits without implementation details.
|
|
67
|
+
|
|
68
|
+
**Standard library interfaces** are available in `~/.moon/lib/core`:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
$ tree -P '*.mbti' -I 'internal' --prune ~/.moon/lib/core # ignore internal packages
|
|
72
|
+
/Users/username/.moon/lib/core
|
|
73
|
+
├── builtin
|
|
74
|
+
│ └── pkg.generated.mbti
|
|
75
|
+
├── array
|
|
76
|
+
│ └── pkg.generated.mbti
|
|
77
|
+
├── bench
|
|
78
|
+
│ └── pkg.generated.mbti
|
|
79
|
+
├── bigint
|
|
80
|
+
│ └── pkg.generated.mbti
|
|
81
|
+
.....
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**When to use each approach**:
|
|
85
|
+
|
|
86
|
+
- Use `moon doc` for interactive API discovery (preferred, see "API Discovery with `moon doc`" section below)
|
|
87
|
+
- Read `.mbti` files directly when you need the complete API surface at once or when working offline
|
|
88
|
+
|
|
89
|
+
**Reading `.mbti` files for API discovery**:
|
|
90
|
+
|
|
91
|
+
- **Start with `builtin/pkg.generated.mbti`** - contains core types (String, Int, Array, etc.) and their fundamental APIs
|
|
92
|
+
- **Note**: Some builtin types like `String` expose APIs in both `builtin` and their dedicated packages (e.g., `String`)
|
|
93
|
+
- **Local dependencies**: Find `.mbti` files in the `.mooncakes` directory by searching for `pkg.generated.mbti`
|
|
94
|
+
- **Your own packages**: After running `moon info`, check the generated `.mbti` in each package directory to verify public API changes
|
|
95
|
+
|
|
96
|
+
# MoonBit Language Fundamentals
|
|
97
|
+
|
|
98
|
+
## Core Facts
|
|
99
|
+
|
|
100
|
+
Core facts that impact how you write and refactor code.
|
|
101
|
+
|
|
102
|
+
- **Expression‑oriented**: `if`, `match`, loops return values; last expression is the return.
|
|
103
|
+
- **References by default**: Arrays/Maps/structs mutate via reference; use `Ref[T]` for primitive mutability.
|
|
104
|
+
- **Errors**: Functions declare `raise ...`; use `try?` for `Result` or `try { } catch { }` to handle.
|
|
105
|
+
- **Blocks**: Separate top‑level items with `///|`. Generate code block‑by‑block.
|
|
106
|
+
- **Visibility**: `fn` private by default; `pub` exposes read/construct as allowed; `pub(all)` allows external construction.
|
|
107
|
+
- **Naming convention**: lower_snake for values/functions; UpperCamel for types/enums; enum variants start UpperCamel.
|
|
108
|
+
- **Packages**: No `import` in code files; call via `@alias.fn`. Configure imports in `moon.pkg.json`.
|
|
109
|
+
- **Placeholders**: `...` is a valid placeholder in MoonBit code for incomplete implementations.
|
|
110
|
+
- **Global values**: immutable by default and generally require type annotations.
|
|
111
|
+
- **Garbage collection**: MoonBit has a GC, there is no lifetime annotation, there's no ownership system.
|
|
112
|
+
<Important> Delimit top-level items with `///|` comments so tools can split the file reliably.
|
|
113
|
+
</Important>
|
|
114
|
+
|
|
115
|
+
Quick reference:
|
|
116
|
+
|
|
117
|
+
```mbt check
|
|
118
|
+
///|
|
|
119
|
+
/// comments doc string
|
|
120
|
+
pub fn sum(x : Int, y : Int) -> Int {
|
|
121
|
+
x + y
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
///|
|
|
125
|
+
/// error declaration and usage
|
|
126
|
+
suberror MySubError
|
|
127
|
+
|
|
128
|
+
///|
|
|
129
|
+
fn risky() -> Int raise MySubError {
|
|
130
|
+
raise MySubError::MySubError
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
///|
|
|
134
|
+
struct Rect {
|
|
135
|
+
width : Int
|
|
136
|
+
height : Int
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
///|
|
|
140
|
+
fn Rect::area(self : Rect) -> Int {
|
|
141
|
+
self.width * self.height
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
///|
|
|
145
|
+
pub impl Show for Rect with output(_self, logger) {
|
|
146
|
+
logger.write_string("Rect")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
///|
|
|
150
|
+
enum MyOption {
|
|
151
|
+
MyNone
|
|
152
|
+
MySome(Int)
|
|
153
|
+
} derive(Show, ToJson, Eq, Compare)
|
|
154
|
+
|
|
155
|
+
///|
|
|
156
|
+
/// match + loops are expressions
|
|
157
|
+
test "everything is expression in MoonBit" {
|
|
158
|
+
// tuple
|
|
159
|
+
let (n, opt) = (1, MySome(2))
|
|
160
|
+
// if expressions return values
|
|
161
|
+
let msg : String = if n > 0 { "pos" } else { "non-pos" }
|
|
162
|
+
let res = match opt {
|
|
163
|
+
MySome(x) => {
|
|
164
|
+
inspect(x, content="2")
|
|
165
|
+
1
|
|
166
|
+
}
|
|
167
|
+
MyNone => 0
|
|
168
|
+
}
|
|
169
|
+
let status : Result[Int, String] = Ok(10)
|
|
170
|
+
// match expressions return values
|
|
171
|
+
let description = match status {
|
|
172
|
+
Ok(value) => "Success: \{value}"
|
|
173
|
+
Err(error) => "Error: \{error}"
|
|
174
|
+
}
|
|
175
|
+
let array = [1, 2, 3, 4, 5]
|
|
176
|
+
let mut i = 0 // mutable bindings (local only, globals are immutable)
|
|
177
|
+
let target = 3
|
|
178
|
+
// loops return values with 'break'
|
|
179
|
+
let found : Int? = while i < array.length() {
|
|
180
|
+
if array[i] == target {
|
|
181
|
+
break Some(i) // Exit with value
|
|
182
|
+
}
|
|
183
|
+
i = i + 1
|
|
184
|
+
} else { // Value when loop completes normally
|
|
185
|
+
None
|
|
186
|
+
}
|
|
187
|
+
assert_eq(found, Some(2)) // Found at index 2
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
///|
|
|
191
|
+
/// global bindings
|
|
192
|
+
pub let my_name : String = "MoonBit"
|
|
193
|
+
|
|
194
|
+
///|
|
|
195
|
+
pub const PI : Double = 3.14159 // constants use UPPER_SNAKE or PascalCase
|
|
196
|
+
|
|
197
|
+
///|
|
|
198
|
+
pub fn maximum(xs : Array[Int]) -> Int raise {
|
|
199
|
+
// Toplevel functions are *mutually recursive* by default
|
|
200
|
+
// `raise` annotation means the function would raise any Error
|
|
201
|
+
// Only add `raise XXError` when you do need track the specific error type
|
|
202
|
+
match xs {
|
|
203
|
+
[] => fail("Empty array") // fail() is built-in for generic errors
|
|
204
|
+
[x] => x
|
|
205
|
+
// pattern match over array, the `.. rest` is a rest pattern
|
|
206
|
+
// it is of type `ArrayView[Int]` which is a slice
|
|
207
|
+
[x, .. rest] => {
|
|
208
|
+
let mut max_val = x // `mut` only allowed in local bindings
|
|
209
|
+
for y in rest {
|
|
210
|
+
if y > max_val {
|
|
211
|
+
max_val = y
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
max_val // return can be omitted if the last expression is the return value
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
///|
|
|
220
|
+
/// pub(all) means it can be both read and created outside the package
|
|
221
|
+
pub(all) struct Point {
|
|
222
|
+
x : Int
|
|
223
|
+
mut y : Int
|
|
224
|
+
} derive(Show, ToJson)
|
|
225
|
+
|
|
226
|
+
///|
|
|
227
|
+
pub enum MyResult[T, E] {
|
|
228
|
+
MyOk(T) // semicolon `;` is optional when we have a newline
|
|
229
|
+
MyErr(E) // Enum variants must start uppercase
|
|
230
|
+
} derive(Show, Eq, ToJson)
|
|
231
|
+
// pub means it can only be pattern matched outside the package
|
|
232
|
+
// but it can not be created outside the package, use `pub(all)` otherwise
|
|
233
|
+
|
|
234
|
+
///|
|
|
235
|
+
/// pub (open) means the trait can be implemented for outside packages
|
|
236
|
+
pub(open) trait Comparable {
|
|
237
|
+
compare(Self, Self) -> Int // `Self` refers to the implementing type
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
///|
|
|
241
|
+
test "inspect test" {
|
|
242
|
+
let result = sum(1, 2)
|
|
243
|
+
inspect(result, content="3")
|
|
244
|
+
// The `content` can be auto-corrected by running `moon test --update`
|
|
245
|
+
let point = Point::{ x: 10, y: 20 }
|
|
246
|
+
// For complex structures, use @json.inspect for better readability:
|
|
247
|
+
@json.inspect(point, content={ "x": 10, "y": 20 })
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Integers, Char
|
|
252
|
+
|
|
253
|
+
MoonBit supports Byte, Int16, Int, UInt16, UInt, Int64, UInt64, etc. When the type is known,
|
|
254
|
+
the literal can be overloaded:
|
|
255
|
+
|
|
256
|
+
```mbt check
|
|
257
|
+
test "integer and char literal overloading disambiguation via type in the current context" {
|
|
258
|
+
let a0 = 1 // a is Int by default
|
|
259
|
+
let (int, uint, uint16, int64, byte) : (Int, UInt, UInt16, Int64, Byte) = (
|
|
260
|
+
1, 1, 1, 1, 1,
|
|
261
|
+
)
|
|
262
|
+
assert_eq(int, uint16.to_int())
|
|
263
|
+
let a1 : Int = 'b' // this also works, a5 will be the unicode value
|
|
264
|
+
let a2 : Char = 'b'
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Bytes
|
|
269
|
+
|
|
270
|
+
Bytes is immutable; Indexing (`b[i]`) returns a `Byte`.
|
|
271
|
+
|
|
272
|
+
```mbt check
|
|
273
|
+
test "bytes literals overloading and indexing" {
|
|
274
|
+
let b0 : Bytes = b"abcd"
|
|
275
|
+
let b1 : Bytes = "abcd" // b" prefix is optional, when we know the type
|
|
276
|
+
let b2 : Bytes = [0xff, 0x00, 0x01] // Array literal overloading
|
|
277
|
+
assert_eq(b0[0], b'a') // indexing returns Byte
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Array
|
|
282
|
+
|
|
283
|
+
MoonBit Array is resizable array, FixedArray is fixed size array.
|
|
284
|
+
|
|
285
|
+
```mbt check
|
|
286
|
+
///|
|
|
287
|
+
test "array literals overloading: disambiguation via type in the current context" {
|
|
288
|
+
let a0 : Array[Int] = [1, 2, 3] // resizable
|
|
289
|
+
let a1 : FixedArray[Int] = [1, 2, 3]
|
|
290
|
+
let a2 : ReadOnlyArray[Int] = [1, 2, 3]
|
|
291
|
+
let a3 : ArrayView[Int] = [1, 2, 3]
|
|
292
|
+
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## String
|
|
297
|
+
|
|
298
|
+
MoonBit's String is immutable utf16 encoded, `s[i]` returns a code unit (UInt16),
|
|
299
|
+
`s.get_char(i)` returns `Option[Char]`.
|
|
300
|
+
Since MoonBit supports char literal overloading, you can write code snippets like this:
|
|
301
|
+
|
|
302
|
+
```mbt check
|
|
303
|
+
#warnings("-unused_value")
|
|
304
|
+
test "string indexing and utf8 encode/decode" {
|
|
305
|
+
let s = "hello world"
|
|
306
|
+
let b0 : UInt16 = s[0]
|
|
307
|
+
assert_true(b0 is ('\n' | 'h' | 'b' | 'a'..='z'))
|
|
308
|
+
// In check mode (expression with explicit type), ('\n' : Int) is valid.
|
|
309
|
+
// Here the compiler knows `s[i]` is Int
|
|
310
|
+
|
|
311
|
+
// Using get_char for Option handling
|
|
312
|
+
let b1 : Char? = s.get_char(0)
|
|
313
|
+
assert_true(b1 is Some('a'..='z'))
|
|
314
|
+
|
|
315
|
+
// ⚠️ Important: Variables won't work with direct indexing
|
|
316
|
+
let eq_char : Char = '='
|
|
317
|
+
// s[0] == eq_char // ❌ Won't compile - eq_char is not a literal, lhs is UInt while rhs is Char
|
|
318
|
+
// Use: s[0] == '=' or s.get_char(0) == Some(eq_char)
|
|
319
|
+
let bytes = @encoding/utf8.encode("中文") // utf8 encode package is in stdlib
|
|
320
|
+
assert_true(bytes is [0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87])
|
|
321
|
+
let s2 : String = @encoding/utf8.decode(bytes) // decode utf8 bytes back to String
|
|
322
|
+
assert_true(s2 is "中文")
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### String Interpolation
|
|
327
|
+
|
|
328
|
+
MoonBit uses `\{}` for string interpolation:
|
|
329
|
+
|
|
330
|
+
```mbt check
|
|
331
|
+
test "string interpolation basics" {
|
|
332
|
+
let point : Point = { x: 10, y: 20 }
|
|
333
|
+
let name : String = "Moon"
|
|
334
|
+
let config = { "cache": 123 }
|
|
335
|
+
let version = 1.0
|
|
336
|
+
let message = "Hello \{name} v\{version}" // "Hello Moon v1.0"
|
|
337
|
+
let desc = "Point at \{point}" // Uses point.to_string()
|
|
338
|
+
// Works with any type implementing Show
|
|
339
|
+
|
|
340
|
+
// ❌ Wrong - quotes inside interpolation not allowed:
|
|
341
|
+
// println(" - Checking if 'cache' section exists: \{config["cache"]}")
|
|
342
|
+
|
|
343
|
+
// ✅ Correct - extract to variable first:
|
|
344
|
+
let has_key = config["cache"] // `"` not allowed in interpolation
|
|
345
|
+
println(" - Checking if 'cache' section exists: \{has_key}")
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
<Important> expressions inside `\{}` can only be basic expressions (no quotes, newlines, or nested interpolations). String literals are not allowed as it makes lexing too difficult.
|
|
350
|
+
</Important>
|
|
351
|
+
|
|
352
|
+
#### Multiple line strings
|
|
353
|
+
|
|
354
|
+
```mbt check
|
|
355
|
+
///|
|
|
356
|
+
test "multi-line string literals" {
|
|
357
|
+
let multi_line_string : String =
|
|
358
|
+
#|Hello
|
|
359
|
+
#|World
|
|
360
|
+
#|
|
|
361
|
+
inspect(
|
|
362
|
+
multi_line_string,
|
|
363
|
+
content=(
|
|
364
|
+
#|Hello
|
|
365
|
+
#|World
|
|
366
|
+
#|
|
|
367
|
+
), // when multiple line string is passed as argument, `()` wrapper is required
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Map
|
|
373
|
+
|
|
374
|
+
A built-in `Map` type that preserves insertion order (like
|
|
375
|
+
JavaScript's Map):
|
|
376
|
+
|
|
377
|
+
```mbt check
|
|
378
|
+
///|
|
|
379
|
+
test "map literals and common operations" {
|
|
380
|
+
// Map literal syntax
|
|
381
|
+
let map : Map[String, Int] = { "a": 1, "b": 2, "c": 3 }
|
|
382
|
+
|
|
383
|
+
// Empty map
|
|
384
|
+
let empty : Map[String, Int] = {}
|
|
385
|
+
|
|
386
|
+
// From array of pairs
|
|
387
|
+
let from_pairs : Map[String, Int] = Map::from_array([("x", 1), ("y", 2)])
|
|
388
|
+
|
|
389
|
+
// Set/update value
|
|
390
|
+
map["new-key"] = 3
|
|
391
|
+
map["a"] = 10 // Updates existing key
|
|
392
|
+
|
|
393
|
+
// Get value - returns Option[T]
|
|
394
|
+
assert_eq(map.get("new-key"), Some(3))
|
|
395
|
+
assert_eq(map.get("missing"), None)
|
|
396
|
+
|
|
397
|
+
// Direct access (panics if key missing)
|
|
398
|
+
let value : Int = map["a"] // value = 10
|
|
399
|
+
|
|
400
|
+
// Iteration preserves insertion order
|
|
401
|
+
for k, v in map {
|
|
402
|
+
println("\{k}: \{v}") // Prints: a: 10, b: 2, c: 3, new-key: 3
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Other common operations
|
|
406
|
+
map.remove("b")
|
|
407
|
+
assert_eq(map.contains("b"), false)
|
|
408
|
+
assert_eq(map.length(), 3)
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## View Types
|
|
413
|
+
|
|
414
|
+
**Key Concept**: View types (`StringView`, `BytesView`, `ArrayView[T]`) are zero-copy, non-owning read-only slices created with the `[:]` syntax. They don't allocate memory and are ideal for passing sub-sequences without copying data.
|
|
415
|
+
|
|
416
|
+
- `String` → `StringView` via `s[:]` or `s[start:end]`
|
|
417
|
+
- `Bytes` → `BytesView` via `b[:]` or `b[start:end]`
|
|
418
|
+
- `Array[T]` → `ArrayView[T]` via `a[:]` or `a[start:end]`
|
|
419
|
+
|
|
420
|
+
**Important**: StringView slice is slightly different due to unicode safety:
|
|
421
|
+
`s[a:b]` may raise an error at surrogate boundaries (UTF-16 encoding edge case). You have two options:
|
|
422
|
+
|
|
423
|
+
- Use `try! s[a:b]` if you're certain the boundaries are valid (crashes on invalid boundaries)
|
|
424
|
+
- Let the error propagate to the caller for proper handling
|
|
425
|
+
|
|
426
|
+
**When to use views**:
|
|
427
|
+
|
|
428
|
+
- Pattern matching with rest patterns (`[first, .. rest]`)
|
|
429
|
+
- Passing slices to functions without allocation overhead
|
|
430
|
+
- Avoiding unnecessary copies of large sequences
|
|
431
|
+
|
|
432
|
+
Convert back with `.to_string()`, `.to_bytes()`, or `.to_array()` when you need ownership.
|
|
433
|
+
|
|
434
|
+
## Complex Types
|
|
435
|
+
|
|
436
|
+
```mbt check
|
|
437
|
+
///|
|
|
438
|
+
type UserId = Int // Int is aliased to UserId - like symlink
|
|
439
|
+
|
|
440
|
+
///|
|
|
441
|
+
/// Tuple-struct for callback
|
|
442
|
+
struct Handler((String) -> Unit) // A newtype wrapper
|
|
443
|
+
|
|
444
|
+
///|
|
|
445
|
+
/// Tuple-struct syntax for single-field newtypes
|
|
446
|
+
struct Meters(Int) // Tuple-struct syntax
|
|
447
|
+
|
|
448
|
+
///|
|
|
449
|
+
let distance : Meters = Meters(100)
|
|
450
|
+
|
|
451
|
+
///|
|
|
452
|
+
let raw : Int = distance.0 // Access first field with .0
|
|
453
|
+
|
|
454
|
+
///|
|
|
455
|
+
struct Addr {
|
|
456
|
+
host : String
|
|
457
|
+
port : Int
|
|
458
|
+
} derive(Show, Eq, ToJson, FromJson)
|
|
459
|
+
|
|
460
|
+
///|
|
|
461
|
+
/// Structural types with literal syntax
|
|
462
|
+
let config : Addr = {
|
|
463
|
+
// `Type::` can be omitted if the type is already known
|
|
464
|
+
// otherwise `Type::{...}`
|
|
465
|
+
host: "localhost",
|
|
466
|
+
port: 8080,
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
///|
|
|
470
|
+
/// Recursive enum for trees
|
|
471
|
+
enum Tree[T] {
|
|
472
|
+
Leaf(T)
|
|
473
|
+
Node(left~ : Tree[T], T, right~ : Tree[T]) // enum can use labels
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Pattern match on enum variants
|
|
477
|
+
|
|
478
|
+
///|
|
|
479
|
+
fn sum_tree(tree : Tree[Int]) -> Int {
|
|
480
|
+
match tree {
|
|
481
|
+
Leaf(x) => x
|
|
482
|
+
Node(left~, x, right~) => sum_tree(left) + x + sum_tree(right)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Common Derivable Traits
|
|
488
|
+
|
|
489
|
+
Most types can automatically derive standard traits using the `derive(...)` syntax:
|
|
490
|
+
|
|
491
|
+
- **`Show`** - Enables `to_string()` and string interpolation with `\{value}`
|
|
492
|
+
- **`Eq`** - Enables `==` and `!=` equality operators
|
|
493
|
+
- **`Compare`** - Enables `<`, `>`, `<=`, `>=` comparison operators
|
|
494
|
+
- **`ToJson`** - Enables `@json.inspect()` for readable test output
|
|
495
|
+
- **`Hash`** - Enables use as Map keys
|
|
496
|
+
|
|
497
|
+
```mbt check
|
|
498
|
+
///|
|
|
499
|
+
struct Coordinate {
|
|
500
|
+
x : Int
|
|
501
|
+
y : Int
|
|
502
|
+
} derive(Show, Eq, ToJson)
|
|
503
|
+
|
|
504
|
+
///|
|
|
505
|
+
enum Status {
|
|
506
|
+
Active
|
|
507
|
+
Inactive
|
|
508
|
+
} derive(Show, Eq, Compare)
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Best practice**: Always derive `Show` and `Eq` for data types. Add `ToJson` if you plan to test them with `@json.inspect()`.
|
|
512
|
+
|
|
513
|
+
## Reference Semantics by Default
|
|
514
|
+
|
|
515
|
+
MoonBit passes most types by reference semantically (the optimizer may copy
|
|
516
|
+
immutables):
|
|
517
|
+
|
|
518
|
+
```mbt check
|
|
519
|
+
///|
|
|
520
|
+
/// Structs with 'mut' fields are always passed by reference
|
|
521
|
+
struct Counter {
|
|
522
|
+
mut value : Int
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
///|
|
|
526
|
+
fn increment(c : Counter) -> Unit {
|
|
527
|
+
c.value += 1 // Modifies the original
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
///|
|
|
531
|
+
/// Arrays and Maps are mutable references
|
|
532
|
+
fn modify_array(arr : Array[Int]) -> Unit {
|
|
533
|
+
arr[0] = 999 // Modifies original array
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
///|
|
|
537
|
+
/// Use Ref[T] for explicit mutable references to primitives
|
|
538
|
+
fn swap_values(a : Ref[Int], b : Ref[Int]) -> Unit {
|
|
539
|
+
let temp = a.val
|
|
540
|
+
a.val = b.val
|
|
541
|
+
b.val = temp
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
///|
|
|
545
|
+
test "ref swap" {
|
|
546
|
+
let x : Ref[Int] = Ref::new(10)
|
|
547
|
+
let y : Ref[Int] = Ref::new(20)
|
|
548
|
+
swap_values(x, y) // x.val is now 20, y.val is now 10
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Pattern Matching
|
|
553
|
+
|
|
554
|
+
MoonBit's pattern matching is comprehensive and exhaustive:
|
|
555
|
+
|
|
556
|
+
```mbt check
|
|
557
|
+
///|
|
|
558
|
+
/// Destructure arrays with rest patterns
|
|
559
|
+
fn process_array(arr : Array[Int]) -> String {
|
|
560
|
+
match arr {
|
|
561
|
+
[] => "empty"
|
|
562
|
+
[single] => "one: \{single}"
|
|
563
|
+
[first, .. _middle, last] => "first: \{first}, last: \{last}"
|
|
564
|
+
// middle is of type ArrayView[Int]
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
///|
|
|
569
|
+
fn analyze_point(point : Point) -> String {
|
|
570
|
+
match point {
|
|
571
|
+
{ x: 0, y: 0 } => "origin"
|
|
572
|
+
{ x, y } if x == y => "on diagonal"
|
|
573
|
+
{ x, .. } if x < 0 => "left side"
|
|
574
|
+
_ => "other"
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
///|
|
|
579
|
+
/// StringView pattern matching for parsing
|
|
580
|
+
fn is_palindrome(s : StringView) -> Bool {
|
|
581
|
+
loop s {
|
|
582
|
+
[] | [_] => true
|
|
583
|
+
[a, .. rest, b] if a == b => continue rest
|
|
584
|
+
// a is of type Char, rest is of type StringView
|
|
585
|
+
_ => false
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## Functional `loop` control flow
|
|
591
|
+
|
|
592
|
+
The `loop` construct is unique to MoonBit:
|
|
593
|
+
|
|
594
|
+
```mbt check
|
|
595
|
+
///|
|
|
596
|
+
/// Functional loop with pattern matching on loop variables
|
|
597
|
+
/// @list.List is from the standard library
|
|
598
|
+
fn sum_list(list : @list.List[Int]) -> Int {
|
|
599
|
+
loop (list, 0) {
|
|
600
|
+
(Empty, acc) => acc // Base case returns accumulator
|
|
601
|
+
(More(x, tail=rest), acc) => continue (rest, x + acc) // Recurse with new values
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
///|
|
|
606
|
+
/// Multiple loop variables with complex control flow
|
|
607
|
+
fn find_pair(arr : Array[Int], target : Int) -> (Int, Int)? {
|
|
608
|
+
loop (0, arr.length() - 1) {
|
|
609
|
+
(i, j) if i >= j => None
|
|
610
|
+
(i, j) => {
|
|
611
|
+
let sum = arr[i] + arr[j]
|
|
612
|
+
if sum == target {
|
|
613
|
+
Some((i, j)) // Found pair
|
|
614
|
+
} else if sum < target {
|
|
615
|
+
continue (i + 1, j) // Move left pointer
|
|
616
|
+
} else {
|
|
617
|
+
continue (i, j - 1) // Move right pointer
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**Note**: You must provide a payload to `loop`. If you want an infinite loop, use `while true { ... }` instead. The syntax `loop { ... }` without arguments is invalid.
|
|
625
|
+
|
|
626
|
+
## Functional `for` control flow
|
|
627
|
+
|
|
628
|
+
`for` loops have unique MoonBit features:
|
|
629
|
+
|
|
630
|
+
```mbt check
|
|
631
|
+
///|
|
|
632
|
+
test "functional for loop control flow" {
|
|
633
|
+
// For loop with multiple loop variables,
|
|
634
|
+
// i and j are loop state
|
|
635
|
+
let sum_result : Int = for i = 0, sum = 0 {
|
|
636
|
+
if i <= 10 {
|
|
637
|
+
continue i + 1, sum + i
|
|
638
|
+
// update new loop state in a functional way
|
|
639
|
+
} else { // Continue with new values
|
|
640
|
+
break sum // Final value when loop completes normally
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
inspect(sum_result, content="55")
|
|
644
|
+
|
|
645
|
+
// special form with condition and state update in the `for` header
|
|
646
|
+
let sum_result2 : Int = for i = 0, sum = 0; i <= 10; i = i + 1, sum = sum + i {
|
|
647
|
+
|
|
648
|
+
} else {
|
|
649
|
+
sum
|
|
650
|
+
}
|
|
651
|
+
inspect(sum_result2, content="55")
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## Label and Optional Parameters
|
|
656
|
+
|
|
657
|
+
Good example: Use labeled and optional parameters
|
|
658
|
+
|
|
659
|
+
```mbt
|
|
660
|
+
fn g(
|
|
661
|
+
positional : Int,
|
|
662
|
+
required~ : Int,
|
|
663
|
+
optional? : Int,
|
|
664
|
+
optional_with_default? : Int = 42,
|
|
665
|
+
) -> String {
|
|
666
|
+
// make sure you understand the types of the arguments really is:
|
|
667
|
+
let _ : Int = positional
|
|
668
|
+
let _ : Int = required
|
|
669
|
+
// let _ : Option[Int] = optional
|
|
670
|
+
let _ : Int = optional_with_default
|
|
671
|
+
"\{positional},\{required},\{optional},\{optional_with_default}"
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
test {
|
|
675
|
+
inspect(g(1, required=2), content="1,2,None,42")
|
|
676
|
+
inspect(g(1, required=2, optional=3), content="1,2,Some(3),42")
|
|
677
|
+
inspect(g(1, required=4, optional_with_default=100), content="1,4,None,100")
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
Misuse: `arg : Type?` is not an optional parameter
|
|
682
|
+
|
|
683
|
+
```mbt
|
|
684
|
+
fn with_config(a : Int?, b : Int?, c : Int) -> String {
|
|
685
|
+
// T? is syntactic sugar for Option[T]
|
|
686
|
+
"\{a},\{b},\{c}"
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
test {
|
|
690
|
+
inspect(with_config(None, None, 1), content="None,None,1")
|
|
691
|
+
inspect(with_config(Some(5), Some(5), 1), content="Some(5),Some(5),1")
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Anti pattern: `arg? : Type?`
|
|
696
|
+
|
|
697
|
+
```mbt
|
|
698
|
+
// How to fix: declare `(a? : Int, b? : Int = 1)` directly
|
|
699
|
+
fn f(a? : Int?, b? : Int? = Some(1)) -> Unit {...}
|
|
700
|
+
test {
|
|
701
|
+
// How to fix: call `f(b=2)` directly
|
|
702
|
+
f(a=None, b=Some(2))
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Bad example: `arg : APIOptions`
|
|
707
|
+
|
|
708
|
+
```mbt
|
|
709
|
+
// Do not use struct to group options.
|
|
710
|
+
struct APIOptions {
|
|
711
|
+
a : Int?
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
fn not_idiomatic(opts : APIOptions, arg : Int) -> Unit {
|
|
715
|
+
...
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
test {
|
|
719
|
+
// Hard to use in call site
|
|
720
|
+
not_idiomatic({ a: Some(5) }, 10)
|
|
721
|
+
not_idiomatic({ a: None }, 10)
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Checked Errors
|
|
726
|
+
|
|
727
|
+
MoonBit uses **checked** error-throwing functions, not unchecked exceptions,
|
|
728
|
+
it is recommended to use `raise` for functions and use `Result` in testing.
|
|
729
|
+
|
|
730
|
+
```mbt check
|
|
731
|
+
///|
|
|
732
|
+
/// Declare error types with 'suberror'
|
|
733
|
+
suberror ValueError String
|
|
734
|
+
|
|
735
|
+
///|
|
|
736
|
+
struct Position(Int, Int) derive(ToJson, Show, Eq)
|
|
737
|
+
|
|
738
|
+
///|
|
|
739
|
+
pub(all) suberror ParseError {
|
|
740
|
+
InvalidChar(Position, Char)
|
|
741
|
+
InvalidEof
|
|
742
|
+
InvalidNumber(Position, String)
|
|
743
|
+
InvalidIdentEscape(Position)
|
|
744
|
+
} derive(Eq, ToJson, Show)
|
|
745
|
+
|
|
746
|
+
///|
|
|
747
|
+
/// Functions declare what they can throw
|
|
748
|
+
fn parse_int(s : String) -> Int raise ParseError {
|
|
749
|
+
// 'raise' throws an error
|
|
750
|
+
if s.is_empty() {
|
|
751
|
+
raise ParseError::InvalidEof
|
|
752
|
+
}
|
|
753
|
+
... // parsing logic
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
///|
|
|
757
|
+
fn div(x : Int, y : Int) -> Int raise {
|
|
758
|
+
if y == 0 {
|
|
759
|
+
raise Failure("Division by zero")
|
|
760
|
+
}
|
|
761
|
+
x / y
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
///|
|
|
765
|
+
test "inspect raise function" {
|
|
766
|
+
inspect(
|
|
767
|
+
try? div(1, 0),
|
|
768
|
+
content=(
|
|
769
|
+
#|Err(Failure("Division by zero"))
|
|
770
|
+
),
|
|
771
|
+
) // Result[Int, MyError]
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Three ways to handle errors:
|
|
775
|
+
|
|
776
|
+
///|
|
|
777
|
+
/// Propagate automatically
|
|
778
|
+
fn use_parse() -> Int raise ParseError {
|
|
779
|
+
let x = parse_int("123")
|
|
780
|
+
// Error *auto* propagates by default.
|
|
781
|
+
// *unlike* Swift, you don't need mark `try` for functions that can raise errors,
|
|
782
|
+
// compiler infers it automatically. This makes error-handling code cleaner
|
|
783
|
+
// while still being type-safe and explicit about what errors can occur.
|
|
784
|
+
x * 2
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
///|
|
|
788
|
+
/// Mark `raise` for all possible errors, don't care what error it is
|
|
789
|
+
/// If you are doing a quick prototype, just mark it as raise is good enough.
|
|
790
|
+
fn use_parse2() -> Int raise {
|
|
791
|
+
let x = parse_int("123")
|
|
792
|
+
x * 2
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
///|
|
|
796
|
+
/// Convert to Result with try?
|
|
797
|
+
fn safe_parse(s : String) -> Result[Int, ParseError] {
|
|
798
|
+
let val1 : Result[_] = try? parse_int(s) // Returns Result[Int, ParseError]
|
|
799
|
+
// try! is rarely used - it panics on error, similar to unwrap() in Rust
|
|
800
|
+
// let val2 : Int = try! parse_int(s) // Returns Int otherwise crash
|
|
801
|
+
|
|
802
|
+
// Alternative explicit handling:
|
|
803
|
+
let val3 = try parse_int(s) catch {
|
|
804
|
+
err => Err(err)
|
|
805
|
+
} noraise { // noraise block is optional - handles the success case
|
|
806
|
+
v => Ok(v)
|
|
807
|
+
}
|
|
808
|
+
...
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
///|
|
|
812
|
+
/// 3. Handle with try-catch
|
|
813
|
+
fn handle_parse(s : String) -> Int {
|
|
814
|
+
parse_int(s) catch {
|
|
815
|
+
ParseError::InvalidEof => {
|
|
816
|
+
println("Parse failed: InvalidEof")
|
|
817
|
+
-1 // Default value
|
|
818
|
+
}
|
|
819
|
+
_ => 2
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
# Methods and Traits
|
|
825
|
+
|
|
826
|
+
Methods use `Type::method_name` syntax, traits require explicit implementation:
|
|
827
|
+
|
|
828
|
+
```mbt check
|
|
829
|
+
///|
|
|
830
|
+
struct Rectangle {
|
|
831
|
+
width : Double
|
|
832
|
+
height : Double
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
///|
|
|
836
|
+
// Methods are prefixed with Type::
|
|
837
|
+
fn Rectangle::area(self : Rectangle) -> Double {
|
|
838
|
+
self.width * self.height
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
///|
|
|
842
|
+
/// Static methods don't need self
|
|
843
|
+
fn Rectangle::new(w : Double, h : Double) -> Rectangle {
|
|
844
|
+
{ width: w, height: h }
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
///|
|
|
848
|
+
/// Show trait now uses output(self, logger) for custom formatting
|
|
849
|
+
/// to_string() is automatically derived from this
|
|
850
|
+
pub impl Show for Rectangle with output(self, logger) {
|
|
851
|
+
logger.write_string("Rectangle(\{self.width}x\{self.height})")
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
///|
|
|
855
|
+
/// Traits can have non-object-safe methods
|
|
856
|
+
trait Named {
|
|
857
|
+
name() -> String // No 'self' parameter - not object-safe
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
///|
|
|
861
|
+
/// Trait bounds in generics
|
|
862
|
+
fn[T : Show + Named] describe(value : T) -> String {
|
|
863
|
+
"\{T::name()}: \{value.to_string()}"
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
///|
|
|
867
|
+
/// Trait implementation
|
|
868
|
+
impl Hash for Rectangle with hash_combine(self, hasher) {
|
|
869
|
+
hasher..combine(self.width)..combine(self.height)
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
## Operator Overloading
|
|
874
|
+
|
|
875
|
+
MoonBit supports operator overloading through traits:
|
|
876
|
+
|
|
877
|
+
```mbt check
|
|
878
|
+
///|
|
|
879
|
+
struct Vector(Int, Int)
|
|
880
|
+
|
|
881
|
+
///|
|
|
882
|
+
/// Implement arithmetic operators
|
|
883
|
+
pub impl Add for Vector with add(self, other) {
|
|
884
|
+
Vector(self.0 + other.0, self.1 + other.1)
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
///|
|
|
888
|
+
pub impl Mul for Vector with mul(self, other) {
|
|
889
|
+
Vector(self.0 * other.0, self.1 * other.1)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
///|
|
|
893
|
+
struct Person {
|
|
894
|
+
age : Int
|
|
895
|
+
} derive(Eq)
|
|
896
|
+
|
|
897
|
+
///|
|
|
898
|
+
/// Comparison operators
|
|
899
|
+
pub impl Compare for Person with compare(self, other) {
|
|
900
|
+
self.age.compare(other.age)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
///|
|
|
904
|
+
test "overloading" {
|
|
905
|
+
let v1 : Vector = Vector(1, 2)
|
|
906
|
+
let v2 : Vector = Vector(3, 4)
|
|
907
|
+
let _v3 : Vector = v1 + v2
|
|
908
|
+
|
|
909
|
+
}
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
## Access Control Modifiers
|
|
913
|
+
|
|
914
|
+
MoonBit has fine-grained visibility control:
|
|
915
|
+
|
|
916
|
+
```mbt check
|
|
917
|
+
///|
|
|
918
|
+
/// `fn` defaults to Private - only visible in current package
|
|
919
|
+
fn internal_helper() -> Unit {
|
|
920
|
+
...
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
///|
|
|
924
|
+
pub fn get_value() -> Int {
|
|
925
|
+
...
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
///|
|
|
929
|
+
// Struct (default) - type visible, implementation hidden
|
|
930
|
+
struct DataStructure {}
|
|
931
|
+
|
|
932
|
+
///|
|
|
933
|
+
/// `pub struct` defaults to readonly - can read, pattern match, but not create
|
|
934
|
+
pub struct Config {}
|
|
935
|
+
|
|
936
|
+
///|
|
|
937
|
+
/// Public all - full access
|
|
938
|
+
pub(all) struct Config2 {}
|
|
939
|
+
|
|
940
|
+
///|
|
|
941
|
+
/// Abstract trait (default) - cannot be implemented by
|
|
942
|
+
/// types outside this package
|
|
943
|
+
pub trait MyTrait {}
|
|
944
|
+
|
|
945
|
+
///|
|
|
946
|
+
/// Open for extension
|
|
947
|
+
pub(open) trait Extendable {}
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
# Best Practices and Reference
|
|
951
|
+
|
|
952
|
+
## Common Pitfalls to Avoid
|
|
953
|
+
|
|
954
|
+
1. **Don't use uppercase for variables/functions** - compilation error
|
|
955
|
+
2. **Don't forget `mut` for mutable fields** - immutable by default
|
|
956
|
+
3. **Don't assume value semantics** - most types pass by reference
|
|
957
|
+
4. **Don't ignore error handling** - errors must be explicitly handled
|
|
958
|
+
5. **Don't use `return` unnecessarily** - last expression is the return value
|
|
959
|
+
6. **Don't create methods without Type:: prefix** - methods need explicit type prefix
|
|
960
|
+
7. Don't forget to handle array bounds - use get() for safe access
|
|
961
|
+
8. Don't mix up String indexing (returns Int). Use `for char in s {...}` for char iteration
|
|
962
|
+
9. Don't forget @package prefix when calling functions from other packages
|
|
963
|
+
10. Don't use ++ or -- (not supported), use `i = i + 1` or `i += 1`
|
|
964
|
+
11. **Don't add explicit `try` for error-raising functions** - errors propagate automatically (unlike Swift)
|
|
965
|
+
12. **Legacy syntax**: Older code may use `function_name!(...)` or `function_name(...)?` - these are deprecated; use normal calls and `try?` for Result conversion
|
|
966
|
+
|
|
967
|
+
# MoonBit Build System - Essential Guide
|
|
968
|
+
|
|
969
|
+
## Idiomatic Project Structure
|
|
970
|
+
|
|
971
|
+
MoonBit projects use `moon.mod.json` (module descriptor) and `moon.pkg.json`
|
|
972
|
+
(package descriptor):
|
|
973
|
+
|
|
974
|
+
```
|
|
975
|
+
my_module
|
|
976
|
+
├── Agents.md # Guide to Agents
|
|
977
|
+
├── README.mbt.md # Markdown with tested code blocks (`test "..." { ... }`)
|
|
978
|
+
├── README.md -> README.mbt.md
|
|
979
|
+
├── cmd # Command line directory
|
|
980
|
+
│ └── main
|
|
981
|
+
│ ├── main.mbt
|
|
982
|
+
│ └── moon.pkg.json # executable package with {"is_main": true}
|
|
983
|
+
├── liba/ # Library packages
|
|
984
|
+
│ └── moon.pkg.json # Referenced by other packages as `@username/my_module/liba`
|
|
985
|
+
│ └── libb/ # Library packages
|
|
986
|
+
│ └── moon.pkg.json # Referenced by other packages as `@username/my_module/liba/libb`
|
|
987
|
+
├── moon.mod.json # Module metadata, source field(optional) specifies the source directory of the module
|
|
988
|
+
├── moon.pkg.json # Package metadata (each directory is a package like Golang)
|
|
989
|
+
├── user_pkg.mbt # Root packages, referenced by other packages as `@username/my_module`
|
|
990
|
+
├── user_pkg_wbtest.mbt # White-box tests (only needed for testing internal private members, similar to Golang's package mypackage)
|
|
991
|
+
└── user_pkg_test.mbt # Black-box tests
|
|
992
|
+
└── ... # More package files, symbols visible to current package (like Golang)
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
## Essential Commands
|
|
996
|
+
|
|
997
|
+
- `moon new my_project` - Create new project
|
|
998
|
+
- `moon run cmd/main` - Run main package
|
|
999
|
+
- `moon build` - Build project
|
|
1000
|
+
- `moon check` - Type check without building, use it regularly
|
|
1001
|
+
- `moon check --target all` - Type check for all backends
|
|
1002
|
+
- `moon add package` - Add dependency
|
|
1003
|
+
- `moon remove package` - Remove dependency
|
|
1004
|
+
- `moon fmt` - Format code
|
|
1005
|
+
|
|
1006
|
+
### Test Commands
|
|
1007
|
+
|
|
1008
|
+
- `moon test` - Run all tests
|
|
1009
|
+
- `moon test --update`
|
|
1010
|
+
- `moon test -v` - Verbose output with test names
|
|
1011
|
+
- `moon test dirname` - Test specific directory
|
|
1012
|
+
- `moon test filename` - Test specific file in a directory
|
|
1013
|
+
- `moon coverage analyze` - Analyze coverage
|
|
1014
|
+
|
|
1015
|
+
## Package Management
|
|
1016
|
+
|
|
1017
|
+
### Adding Dependencies
|
|
1018
|
+
|
|
1019
|
+
```bash
|
|
1020
|
+
moon add moonbitlang/x # Add latest version
|
|
1021
|
+
moon add moonbitlang/x@0.4.6 # Add specific version
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### Updating Dependencies
|
|
1025
|
+
|
|
1026
|
+
```bash
|
|
1027
|
+
moon update # Update package index
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
## Key Configuration
|
|
1031
|
+
|
|
1032
|
+
### Module (`moon.mod.json`)
|
|
1033
|
+
|
|
1034
|
+
```json
|
|
1035
|
+
{
|
|
1036
|
+
"name": "username/hello", // Required format for published modules
|
|
1037
|
+
"version": "0.1.0",
|
|
1038
|
+
"source": ".", // Source directory(optional, default: ".")
|
|
1039
|
+
"repository": "", // Git repository URL
|
|
1040
|
+
"keywords": [], // Search keywords
|
|
1041
|
+
"description": "...", // Module description
|
|
1042
|
+
"deps": {
|
|
1043
|
+
// Dependencies from mooncakes.io, using`moon add` to add dependencies
|
|
1044
|
+
"moonbitlang/x": "0.4.6"
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
### Package (`moon.pkg.json`)
|
|
1050
|
+
|
|
1051
|
+
```json
|
|
1052
|
+
{
|
|
1053
|
+
"is_main": true, // Creates executable when true
|
|
1054
|
+
"import": [ // Package dependencies
|
|
1055
|
+
"username/hello/liba", // Simple import, use @liba.foo() to call functions
|
|
1056
|
+
{
|
|
1057
|
+
"path": "moonbitlang/x/encoding",
|
|
1058
|
+
"alias": "libb" // Custom alias, use @libb.encode() to call functions
|
|
1059
|
+
}
|
|
1060
|
+
],
|
|
1061
|
+
"test-import": [...], // Imports for black-box tests, similar to import
|
|
1062
|
+
"wbtest-import": [...] // Imports for white-box tests, similar to import (rarely used)
|
|
1063
|
+
}
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
Packages per directory, packages without `moon.pkg.json` are not recognized.
|
|
1067
|
+
|
|
1068
|
+
## Package Importing (used in moon.pkg.json)
|
|
1069
|
+
|
|
1070
|
+
- **Import format**: `"module_name/package_path"`
|
|
1071
|
+
- **Usage**: `@alias.function()` to call imported functions
|
|
1072
|
+
- **Default alias**: Last part of path (e.g., `liba` for `username/hello/liba`)
|
|
1073
|
+
- **Package reference**: Use `@packagename` in test files to reference the
|
|
1074
|
+
tested package
|
|
1075
|
+
|
|
1076
|
+
**Package Alias Rules**:
|
|
1077
|
+
|
|
1078
|
+
- Import `"username/hello/liba"` → use `@liba.function()` (default alias is last path segment)
|
|
1079
|
+
- Import with custom alias `{"path": "moonbitlang/x/encoding", "alias": "enc"}` → use `@enc.function()`
|
|
1080
|
+
- In `_test.mbt` or `_wbtest.mbt` files, the package being tested is auto-imported
|
|
1081
|
+
|
|
1082
|
+
Example:
|
|
1083
|
+
|
|
1084
|
+
```mbt
|
|
1085
|
+
///|
|
|
1086
|
+
/// In main.mbt after importing "username/hello/liba" in `moon.pkg.json`
|
|
1087
|
+
fn main {
|
|
1088
|
+
println(@liba.hello()) // Calls hello() from liba package
|
|
1089
|
+
}
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
## Using Standard Library (moonbitlang/core)
|
|
1093
|
+
|
|
1094
|
+
**MoonBit standard library (moonbitlang/core) packages are automatically imported** - DO NOT add them to dependencies:
|
|
1095
|
+
|
|
1096
|
+
- ❌ **DO NOT** use `moon add` to add standard library packages like `moonbitlang/core/strconv`
|
|
1097
|
+
- ❌ **DO NOT** add standard library packages to `"deps"` field of `moon.mod.json`
|
|
1098
|
+
- ❌ **DO NOT** add standard library packages to `"import"` field of `moon.pkg.json`
|
|
1099
|
+
- ✅ **DO** use them directly: `@strconv.parse_int()`, `@list.List`, `@array.fold()`, etc.
|
|
1100
|
+
|
|
1101
|
+
If you get an error like "cannot import `moonbitlang/core/strconv`", remove it from imports - it's automatically available.
|
|
1102
|
+
|
|
1103
|
+
## Creating Packages
|
|
1104
|
+
|
|
1105
|
+
To add a new package `fib` under `.`:
|
|
1106
|
+
|
|
1107
|
+
1. Create directory: `./fib/`
|
|
1108
|
+
2. Add `./fib/moon.pkg.json`: `{}` -- Minimal valid moon.pkg.json
|
|
1109
|
+
3. Add `.mbt` files with your code
|
|
1110
|
+
4. Import in dependent packages:
|
|
1111
|
+
|
|
1112
|
+
```json
|
|
1113
|
+
{
|
|
1114
|
+
"import": [
|
|
1115
|
+
"username/hello/fib",
|
|
1116
|
+
...
|
|
1117
|
+
]
|
|
1118
|
+
}
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
## Conditional Compilation
|
|
1122
|
+
|
|
1123
|
+
Target specific backends/modes in `moon.pkg.json`:
|
|
1124
|
+
|
|
1125
|
+
```json
|
|
1126
|
+
{
|
|
1127
|
+
"targets": {
|
|
1128
|
+
"wasm_only.mbt": ["wasm"],
|
|
1129
|
+
"js_only.mbt": ["js"],
|
|
1130
|
+
"debug_only.mbt": ["debug"],
|
|
1131
|
+
"wasm_or_js.mbt": ["wasm", "js"], // for wasm or js backend
|
|
1132
|
+
"not_js.mbt": ["not", "js"], // for nonjs backend
|
|
1133
|
+
"complex.mbt": ["or", ["and", "wasm", "release"], ["and", "js", "debug"]] // more complex conditions
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
**Available conditions:**
|
|
1139
|
+
|
|
1140
|
+
- **Backends**: `"wasm"`, `"wasm-gc"`, `"js"`, `"native"`
|
|
1141
|
+
- **Build modes**: `"debug"`, `"release"`
|
|
1142
|
+
- **Logical operators**: `"and"`, `"or"`, `"not"`
|
|
1143
|
+
|
|
1144
|
+
## Link Configuration
|
|
1145
|
+
|
|
1146
|
+
### Basic Linking
|
|
1147
|
+
|
|
1148
|
+
```json
|
|
1149
|
+
{
|
|
1150
|
+
"link": true, // Enable linking for this package
|
|
1151
|
+
// OR for advanced cases:
|
|
1152
|
+
"link": {
|
|
1153
|
+
"wasm": {
|
|
1154
|
+
"exports": ["hello", "foo:bar"], // Export functions
|
|
1155
|
+
"heap-start-address": 1024, // Memory layout
|
|
1156
|
+
"import-memory": {
|
|
1157
|
+
// Import external memory
|
|
1158
|
+
"module": "env",
|
|
1159
|
+
"name": "memory"
|
|
1160
|
+
},
|
|
1161
|
+
"export-memory-name": "memory" // Export memory with name
|
|
1162
|
+
},
|
|
1163
|
+
"wasm-gc": {
|
|
1164
|
+
"exports": ["hello"],
|
|
1165
|
+
"use-js-builtin-string": true, // JS String Builtin support
|
|
1166
|
+
"imported-string-constants": "_" // String namespace
|
|
1167
|
+
},
|
|
1168
|
+
"js": {
|
|
1169
|
+
"exports": ["hello"],
|
|
1170
|
+
"format": "esm" // "esm", "cjs", or "iife"
|
|
1171
|
+
},
|
|
1172
|
+
"native": {
|
|
1173
|
+
"cc": "gcc", // C compiler
|
|
1174
|
+
"cc-flags": "-O2 -DMOONBIT", // Compile flags
|
|
1175
|
+
"cc-link-flags": "-s" // Link flags
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
## Warning Control
|
|
1182
|
+
|
|
1183
|
+
Disable specific warnings in `moon.mod.json` or `moon.pkg.json`:
|
|
1184
|
+
|
|
1185
|
+
```json
|
|
1186
|
+
{
|
|
1187
|
+
"warn-list": "-2-29" // Disable unused variable (2) & unused package (29)
|
|
1188
|
+
}
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
**Common warning numbers:**
|
|
1192
|
+
|
|
1193
|
+
- `1` - Unused function
|
|
1194
|
+
- `2` - Unused variable
|
|
1195
|
+
- `11` - Partial pattern matching
|
|
1196
|
+
- `12` - Unreachable code
|
|
1197
|
+
- `29` - Unused package
|
|
1198
|
+
|
|
1199
|
+
Use `moonc build-package -warn-help` to see all available warnings.
|
|
1200
|
+
|
|
1201
|
+
## Pre-build Commands
|
|
1202
|
+
|
|
1203
|
+
Embed external files as MoonBit code:
|
|
1204
|
+
|
|
1205
|
+
```json
|
|
1206
|
+
{
|
|
1207
|
+
"pre-build": [
|
|
1208
|
+
{
|
|
1209
|
+
"input": "data.txt",
|
|
1210
|
+
"output": "embedded.mbt",
|
|
1211
|
+
"command": ":embed -i $input -o $output --name data --text"
|
|
1212
|
+
},
|
|
1213
|
+
... // more embed commands
|
|
1214
|
+
]
|
|
1215
|
+
}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
Generated code example:
|
|
1219
|
+
|
|
1220
|
+
```mbt check
|
|
1221
|
+
///|
|
|
1222
|
+
let data : String =
|
|
1223
|
+
#|hello,
|
|
1224
|
+
#|world
|
|
1225
|
+
#|
|
|
1226
|
+
```
|
|
1227
|
+
|
|
1228
|
+
# Documentation
|
|
1229
|
+
|
|
1230
|
+
Write documentation using `///` comments (started with `///|` to delimit the
|
|
1231
|
+
block code)
|
|
1232
|
+
|
|
1233
|
+
````mbt check
|
|
1234
|
+
///|
|
|
1235
|
+
/// Get the largest element of a non-empty `Array`.
|
|
1236
|
+
///
|
|
1237
|
+
/// # Example
|
|
1238
|
+
/// ```mbt check
|
|
1239
|
+
/// test {
|
|
1240
|
+
/// inspect(sum_array([1, 2, 3, 4, 5, 6]), content="21")
|
|
1241
|
+
/// }
|
|
1242
|
+
/// ```
|
|
1243
|
+
///
|
|
1244
|
+
/// # Panics
|
|
1245
|
+
/// Panics if the `xs` is empty.
|
|
1246
|
+
pub fn sum_array(xs : Array[Int]) -> Int {
|
|
1247
|
+
xs.fold(init=0, (a, b) => a + b)
|
|
1248
|
+
}
|
|
1249
|
+
````
|
|
1250
|
+
|
|
1251
|
+
The MoonBit code in docstring will be type checked and tested automatically.
|
|
1252
|
+
(using `moon test --update`)
|
|
1253
|
+
|
|
1254
|
+
# Development Workflow
|
|
1255
|
+
|
|
1256
|
+
## MoonBit Tips
|
|
1257
|
+
|
|
1258
|
+
- MoonBit code is organized in files/block style.
|
|
1259
|
+
A package is composed of a list of files, their order does not matter,
|
|
1260
|
+
keep them separate so that it is easy to focus on critical parts.
|
|
1261
|
+
|
|
1262
|
+
Each block is separated by `///|`, the order of each block is irrelevant too. You can process
|
|
1263
|
+
block by block independently.
|
|
1264
|
+
|
|
1265
|
+
You are encouraged to generate code in a block-by-block manner.
|
|
1266
|
+
|
|
1267
|
+
You are encouraged to search and replace block by block instead of
|
|
1268
|
+
replacing the whole file.
|
|
1269
|
+
|
|
1270
|
+
You are encouraged to keep each file focused.
|
|
1271
|
+
|
|
1272
|
+
- SPLIT the large file into small files, the order does not matter.
|
|
1273
|
+
|
|
1274
|
+
- Try to keep deprecated blocks in file called `deprecated.mbt` in each
|
|
1275
|
+
directory.
|
|
1276
|
+
|
|
1277
|
+
- `moon fmt` is used to format your code properly.
|
|
1278
|
+
|
|
1279
|
+
- `moon info` is used to update the generated interface of the package
|
|
1280
|
+
**in current project**. Each package has a generated interface file `.mbti`,
|
|
1281
|
+
it is a brief formal description of the package. If nothing in `.mbti`
|
|
1282
|
+
changes, this means your change does not bring the visible changes to the
|
|
1283
|
+
external package users, it is typically a safe refactoring.
|
|
1284
|
+
**Note**: `moon info` will only work with packages in the current project, and
|
|
1285
|
+
therefore you cannot use `moon info` to generate interface for dependencies
|
|
1286
|
+
like standard library.
|
|
1287
|
+
|
|
1288
|
+
- So in the last step, you typically run `moon info && moon fmt` to update the
|
|
1289
|
+
interface and format the code. You also check the diffs of `.mbti` file to see
|
|
1290
|
+
if the changes are expected.
|
|
1291
|
+
|
|
1292
|
+
- You should run `moon test` to check the test is passed. MoonBit supports
|
|
1293
|
+
snapshot testing, so in some cases, your changes indeed change the behavior of
|
|
1294
|
+
the code, you should run `moon test --update` to update the snapshot.
|
|
1295
|
+
|
|
1296
|
+
- You can run `moon check` to check the code is linted correctly, run it
|
|
1297
|
+
regularly to ensure you are not in a messy state.
|
|
1298
|
+
|
|
1299
|
+
- MoonBit packages are organized per directory; each directory has a
|
|
1300
|
+
`moon.pkg.json` listing its dependencies. Each package has its files and
|
|
1301
|
+
blackbox test files (common, ending in `_test.mbt`) and whitebox test files
|
|
1302
|
+
(ending in `_wbtest.mbt`).
|
|
1303
|
+
|
|
1304
|
+
- In the toplevel directory, there is a `moon.mod.json` file describing the
|
|
1305
|
+
module and metadata.
|
|
1306
|
+
|
|
1307
|
+
## MoonBit Package `README` Generation Guide
|
|
1308
|
+
|
|
1309
|
+
- Output `README.mbt.md` in the package directory.
|
|
1310
|
+
`*.mbt.md` file and docstring contents treats `mbt check` specially.
|
|
1311
|
+
`mbt check` block will be included directly as code and also run by `moon check` and `moon test`.
|
|
1312
|
+
In docstrings, `mbt check` should only contain test blocks.
|
|
1313
|
+
If you are only referencing types from the package, you should use `mbt` which will only be syntax highlighted.
|
|
1314
|
+
Symlink `README.mbt.md` to `README.md` to adapt to systems that expect `README.md`.
|
|
1315
|
+
- Aim to cover ≥90% of the public API with concise sections and examples.
|
|
1316
|
+
- Organize by feature: construction, consumption, transformation, and key usage tips.
|
|
1317
|
+
|
|
1318
|
+
## MoonBit Testing Guide
|
|
1319
|
+
|
|
1320
|
+
Practical testing guidance for MoonBit. Keep tests black-box by default and rely on snapshot `inspect(...)`.
|
|
1321
|
+
|
|
1322
|
+
- Black-box by default: Call only public APIs via `@package.fn`. Use white-box tests only when private members matter.
|
|
1323
|
+
- **Snapshots**: Prefer `inspect(value, content="...")`. If unknown, write `inspect(value)` and run `moon test --update` (or `moon test -u`).
|
|
1324
|
+
- Use regular `inspect()` for simple values (uses `Show` trait)
|
|
1325
|
+
- Use `@json.inspect()` for complex nested structures (uses `ToJson` trait, produces more readable output)
|
|
1326
|
+
- It is encouraged to `inspect` or `@json.inspect` the whole return value of a function if
|
|
1327
|
+
the whole return value is not huge, this makes test simple. You need `impl (Show|ToJson) for YourType` or `derive (Show, ToJson)`.
|
|
1328
|
+
- **Update workflow**: After changing code that affects output, run `moon test --update` to regenerate snapshots, then review the diffs in your test files (the `content=` parameter will be updated automatically).
|
|
1329
|
+
- Grouping: Combine related checks in one `test "..." { ... }` block for speed and clarity.
|
|
1330
|
+
- Panics: Name test with prefix `test "panic ..." {...}`; if the call returns a value, wrap it with `ignore(...)` to silence warnings.
|
|
1331
|
+
- Errors: Use `try? f()` to get `Result[...]` and `inspect` it when a function may raise.
|
|
1332
|
+
- Verify: Run `moon test` (or `-u` to update snapshots) and `moon fmt` afterwards.
|
|
1333
|
+
|
|
1334
|
+
## Spec-driven Development
|
|
1335
|
+
|
|
1336
|
+
- The spec can be written in a readonly `spec.mbt` file (name is conventional, not mandatory) with stub code marked as declarations:
|
|
1337
|
+
|
|
1338
|
+
```mbt check
|
|
1339
|
+
#declaration_only
|
|
1340
|
+
pub type Yaml
|
|
1341
|
+
#declaration_only
|
|
1342
|
+
pub fn Yaml::to_string(y : Yaml) -> String raise {...}
|
|
1343
|
+
#declaration_only
|
|
1344
|
+
pub fn parse_yaml(s : String) -> Yaml raise {...}
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
- Add `spec_easy_test.mbt`, `spec_difficult_test.mbt` etc to test the spec functions; everything will be type-checked.
|
|
1348
|
+
- The AI or students can implement the `declaration_only` functions in different files thanks to our package organization.
|
|
1349
|
+
- Run `moon test` to check everything is correct.
|
|
1350
|
+
|
|
1351
|
+
- `#declaration_only` is supported for functions, methods, and types.
|
|
1352
|
+
- The `pub type Yaml` line is an intentionally opaque placeholder; the implementer chooses its representation.
|
|
1353
|
+
- Note the spec file can also contain normal code, not just declarations.
|
|
1354
|
+
|
|
1355
|
+
# Semantics based CLI tools
|
|
1356
|
+
|
|
1357
|
+
## API Discovery with `moon doc`
|
|
1358
|
+
|
|
1359
|
+
**CRITICAL**: `moon doc '<query>'` is your PRIMARY tool for discovering available APIs, functions, types, and methods in MoonBit. It is **more powerful and accurate** than `grep_search`, `semantic_search`, or any file-based searching tools. Always prefer `moon doc` over other approaches when exploring what APIs are available.
|
|
1360
|
+
|
|
1361
|
+
### Query Syntax
|
|
1362
|
+
|
|
1363
|
+
`moon doc` uses a specialized query syntax designed for symbol lookup:
|
|
1364
|
+
|
|
1365
|
+
- **Empty query**: `moon doc `
|
|
1366
|
+
- In a module: shows all available packages in current module
|
|
1367
|
+
- In a package: shows all symbols in current package
|
|
1368
|
+
- Outside package: shows all available packages
|
|
1369
|
+
|
|
1370
|
+
- **Function/value lookup**: `moon doc "[@pkg.]sym"`
|
|
1371
|
+
- **Type lookup**: `moon doc "[@pkg.]Sym"`
|
|
1372
|
+
|
|
1373
|
+
- **Method/field lookup**: `moon doc "[@pkg.]T::sym"`
|
|
1374
|
+
|
|
1375
|
+
- **Package exploration**: `moon doc "@pkg"`
|
|
1376
|
+
- Show package `pkg` and list all its exported symbols
|
|
1377
|
+
- Example: `moon doc "@json"` - explore entire `@json` package
|
|
1378
|
+
- Example: `moon doc "@encoding/utf8"` - explore nested package
|
|
1379
|
+
|
|
1380
|
+
### Workflow for API Discovery
|
|
1381
|
+
|
|
1382
|
+
1. **Finding functions**: Use `moon doc "@pkg.function_name"` before grep searching
|
|
1383
|
+
2. **Exploring packages**: Use `moon doc "@pkg"` to see what's available in a package
|
|
1384
|
+
3. **Method discovery**: Use `moon doc "Type::method"` to find methods on types
|
|
1385
|
+
4. **Type inspection**: Use `moon doc "TypeName"` to see type definition and methods
|
|
1386
|
+
5. **Package exploration**: Use `moon doc ""` at module root to see all available packages, including dependencies and stdlib
|
|
1387
|
+
6. **Globbing**: Use `*` wildcard for partial matches, e.g. `moon doc "String::*rev*"` to find all String methods with "rev" in their name
|
|
1388
|
+
|
|
1389
|
+
### Examples
|
|
1390
|
+
|
|
1391
|
+
```bash
|
|
1392
|
+
# search for String methods in standard library:
|
|
1393
|
+
$ moon doc "String"
|
|
1394
|
+
|
|
1395
|
+
type String
|
|
1396
|
+
|
|
1397
|
+
pub fn String::add(String, String) -> String
|
|
1398
|
+
pub fn String::at(String, Int) -> Int
|
|
1399
|
+
# ... more methods omitted ...
|
|
1400
|
+
|
|
1401
|
+
# list all symbols in a standard library package:
|
|
1402
|
+
$ moon doc "@buffer"
|
|
1403
|
+
moonbitlang/core/buffer
|
|
1404
|
+
|
|
1405
|
+
fn from_array(ArrayView[Byte]) -> Buffer
|
|
1406
|
+
fn from_bytes(Bytes) -> Buffer
|
|
1407
|
+
# ... more functions omitted ...
|
|
1408
|
+
|
|
1409
|
+
# list the specific function in a package:
|
|
1410
|
+
$ moon doc "@buffer.new"
|
|
1411
|
+
package "moonbitlang/core/buffer"
|
|
1412
|
+
|
|
1413
|
+
pub fn new(size_hint? : Int) -> Buffer
|
|
1414
|
+
Creates a new extensible buffer with specified initial capacity. If the
|
|
1415
|
+
initial capacity is less than 1, the buffer will be initialized with capacity
|
|
1416
|
+
1.
|
|
1417
|
+
# ... more details omitted ...
|
|
1418
|
+
|
|
1419
|
+
$ moon doc "String::*rev*"
|
|
1420
|
+
package "moonbitlang/core/string"
|
|
1421
|
+
|
|
1422
|
+
pub fn String::rev(String) -> String
|
|
1423
|
+
Returns a new string with the characters in reverse order. It respects
|
|
1424
|
+
Unicode characters and surrogate pairs but not grapheme clusters.
|
|
1425
|
+
|
|
1426
|
+
pub fn String::rev_find(String, StringView) -> Int?
|
|
1427
|
+
Returns the offset (charcode index) of the last occurrence of the given
|
|
1428
|
+
substring. If the substring is not found, it returns None.
|
|
1429
|
+
|
|
1430
|
+
# ... more details omitted ...
|
|
1431
|
+
|
|
1432
|
+
**Best practice**: When implementing a feature, start with `moon doc` queries to discover available APIs before writing code. This is faster and more accurate than searching through files.
|
|
1433
|
+
|
|
1434
|
+
```
|
|
1435
|
+
|
|
1436
|
+
## `moon ide peek-def` for Definition Lookup
|
|
1437
|
+
|
|
1438
|
+
Use this when you want inline context for a symbol without jumping files.
|
|
1439
|
+
|
|
1440
|
+
```file src/parse.mbt
|
|
1441
|
+
L45:|///|
|
|
1442
|
+
L46:|fn Parser::read_u32_leb128(self : Parser) -> UInt raise ParseError {
|
|
1443
|
+
L47:| ...
|
|
1444
|
+
...:| }
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
```bash
|
|
1448
|
+
$ moon ide peek-def -symbol Parser -loc src/parse.mbt:46:4
|
|
1449
|
+
Definition found at file src/parse.mbt
|
|
1450
|
+
| ///|
|
|
1451
|
+
2 | priv struct Parser {
|
|
1452
|
+
| ^^^^^^
|
|
1453
|
+
| bytes : Bytes
|
|
1454
|
+
| mut pos : Int
|
|
1455
|
+
| }
|
|
1456
|
+
|
|
|
1457
|
+
| ///|
|
|
1458
|
+
| fn Parser::new(bytes : Bytes) -> Parser {
|
|
1459
|
+
| { bytes, pos: 0 }
|
|
1460
|
+
| }
|
|
1461
|
+
|
|
|
1462
|
+
| ///|
|
|
1463
|
+
| fn Parser::eof(self : Parser) -> Bool {
|
|
1464
|
+
| self.pos >= self.bytes.length()
|
|
1465
|
+
| }
|
|
1466
|
+
|
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
For the `-loc` argument, the line number must be precise; the column can be approximate since `-symbol` helps locate the position.
|