firefly-compiler 0.4.79 → 0.4.81
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/.hintrc +4 -4
- package/.vscode/settings.json +4 -4
- package/bin/Release.ff +153 -153
- package/bin/firefly.mjs +1 -1
- package/compiler/Builder.ff +257 -257
- package/compiler/Compiler.ff +227 -227
- package/compiler/Dependencies.ff +187 -187
- package/compiler/DependencyLock.ff +17 -17
- package/compiler/Inference.ff +2 -1
- package/compiler/JsEmitter.ff +940 -946
- package/compiler/LspHook.ff +202 -202
- package/compiler/Main.ff +3 -3
- package/compiler/ModuleCache.ff +178 -178
- package/compiler/Tokenizer.ff +1 -1
- package/compiler/Unification.ff +1 -1
- package/compiler/Workspace.ff +88 -88
- package/core/.firefly/include/package-lock.json +564 -564
- package/core/.firefly/include/package.json +5 -5
- package/core/.firefly/include/prepare.sh +1 -1
- package/core/.firefly/package.ff +2 -2
- package/core/Array.ff +265 -265
- package/core/Atomic.ff +64 -64
- package/core/Box.ff +7 -7
- package/core/BrowserSystem.ff +40 -40
- package/core/BuildSystem.ff +148 -148
- package/core/Crypto.ff +96 -96
- package/core/Equal.ff +36 -36
- package/core/Float.ff +25 -0
- package/core/HttpClient.ff +148 -148
- package/core/JsSystem.ff +69 -69
- package/core/Json.ff +434 -434
- package/core/List.ff +486 -486
- package/core/Lock.ff +144 -144
- package/core/NodeSystem.ff +216 -216
- package/core/Ordering.ff +161 -161
- package/core/Path.ff +401 -401
- package/core/Random.ff +134 -134
- package/core/RbMap.ff +216 -216
- package/core/Show.ff +43 -43
- package/core/SourceLocation.ff +68 -68
- package/core/Stream.ff +9 -9
- package/core/Task.ff +149 -141
- package/core/Try.ff +25 -4
- package/experimental/benchmarks/ListGrab.ff +23 -23
- package/experimental/benchmarks/ListGrab.java +55 -55
- package/experimental/benchmarks/Pyrotek45.ff +30 -30
- package/experimental/benchmarks/Pyrotek45.java +64 -64
- package/experimental/bidirectional/Bidi.ff +88 -88
- package/experimental/random/Index.ff +53 -53
- package/experimental/random/Process.ff +120 -120
- package/experimental/random/Scrape.ff +51 -51
- package/experimental/random/Symbols.ff +73 -73
- package/experimental/random/Tensor.ff +52 -52
- package/experimental/random/Units.ff +36 -36
- package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
- package/experimental/s3/S3TestPut.ff +16 -16
- package/experimental/tests/TestJson.ff +26 -26
- package/firefly.sh +0 -0
- package/fireflysite/.firefly/package.ff +4 -4
- package/fireflysite/CommunityOverview.ff +20 -20
- package/fireflysite/CountingButtonDemo.ff +58 -58
- package/fireflysite/DocumentParser.ff +331 -217
- package/fireflysite/ExamplesOverview.ff +40 -40
- package/fireflysite/FrontPage.ff +344 -360
- package/fireflysite/{GuideIntroduction.ff → GettingStarted.ff} +45 -52
- package/fireflysite/Guide.ff +442 -411
- package/fireflysite/Main.ff +151 -137
- package/fireflysite/MatchingPasswordsDemo.ff +82 -82
- package/fireflysite/PackagesOverview.ff +49 -49
- package/fireflysite/PostgresqlDemo.ff +34 -34
- package/fireflysite/ReferenceAll.ff +18 -0
- package/fireflysite/ReferenceIntroduction.ff +11 -0
- package/fireflysite/Styles.ff +567 -495
- package/fireflysite/Test.ff +46 -0
- package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -0
- package/fireflysite/assets/markdown/reference/EmittedJavascript.md +66 -0
- package/fireflysite/assets/markdown/reference/Exceptions.md +101 -0
- package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +338 -0
- package/fireflysite/assets/markdown/reference/JavascriptInterop.md +134 -0
- package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -0
- package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -0
- package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -0
- package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -0
- package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -0
- package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -0
- package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -0
- package/fireflysite/assets/markdown/{ControlFlow.md → scratch/ControlFlow.md} +136 -136
- package/fireflysite/assets/markdown/scratch/Toc.md +41 -0
- package/lsp/.firefly/package.ff +1 -1
- package/lsp/CompletionHandler.ff +828 -828
- package/lsp/Handler.ff +714 -714
- package/lsp/HoverHandler.ff +79 -79
- package/lsp/LanguageServer.ff +272 -272
- package/lsp/SignatureHelpHandler.ff +55 -55
- package/lsp/SymbolHandler.ff +181 -181
- package/lsp/TestReferences.ff +17 -17
- package/lsp/TestReferencesCase.ff +7 -7
- package/lsp/stderr.txt +1 -1
- package/lsp/stdout.txt +34 -34
- package/lux/.firefly/package.ff +1 -1
- package/lux/Css.ff +648 -648
- package/lux/CssTest.ff +48 -48
- package/lux/Lux.ff +593 -487
- package/lux/LuxEvent.ff +116 -116
- package/lux/Main.ff +123 -123
- package/lux/Main2.ff +143 -143
- package/lux/TestDry.ff +27 -0
- package/output/js/ff/compiler/Builder.mjs +47 -47
- package/output/js/ff/compiler/Dependencies.mjs +3 -3
- package/output/js/ff/compiler/Inference.mjs +2 -2
- package/output/js/ff/compiler/JsEmitter.mjs +18 -72
- package/output/js/ff/compiler/Main.mjs +4 -4
- package/output/js/ff/compiler/ModuleCache.mjs +4 -4
- package/output/js/ff/core/Array.mjs +59 -59
- package/output/js/ff/core/Atomic.mjs +36 -36
- package/output/js/ff/core/BrowserSystem.mjs +11 -11
- package/output/js/ff/core/BuildSystem.mjs +30 -30
- package/output/js/ff/core/Crypto.mjs +40 -40
- package/output/js/ff/core/Float.mjs +50 -0
- package/output/js/ff/core/HttpClient.mjs +56 -56
- package/output/js/ff/core/Json.mjs +147 -147
- package/output/js/ff/core/List.mjs +50 -50
- package/output/js/ff/core/Lock.mjs +97 -97
- package/output/js/ff/core/NodeSystem.mjs +87 -87
- package/output/js/ff/core/Ordering.mjs +8 -8
- package/output/js/ff/core/Path.mjs +231 -231
- package/output/js/ff/core/Random.mjs +56 -56
- package/output/js/ff/core/Task.mjs +71 -39
- package/output/js/ff/core/Try.mjs +98 -4
- package/package.json +1 -1
- package/postgresql/Pg.ff +1 -1
- package/rpc/.firefly/package.ff +1 -1
- package/rpc/Rpc.ff +70 -70
- package/s3/.firefly/package.ff +1 -1
- package/s3/S3.ff +94 -94
- package/unsafejs/UnsafeJs.ff +19 -19
- package/vscode/LICENSE.txt +21 -21
- package/vscode/Prepublish.ff +15 -15
- package/vscode/README.md +16 -16
- package/vscode/client/package.json +22 -22
- package/vscode/client/src/extension.ts +104 -104
- package/vscode/icons/firefly-icon.svg +10 -10
- package/vscode/language-configuration.json +61 -61
- package/vscode/package-lock.json +3623 -3623
- package/vscode/package.json +1 -1
- package/vscode/snippets.json +241 -241
- package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
- package/webserver/.firefly/include/package-lock.json +22 -22
- package/webserver/.firefly/include/package.json +5 -5
- package/webserver/.firefly/package.ff +2 -2
- package/webserver/WebServer.ff +685 -685
- package/websocket/.firefly/package.ff +1 -1
- package/websocket/WebSocket.ff +131 -131
- package/fireflysite/GuideAll.ff +0 -21
- package/fireflysite/GuideBaseTypes.ff +0 -168
- package/fireflysite/GuideControlFlow.ff +0 -212
- package/fireflysite/assets/markdown/Example.md +0 -78
- /package/fireflysite/assets/{NotoSansMono-Regular.ttf → font/NotoSansMono-Regular.ttf} +0 -0
- /package/fireflysite/assets/{NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf → font/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf} +0 -0
- /package/fireflysite/assets/{autocomplete-small.png → image/autocomplete-small.png} +0 -0
- /package/fireflysite/assets/{autocomplete.png → image/autocomplete.png} +0 -0
- /package/fireflysite/assets/{edit-time-error.png → image/edit-time-error.png} +0 -0
- /package/fireflysite/assets/{firefly-logo-notext.png → image/firefly-logo-notext.png} +0 -0
- /package/fireflysite/assets/{firefly-logo-yellow.png → image/firefly-logo-yellow.png} +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Functions and methods
|
|
2
|
+
|
|
3
|
+
There are 5 kinds of functions in Firefly:
|
|
4
|
+
|
|
5
|
+
* Top-level functions
|
|
6
|
+
* Anonymous functions
|
|
7
|
+
* Local functions
|
|
8
|
+
* Methods
|
|
9
|
+
* Trait functions
|
|
10
|
+
|
|
11
|
+
Trait functions are covered in section [Traits and instances](traits-and-instances), the rest are covered below.
|
|
12
|
+
|
|
13
|
+
# Top-level functions
|
|
14
|
+
|
|
15
|
+
Functions at the top level are defined like this:
|
|
16
|
+
|
|
17
|
+
```firefly
|
|
18
|
+
add(a: Int, b: Int): Int {
|
|
19
|
+
let sum = a + b
|
|
20
|
+
sum
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This function takes two arguments of type `Int` and returns their sum as an `Int`. A function with a return type other than `Unit`, must have an expression as the last statement, which is returned. In the example above, the value of `sum` is returned.
|
|
25
|
+
|
|
26
|
+
When a function returns Unit, it can end with any statement.
|
|
27
|
+
|
|
28
|
+
```firefly
|
|
29
|
+
class Counter(mutable value: Int)
|
|
30
|
+
|
|
31
|
+
reset(counter: Counter): Unit {
|
|
32
|
+
counter.value = 0
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The example above defines a type `Counter` with a mutable field. The function `reset` sets this value to zero.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
A `Unit` return type can be omitted in a function definition, like this:
|
|
40
|
+
|
|
41
|
+
```firefly
|
|
42
|
+
reset(counter: Counter) {
|
|
43
|
+
counter.value = 0
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The parameter types must be declares expitly and the parentheses are required, even if there are no parameters. The function name and parameter names must start with a lowercase letter.
|
|
48
|
+
|
|
49
|
+
# Type parameters
|
|
50
|
+
|
|
51
|
+
In a function signature, you can introduce a list of type parameters enclosed in square brackets, immediately following the function name. These type parameters can be used in the rest of the signature.
|
|
52
|
+
|
|
53
|
+
The following example defines a function `swap` that takes any Pair and returns a Pair with first and second values swapped:
|
|
54
|
+
|
|
55
|
+
```firefly
|
|
56
|
+
swap[A, B](pair: Pair[A, B]): Pair[B, A] {
|
|
57
|
+
Pair(pair.second, pair.first)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Two type parameters `A` and `B` are first introduced in the square brackets. These type parameters are swapped in the input and return type, expressing the value swap at type level. The type parameters are unbuilded in the sense that `swap` may be called with `A` and `B` replaced by any types.
|
|
62
|
+
|
|
63
|
+
Type parameters can be bounded or constrained like this:
|
|
64
|
+
|
|
65
|
+
```firefly
|
|
66
|
+
same[E: Equal](pair: Pair[E, E]): Bool {
|
|
67
|
+
pair.first == pair.second
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The type parameter `E` must implement the Equal trait, which is required to perform equality comparisons. The section on [traits and instances](traits-and-instances) will discuss constraints in more detail.
|
|
72
|
+
|
|
73
|
+
Firefly cannot operate on the concrete types of type parameters at runtime. The behavior of a function is limited to what is specified in the function signature.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Calling functions
|
|
77
|
+
|
|
78
|
+
Functions are called like this:
|
|
79
|
+
|
|
80
|
+
```firefly
|
|
81
|
+
add(1, 2) // 3
|
|
82
|
+
same(Pair('A', 'a')) // False
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Just like variants, the arguments can be named, and given out of order, like this:
|
|
86
|
+
|
|
87
|
+
```firefly
|
|
88
|
+
add(b = 2, a = 1)
|
|
89
|
+
add(b = 2, 1) // Same as above
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
# Default Values
|
|
93
|
+
|
|
94
|
+
Function parameters in Firefly can have default values, which are used when no argument is provided for that parameter during a function call. Default values are specified in the function signature by assigning a value to the parameter.
|
|
95
|
+
|
|
96
|
+
Here’s an example of a function with a default value:
|
|
97
|
+
|
|
98
|
+
```firefly
|
|
99
|
+
add(a: Int, b: Int = 1): Int {
|
|
100
|
+
a + b
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
When called without the second arguments, the function will use the default value:
|
|
105
|
+
|
|
106
|
+
```firefly
|
|
107
|
+
greet(1) // returns 2
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If an argument is provided, it overrides the default:
|
|
111
|
+
|
|
112
|
+
```firefly
|
|
113
|
+
greet(1, 2) // returns 3
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Recursion
|
|
118
|
+
|
|
119
|
+
Function definitions can be recursive, meaning that a function can call itself. Here’s an example of a recursive function that calculates the factorial of a number:
|
|
120
|
+
|
|
121
|
+
```firefly
|
|
122
|
+
factorial(n: Int): Int {
|
|
123
|
+
if(n == 0) {
|
|
124
|
+
1
|
|
125
|
+
} else {
|
|
126
|
+
n * factorial(n - 1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
When calling `factorial(0)`, the base case is triggered, returning `1`. Calling the function with a positive integer will recursively calculate the factorial. However, calling it with negative values will result in infinite recursion, as no base case exists for such input.
|
|
132
|
+
|
|
133
|
+
# Tail Recursion
|
|
134
|
+
|
|
135
|
+
In some recursive functions, the recursive call is the last operation performed before returning the result. This is *tail recursion*. The Firefly compiler can optimize tail recursive calls, avoiding the buildup of function calls on the stack.
|
|
136
|
+
|
|
137
|
+
You can use the `tailcall` keyword to explicitly mark a recursive call as tail-recursive, ensuring the compiler applies the optimization.
|
|
138
|
+
|
|
139
|
+
Here is a tail-recursive implementation of the factorial function:
|
|
140
|
+
|
|
141
|
+
```firefly
|
|
142
|
+
factorial(n: Int, acc: Int = 1): Int {
|
|
143
|
+
if(n == 0) {
|
|
144
|
+
acc
|
|
145
|
+
} else {
|
|
146
|
+
tailcall factorial(n - 1, n * acc)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This version introduces an additional parameter, `acc`, which acts as an accumulator to hold the running result of the factorial calculation. It is initialized to 1 by default, ensuring that when the function is first called with a single argument, the computation starts correctly. By moving the multiplication into the recursive call via the accumulator, the call becomes a tail-call, allowing the Firefly compiler to optimize it.
|
|
152
|
+
|
|
153
|
+
# Anonymous functions
|
|
154
|
+
|
|
155
|
+
In firefly anonymous functions are written in curlybrases and constucted like this:
|
|
156
|
+
|
|
157
|
+
```firefly
|
|
158
|
+
{a, b =>
|
|
159
|
+
let sum = a + b
|
|
160
|
+
sum
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This anonymous function takes two arguments and returns their sum. Like named functions, the body is a sequence of statements where the last expression is returned.
|
|
165
|
+
|
|
166
|
+
Anonymous functions are often used right away, like below:
|
|
167
|
+
:
|
|
168
|
+
|
|
169
|
+
```firefly
|
|
170
|
+
[1, 2, 3].map({x => x + 1}) // Returns [2, 3, 4]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
An anonymous function that increments the given value by one is passed as argument to the methods `map` working on lists.
|
|
174
|
+
|
|
175
|
+
These functions are anonymous in the sense that they do not bring a name into scope themselves. They are just expressions that construct a function value. Like all other values, they can be assigned to variables, passed as arguments, or returned from other functions. But unlike other values, they can also be called.
|
|
176
|
+
|
|
177
|
+
This is in contrast to named functions, which are not first-class in Firefly. The name of a top-level function can only be called but is not an expression in Firefly. To pass a top-level function as an argument, for instance, it must be converted to an anonymous function first.
|
|
178
|
+
|
|
179
|
+
The type of function values are writen like this:
|
|
180
|
+
|
|
181
|
+
```firefly
|
|
182
|
+
Int => Int // One parameter
|
|
183
|
+
(Int, Int) => Int // Multiple parameters
|
|
184
|
+
() => Int // No parameters
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The type of an anonymous function cannot be written explicitly in the definition but is inferred from its usage. It will always have a monomorphic type where the argument and return types are concrete types.
|
|
188
|
+
|
|
189
|
+
Here are some examples of anonymous functions assigned to variables explicitly given a type.
|
|
190
|
+
|
|
191
|
+
Anonymous function without parameters are written without the arrow (`=>`), like this:
|
|
192
|
+
|
|
193
|
+
```firefly
|
|
194
|
+
let life: () => Int = {42}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
This is an anonymous function taking no arguments and returning `Unit`:
|
|
198
|
+
|
|
199
|
+
```firefly
|
|
200
|
+
let unit: () => Unit = {}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This is an anonymous function that increments its input:
|
|
204
|
+
|
|
205
|
+
```firefly
|
|
206
|
+
let next: Int => Int = {i => i + 1}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This anonymous function takes multiple arguments:
|
|
210
|
+
|
|
211
|
+
```firefly
|
|
212
|
+
let plus: (Int, Int) => Int = {a, b => a + b}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Anonymous function are called like named function.
|
|
216
|
+
|
|
217
|
+
```firefly
|
|
218
|
+
life() // returns 42
|
|
219
|
+
unit() // returns unit
|
|
220
|
+
next(1) // returns 2
|
|
221
|
+
plus(1, 2) // returns 3
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Parameter names are not part of the function type, and likewise, anonymous functions cannot be called with named arguments. The same goes for default argument values, which are not supported for anonymous functions.
|
|
225
|
+
|
|
226
|
+
The parameter list and the function arrow can be omitted when the parameters are only used once in the function body. In such cases, the parameters in the body are replaced with underscores (`_`), like this:
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
```firefly
|
|
230
|
+
let next: Int => Int = {_ + 1}
|
|
231
|
+
let plus: (Int, Int) => Int = {_ + _}
|
|
232
|
+
let identity: Int => Int = {_}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
These underscores, or anonymous parameters, always belong to the nearest anonymous function. Consider the following function:
|
|
236
|
+
|
|
237
|
+
```firefly
|
|
238
|
+
let f: Int => Int = {{_ + 1}(_)}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
In this code, there is an outer and an inner anonymous function, both taking one argument. The first underscore belongs to the inner function, which is called immediately by the outer function with the outer function's anonymous parameter as the argument.
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# Local functions
|
|
245
|
+
|
|
246
|
+
Local functions are declared exactly like top-level functions but with the `function` keyword in front of the signature, like this:
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
```firefly
|
|
250
|
+
function square(n: Int): Int {
|
|
251
|
+
n * n
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The above local function definition is a statement, similar to local variables declared with `let`. The function name `square` will be in scope for the rest of the code block.
|
|
256
|
+
|
|
257
|
+
Furthermore, local functions declared in sequence are in scope within each other's bodies, allowing them to be mutually recursive.
|
|
258
|
+
|
|
259
|
+
# Trailing lambda calls
|
|
260
|
+
|
|
261
|
+
```firefly
|
|
262
|
+
if(x == 1) {"One"}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
# Methods
|
|
266
|
+
|
|
267
|
+
Firefly has methods, which are called like this:
|
|
268
|
+
|
|
269
|
+
```firefly
|
|
270
|
+
Some(1).isEmpty() // False
|
|
271
|
+
Some(1).map {_ + 1} // Some(2)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The examples above, calls the two methods `isEmpty` and `map` defined on `Option`. The code below, shows how these methods are defined in `ff:core` package.
|
|
275
|
+
|
|
276
|
+
```firefly
|
|
277
|
+
data Option[T] {
|
|
278
|
+
None
|
|
279
|
+
Some(value: T)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
extend self[T]: Option[T] {
|
|
283
|
+
isEmpty(): Bool {
|
|
284
|
+
self.{
|
|
285
|
+
| None => True
|
|
286
|
+
| Some(_) => False
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
map[R](body: T => R): Option[R] {
|
|
291
|
+
self.{
|
|
292
|
+
| None => None
|
|
293
|
+
| Some(v) => Some(body(v))
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Methods can be defined for a more narrow targer type, like `flatten` below:
|
|
300
|
+
|
|
301
|
+
```firefly
|
|
302
|
+
extend self[T]: Option[Option[T]] {
|
|
303
|
+
flatten(): Option[T] {
|
|
304
|
+
self.{
|
|
305
|
+
| None => None
|
|
306
|
+
| Some(v) => v
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The extend block above, will only define `flatten` for options types of options.
|
|
313
|
+
|
|
314
|
+
In code below, the extend block defines methods for the target type `Option[T]`, but only when `T` implements the `Equal` trait.
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
```firefly
|
|
318
|
+
extend self[T: Equal]: Option[T] {
|
|
319
|
+
contains(value: T): Bool {
|
|
320
|
+
self.{
|
|
321
|
+
| None => False
|
|
322
|
+
| Some(v) => v == value
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# Special method call syntax
|
|
331
|
+
|
|
332
|
+
```firefly
|
|
333
|
+
if(x == 1) {"One"} else {"Several"}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
# Trait functions
|
|
337
|
+
|
|
338
|
+
Trait functions are covered in the section about [traits and instances](traits-and-instances)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# JavaScript interop
|
|
2
|
+
|
|
3
|
+
Firefly compiles to JavaScript, which enables it to run in the browser.
|
|
4
|
+
It uses Node.js as its server side and desktop runtime.
|
|
5
|
+
|
|
6
|
+
The JavaScript interop features enable the wrapping of libraries that are written in JavaScript so that they can be used in Firefly.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# The JsSystem
|
|
10
|
+
|
|
11
|
+
Most JavaScript functionality can be accessed via the `JsSystem` object.
|
|
12
|
+
|
|
13
|
+
```firefly
|
|
14
|
+
browserMain(system: BrowserSystem): Unit {
|
|
15
|
+
let document = system.js().global().get("document")
|
|
16
|
+
let element = document.call1("getElementById", "my-id")
|
|
17
|
+
element.set("innerText", "Hi!")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This example gets the global `document`, calls `getElementId("my-id")` on it, and sets `innerText = "Hi!"`.
|
|
22
|
+
|
|
23
|
+
The type of the `document` and `element` variables here is `JsValue`, which represents an arbitrary JavaScript value.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# The ff:unsafejs package
|
|
27
|
+
|
|
28
|
+
This package provides access to unsafe JavaScript features:
|
|
29
|
+
|
|
30
|
+
```firefly
|
|
31
|
+
// Obtains the JsSystem without a capability
|
|
32
|
+
jsSystem(): JsSystem
|
|
33
|
+
|
|
34
|
+
// Imports a JavaScript module (the import gets hoisted to a top level import)
|
|
35
|
+
import(module: String): JsValue
|
|
36
|
+
|
|
37
|
+
// Awaits an async function (only works in async context)
|
|
38
|
+
await[T](body: () => T): T
|
|
39
|
+
|
|
40
|
+
// Throws TaskAbortedException if the current task has been aborted
|
|
41
|
+
throwIfCancelled(): Unit
|
|
42
|
+
|
|
43
|
+
// Returns true if the current task has been aborted
|
|
44
|
+
cancelled(): Bool
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
In the future, it may be possible to provide a whitelist of dependencies that are allowed to use this package.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Internal FFI
|
|
51
|
+
|
|
52
|
+
The `target` keyword allows writing almost raw JavaScript.
|
|
53
|
+
|
|
54
|
+
```firefly
|
|
55
|
+
alertHi(name: String)
|
|
56
|
+
target browser sync """
|
|
57
|
+
alert("Hi " + name_ + "!");
|
|
58
|
+
"""
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Multiple target keywords are allowed per function or method.
|
|
62
|
+
The target type is `js` or the more specific types `browser` or `node`, and then a mode that's either `sync` for when called synchronously, or `async` for when called asynchronously.
|
|
63
|
+
|
|
64
|
+
Argument names are avaliable with a `_` suffix in the JavaScript code block.
|
|
65
|
+
|
|
66
|
+
JavaScript module imports can be done in the beginning of a JavaScript code block with the specfic syntax `import * as foo from 'bar'`. The import statement will be hoisted to a top level import.
|
|
67
|
+
|
|
68
|
+
In the future, the `target` keyword and its functionality may be removed from the language.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Emitted JavaScript
|
|
72
|
+
|
|
73
|
+
While most Firefly code maps directly to the JavaScript equivalent, there are two notable exceptions:
|
|
74
|
+
|
|
75
|
+
* I/O appears to be blocking, but compiles down to JavaScript `async`/`await`.
|
|
76
|
+
* Methods are resolved statically in Firefly and become top level functions in JavaScript.
|
|
77
|
+
|
|
78
|
+
In addition, pattern matching doesn't have a direct equivalent in JavaScript, and neither does traits.
|
|
79
|
+
|
|
80
|
+
Consider the following main function:
|
|
81
|
+
|
|
82
|
+
```firefly
|
|
83
|
+
nodeMain(system: NodeSystem) {
|
|
84
|
+
|
|
85
|
+
let files = ["a.txt", "b.txt"]
|
|
86
|
+
|
|
87
|
+
let contents = files.map {file =>
|
|
88
|
+
system.path(file).readText()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let upper = contents.map {content =>
|
|
92
|
+
content.upper()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
system.writeLine("Result: " + upper.join(""))
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The JavaScript that's emitted looks roughly like this:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
export async function nodeMain$(system) {
|
|
104
|
+
|
|
105
|
+
const files = ["a.txt", "b.txt"]
|
|
106
|
+
|
|
107
|
+
const contents = await List_map$(files, async file => {
|
|
108
|
+
return await Path_readText$(await NodeSystem_path$(system, file))
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const upper = List_map(contents, content => {
|
|
112
|
+
return String_upper(content)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
NodeSystem_writeLine$("Result: " + String_join(upper, ""))
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
In JavaScript, `nodeMain` becomes an `async` function and gets the `$` suffix to distinguish it from a synchronous function.
|
|
121
|
+
|
|
122
|
+
The `let` keyword in Firefly corresponds to the `const` keyword in JavaScript, and Firefly list literals become JavaScript array literals.
|
|
123
|
+
|
|
124
|
+
The `map` method becomes a top level function, or rather, one `async` top level function named `List_map$` and another synchronous function named `List_map`.
|
|
125
|
+
A static analysis is performed to decide which version to call.
|
|
126
|
+
|
|
127
|
+
Because the first call to `map` is passed an anonymous function that calls a method on `system`, which is a capability, and the current top level function is asynchronous,
|
|
128
|
+
the analysis picks the asynchronous version `List_map$` and uses the `await` keyword.
|
|
129
|
+
|
|
130
|
+
The second call to `map` is passed an anonymous function that doesn't involve any other capabilities, the analysis picks the synchronous version `List_map`.
|
|
131
|
+
|
|
132
|
+
This static analysis is necessarily conservative, and may occasionally call the asynchronous version of a function where the synchrhonous version would suffice.
|
|
133
|
+
When using the VSCode extension, the hover information for a call will note if the call is asynchronous.
|
|
134
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Modules and packages
|
|
2
|
+
|
|
3
|
+
In Firefly, source code is organized in packages, which can be versioned, released and depended on.
|
|
4
|
+
Inside packages, there are modules, which are individual files that can be imported from other modules.
|
|
5
|
+
|
|
6
|
+
The minimal package consists of just a single module file, and nothing else.
|
|
7
|
+
Such a module can contain the following top level constructs:
|
|
8
|
+
|
|
9
|
+
```firefly
|
|
10
|
+
// Package information
|
|
11
|
+
package mygroup:mypackage:1.2.3
|
|
12
|
+
dependency ff:webserver:0.0.0
|
|
13
|
+
include "node_modules"
|
|
14
|
+
|
|
15
|
+
// Module imports
|
|
16
|
+
import WebServer from ff:webserver
|
|
17
|
+
|
|
18
|
+
// Named constants
|
|
19
|
+
answer: Int = 42
|
|
20
|
+
|
|
21
|
+
// Function definitions
|
|
22
|
+
f(x: Int): Int {
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Type definitions
|
|
27
|
+
data Point(x: Int, y: Int)
|
|
28
|
+
|
|
29
|
+
// Method definitions
|
|
30
|
+
extend self: Point {
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Traits and trait instances
|
|
35
|
+
trait T: HasCenter {
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
instance Point: HasCenter {
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In a multi-file package, the package information is instead specified in a separate `package.ff` file which must live in the `.firefly/` subdirectory. A typical multi-file package looks like this:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
mypackage/
|
|
47
|
+
.firefly/
|
|
48
|
+
package.ff
|
|
49
|
+
MyModule.ff
|
|
50
|
+
MyOtherModule.ff
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The two module files here `MyModule.ff` and `MyOtherModule.ff` defines the modules `MyModule` and `MyOtherModule` respectively. Module files must start with a capital letter.
|
|
54
|
+
|
|
55
|
+
In general, identifiers of any kind in Firefly must start with an ASCII letter, and can only contain ASCII letters and numbers. This also applies to module and package names.
|
|
56
|
+
|
|
57
|
+
Apart from containing the `package.ff` file, the `.firefly/` subdirectory is used for various compiler output and can contain an `include/` directory with JavaScript that you want to include verbatim into the build via the `include` directive.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Package information
|
|
61
|
+
|
|
62
|
+
The `package` keyword specifies the group name, package name and major.minor.patch package version:
|
|
63
|
+
|
|
64
|
+
```firefly
|
|
65
|
+
package mygroup:mypackage:1.2.3
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A group is a person, organization or similar entity that's allowed to publish packages under that group name.
|
|
69
|
+
|
|
70
|
+
If present, the `package` keyword must be the first token in the file. In a single file package, all the package information goes before any other content.
|
|
71
|
+
|
|
72
|
+
The `dependency` keyword specifies the group name, package name and major.minor.patch package version of a package that is a dependency of this package:
|
|
73
|
+
|
|
74
|
+
```firefly
|
|
75
|
+
dependency ff:webserver:0.0.0
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
There may be zero or more dependencies. If there are conflicting versions of the same package in the dependencies or transitive dependencies, the first version that's encountered in a breadth first search from top to bottom will be used.
|
|
79
|
+
|
|
80
|
+
The `include` keyword includes files verbatim in the JavaScript that is emitted by the compiler:
|
|
81
|
+
|
|
82
|
+
```firefly
|
|
83
|
+
include "node_modules"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This instructs the compiler to copy the file or directory `mypackage/.firefly/include/node_modules` verbatim into the `mypackage/.firefly/ouput/node/mygroup/mypackage/node/node_modules` directory. It doesn't do anything for the browser target.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Imports and exports
|
|
90
|
+
|
|
91
|
+
To access the symbols that a module exports, it is necessary to import it:
|
|
92
|
+
|
|
93
|
+
```firefly
|
|
94
|
+
import WebServer from ff:webserver
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This imports the `WebServer` module from the `ff:webserver` package, which must have been declared as a dependency.
|
|
98
|
+
|
|
99
|
+
Symbols exported from the module can then be accessed using the `WebServer.` prefix:
|
|
100
|
+
|
|
101
|
+
```firefly
|
|
102
|
+
WebServer.new(system, "localhost", 8080)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Types, variants and traits are available under the prefix, but can also be accessed with no prefix at all. In the case of naming collisions between these, the last import wins.
|
|
106
|
+
|
|
107
|
+
If two imported modules have the same name, or a different prefix is desired, the symbols can be imported under a different prefix:
|
|
108
|
+
|
|
109
|
+
```firefly
|
|
110
|
+
import WebServer as W from ff:webserver
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The symbols can then be accessed using the `W.` prefix:
|
|
114
|
+
|
|
115
|
+
```firefly
|
|
116
|
+
W.new(system, "localhost", 8080)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Currently, all top level definitions are automatically exported. This is likely to change in the future.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Main functions
|
|
123
|
+
|
|
124
|
+
In Firefly, there are three targets, each with its own main function:
|
|
125
|
+
|
|
126
|
+
```firefly
|
|
127
|
+
nodeMain(system: NodeSystem) {
|
|
128
|
+
system.writeLine("Hello server!")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
browserMain(system: BrowserSystem) {
|
|
132
|
+
system.writeLine("Hello browser!")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
buildMain(system: BuildSystem) {
|
|
136
|
+
system.writeLine("Hello build!")
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The three main functions may coexist in the same file.
|
|
141
|
+
The `nodeMain` function runs when you run your executable,
|
|
142
|
+
the `browserMain` function runs in the browser, and
|
|
143
|
+
the `buildMain` function runs when you build your program.
|
|
144
|
+
|
|
145
|
+
The `system` parameter is an object with methods that let you do I/O in the target system.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Constants
|
|
149
|
+
|
|
150
|
+
Named constants may be defined at the top level, and must have an explict type:
|
|
151
|
+
|
|
152
|
+
```firefly
|
|
153
|
+
answer: Int = 42
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Here `answer` is defined to be an `Int` with the value `42`.
|
|
157
|
+
|
|
158
|
+
There's no global state in Firefly, and to enforce this, the type of a named constant must have be declared with the `data` or `newtype` keyword.
|
|
159
|
+
|
|
160
|
+
Named constants that occur on the right hand side must not directly or indirecly refer to the named constant being defined.
|
|
161
|
+
|
|
162
|
+
Neither of the requirements are enforced currently, but this is likely to change in the future.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Structured concurrency
|
|
2
|
+
|
|
3
|
+
Firefly has a lightweight task system that supports structured concurrency and cancellation.
|
|
4
|
+
|
|
5
|
+
Tasks are structured in a parent-child relationship, where the parent scope waits for all its child tasks to complete before proceeding.
|
|
6
|
+
|
|
7
|
+
If the parent task or a sibling subtask fails with an uncaught exception, the other subtasks are cancelled.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Spawning a subtask
|
|
11
|
+
|
|
12
|
+
The `system` parameter for the main function has a `mainTask()` method that returns a `Task`.
|
|
13
|
+
This is the main task whose lifecycle corresponds to that of the application.
|
|
14
|
+
A task can `spawn` subtasks:
|
|
15
|
+
|
|
16
|
+
```firefly
|
|
17
|
+
nodeMain(system: NodeSystem) {
|
|
18
|
+
system.mainTask().spawn {subtask =>
|
|
19
|
+
while {True} {
|
|
20
|
+
Log.trace("Hello from subtask!")
|
|
21
|
+
subtask.sleep(Duration(1.0))
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
while {True} {
|
|
25
|
+
Log.trace("Hello from main task!")
|
|
26
|
+
system.mainTask().sleep(Duration(1.0))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In the above example, there's one while loop running in the subtask, and another running in the main task.
|
|
32
|
+
They run concurrently, and the `"Hello..."` messages are thus logged in an interleaved fashion.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Waiting for subtasks
|
|
36
|
+
|
|
37
|
+
```firefly
|
|
38
|
+
nodeMain(system: NodeSystem) {
|
|
39
|
+
system.mainTask().spawn {task =>
|
|
40
|
+
task.spawn {subtask =>
|
|
41
|
+
while {True} {
|
|
42
|
+
Log.trace("Hello from subtask!")
|
|
43
|
+
subtask.sleep(Duration(1.0))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|