firefly-compiler 0.4.80 → 0.4.82
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/compiler/Parser.ff +44 -3
- package/compiler/Token.ff +8 -4
- package/compiler/Tokenizer.ff +2 -2
- package/core/JsSystem.ff +12 -0
- package/core/JsValue.ff +6 -0
- package/core/Task.ff +8 -0
- package/fireflysite/Guide.ff +2 -3
- package/fireflysite/Main.ff +34 -24
- package/fireflysite/ReferenceAll.ff +1 -2
- package/fireflysite/Test.ff +8 -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 +139 -9
- package/fireflysite/assets/markdown/reference/JavascriptInterop.md +134 -0
- package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +7 -13
- package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -0
- package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -0
- package/lux/Lux.ff +179 -49
- package/lux/TestDry.ff +28 -0
- package/output/js/ff/compiler/Parser.mjs +78 -12
- package/output/js/ff/compiler/Token.mjs +82 -74
- package/output/js/ff/compiler/Tokenizer.mjs +4 -4
- package/output/js/ff/core/JsSystem.mjs +32 -0
- package/output/js/ff/core/JsValue.mjs +16 -0
- package/output/js/ff/core/Task.mjs +32 -0
- package/package.json +1 -1
- package/vscode/client/package-lock.json +544 -544
- package/vscode/package.json +1 -1
- package/vscode/syntaxes/firefly.tmLanguage.json +5 -0
package/compiler/Parser.ff
CHANGED
|
@@ -1025,11 +1025,10 @@ extend self: Parser {
|
|
|
1025
1025
|
if(self.current().is(LKeyword) && (self.current().rawIs("let") || self.current().rawIs("mutable"))) {self.parseLet()} else:
|
|
1026
1026
|
if(self.current().is(LKeyword) && self.current().rawIs("function")) {self.parseFunctions()} else:
|
|
1027
1027
|
let term = self.parseTerm()
|
|
1028
|
-
if(!self.current().is(LAssign) && !self.current().
|
|
1028
|
+
if(!self.current().is(LAssign) && !self.current().is2(LAssignPlus, LAssignMinus)) {term} else:
|
|
1029
1029
|
let token = do {
|
|
1030
1030
|
if(self.current().is(LAssignPlus)) {self.skip(LAssignPlus)} else:
|
|
1031
1031
|
if(self.current().is(LAssignMinus)) {self.skip(LAssignMinus)} else:
|
|
1032
|
-
if(self.current().is(LAssignLink)) {self.skip(LAssignLink)} else:
|
|
1033
1032
|
self.skip(LAssign)
|
|
1034
1033
|
}
|
|
1035
1034
|
let operator = token.raw().dropLast(1)
|
|
@@ -1153,7 +1152,7 @@ extend self: Parser {
|
|
|
1153
1152
|
True
|
|
1154
1153
|
} else {False}
|
|
1155
1154
|
mutable result = self.parseAtom()
|
|
1156
|
-
while {self.current().
|
|
1155
|
+
while {self.current().is4(LBracketLeft, LColon, LDot, LArrowThin)} {
|
|
1157
1156
|
if(self.current().is(LDot)) {
|
|
1158
1157
|
self.skip(LDot)
|
|
1159
1158
|
if(self.current().rawIs("{")) {
|
|
@@ -1166,6 +1165,8 @@ extend self: Parser {
|
|
|
1166
1165
|
let token = self.skip(LLower)
|
|
1167
1166
|
result = EField(token.at(), False, result, token.raw())
|
|
1168
1167
|
}
|
|
1168
|
+
} elseIf {self.current().is(LArrowThin)} {
|
|
1169
|
+
result = self.parseDynamicMember(result)
|
|
1169
1170
|
} else {
|
|
1170
1171
|
let at = self.current().at()
|
|
1171
1172
|
let typeArguments = if(!self.current().rawIs("[")) {[]} else {self.parseTypeArguments()}
|
|
@@ -1181,6 +1182,46 @@ extend self: Parser {
|
|
|
1181
1182
|
}
|
|
1182
1183
|
result
|
|
1183
1184
|
}
|
|
1185
|
+
|
|
1186
|
+
parseDynamicMember(record: Term): Term {
|
|
1187
|
+
self.skip(LArrowThin)
|
|
1188
|
+
let token = self.skip(LLower)
|
|
1189
|
+
let member = EString(token.at(), "\"" + token.raw() + "\"")
|
|
1190
|
+
if(self.current().rawIs("(")) {
|
|
1191
|
+
let arguments = self.parseFunctionArguments(record.at, False)
|
|
1192
|
+
let effect = self.freshUnificationVariable(record.at)
|
|
1193
|
+
let target = DynamicCall(EField(token.at(), False, record, "call" + arguments.first.size()), False)
|
|
1194
|
+
ECall(record.at, target, effect, [], [
|
|
1195
|
+
Argument(member.at, None, member)
|
|
1196
|
+
...arguments.first
|
|
1197
|
+
], [])
|
|
1198
|
+
} elseIf {self.current().is3(LAssign, LAssignPlus, LAssignMinus)} {
|
|
1199
|
+
let method =
|
|
1200
|
+
if(self.current().is(LAssign)) {
|
|
1201
|
+
self.skip(LAssign)
|
|
1202
|
+
"set"
|
|
1203
|
+
} elseIf {self.current().is(LAssignPlus)} {
|
|
1204
|
+
self.skip(LAssignPlus)
|
|
1205
|
+
"increment"
|
|
1206
|
+
} else {
|
|
1207
|
+
self.skip(LAssignMinus)
|
|
1208
|
+
"decrement"
|
|
1209
|
+
}
|
|
1210
|
+
let value = self.parseTerm()
|
|
1211
|
+
let effect = self.freshUnificationVariable(record.at)
|
|
1212
|
+
let target = DynamicCall(EField(token.at(), False, record, "set"), False)
|
|
1213
|
+
ECall(record.at, target, effect, [], [
|
|
1214
|
+
Argument(member.at, None, member)
|
|
1215
|
+
Argument(value.at, None, value)
|
|
1216
|
+
], [])
|
|
1217
|
+
} else {
|
|
1218
|
+
let effect = self.freshUnificationVariable(record.at)
|
|
1219
|
+
let target = DynamicCall(EField(token.at(), False, record, "get"), False)
|
|
1220
|
+
ECall(record.at, target, effect, [], [
|
|
1221
|
+
Argument(member.at, None, member)
|
|
1222
|
+
], [])
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1184
1225
|
|
|
1185
1226
|
parseAtom(): Term {
|
|
1186
1227
|
if(self.current().is(LString)) {
|
package/compiler/Token.ff
CHANGED
|
@@ -38,6 +38,10 @@ extend token: Token {
|
|
|
38
38
|
token.kind == kind1 || token.kind == kind2 || token.kind == kind3
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
is4(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind, kind4: TokenKind): Bool {
|
|
42
|
+
token.kind == kind1 || token.kind == kind2 || token.kind == kind3 || token.kind == kind4
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
rawIs(value: String): Bool {
|
|
42
46
|
token.stopOffset - token.startOffset == value.size() &&
|
|
43
47
|
token.code.startsWith(value, token.startOffset)
|
|
@@ -78,11 +82,11 @@ data TokenKind {
|
|
|
78
82
|
LPipe
|
|
79
83
|
LColon
|
|
80
84
|
LDotDotDot
|
|
85
|
+
LArrowThin
|
|
81
86
|
LArrowThick
|
|
82
87
|
LAssign
|
|
83
88
|
LAssignPlus
|
|
84
89
|
LAssignMinus
|
|
85
|
-
LAssignLink
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
extend self: TokenKind {
|
|
@@ -109,11 +113,11 @@ extend self: TokenKind {
|
|
|
109
113
|
| LPipe => False
|
|
110
114
|
| LColon => False
|
|
111
115
|
| LDotDotDot => False
|
|
116
|
+
| LArrowThin => False
|
|
112
117
|
| LArrowThick => False
|
|
113
118
|
| LAssign => False
|
|
114
119
|
| LAssignPlus => False
|
|
115
120
|
| LAssignMinus => False
|
|
116
|
-
| LAssignLink => False
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
@@ -139,11 +143,11 @@ extend self: TokenKind {
|
|
|
139
143
|
| LPipe => False
|
|
140
144
|
| LColon => False
|
|
141
145
|
| LDotDotDot => True
|
|
146
|
+
| LArrowThin => False
|
|
142
147
|
| LArrowThick => False
|
|
143
148
|
| LAssign => False
|
|
144
149
|
| LAssignPlus => False
|
|
145
150
|
| LAssignMinus => False
|
|
146
|
-
| LAssignLink => False
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -169,11 +173,11 @@ extend self: TokenKind {
|
|
|
169
173
|
| LPipe => False
|
|
170
174
|
| LColon => False
|
|
171
175
|
| LDotDotDot => False
|
|
176
|
+
| LArrowThin => False
|
|
172
177
|
| LArrowThick => False
|
|
173
178
|
| LAssign => False
|
|
174
179
|
| LAssignPlus => False
|
|
175
180
|
| LAssignMinus => False
|
|
176
|
-
| LAssignLink => False
|
|
177
181
|
}
|
|
178
182
|
}
|
|
179
183
|
|
package/compiler/Tokenizer.ff
CHANGED
|
@@ -226,6 +226,8 @@ tokenize(file: String, code: String, completionAt: Option[Location], attemptFixe
|
|
|
226
226
|
LColon
|
|
227
227
|
} elseIf {i - start == 3 && code.grab(i - 3) == '.' && code.grab(i - 2) == '.' && code.grab(i - 1) == '.'} {
|
|
228
228
|
LDotDotDot
|
|
229
|
+
} elseIf {i - start == 2 && code.grab(i - 2) == '-' && code.grab(i - 1) == '>'} {
|
|
230
|
+
LArrowThin
|
|
229
231
|
} elseIf {i - start == 2 && code.grab(i - 2) == '=' && code.grab(i - 1) == '>'} {
|
|
230
232
|
LArrowThick
|
|
231
233
|
} elseIf {i - start == 1 && code.grab(i - 1) == '='} {
|
|
@@ -234,8 +236,6 @@ tokenize(file: String, code: String, completionAt: Option[Location], attemptFixe
|
|
|
234
236
|
LAssignPlus
|
|
235
237
|
} elseIf {i - start == 2 && code.grab(i - 2) == '-' && code.grab(i - 1) == '='} {
|
|
236
238
|
LAssignMinus
|
|
237
|
-
} elseIf {i - start == 3 && code.grab(i - 3) == ':' && code.grab(i - 2) == ':' && code.grab(i - 1) == '='} {
|
|
238
|
-
LAssignLink
|
|
239
239
|
} else {
|
|
240
240
|
LOperator
|
|
241
241
|
}
|
package/core/JsSystem.ff
CHANGED
|
@@ -5,6 +5,18 @@ extend self: JsSystem {
|
|
|
5
5
|
global(): JsValue
|
|
6
6
|
target js sync "return self_"
|
|
7
7
|
|
|
8
|
+
get(key: String): JsValue
|
|
9
|
+
target js sync "return self_[key_]"
|
|
10
|
+
|
|
11
|
+
set[V: IsJsValue](key: String, value: V): Unit
|
|
12
|
+
target js sync "self_[key_] = value_"
|
|
13
|
+
|
|
14
|
+
increment[V: IsJsValue](key: String, value: V): Unit
|
|
15
|
+
target js sync "self_[key_] += value_"
|
|
16
|
+
|
|
17
|
+
decrement[V: IsJsValue](key: String, value: V): Unit
|
|
18
|
+
target js sync "self_[key_] -= value_"
|
|
19
|
+
|
|
8
20
|
parseJson(json: String): JsValue
|
|
9
21
|
target js sync "return JSON.parse(json_)"
|
|
10
22
|
|
package/core/JsValue.ff
CHANGED
|
@@ -88,6 +88,12 @@ extend self: JsValue {
|
|
|
88
88
|
set[K: IsJsValue, V: IsJsValue](key: K, value: V): Unit
|
|
89
89
|
target js sync "self_[key_] = value_"
|
|
90
90
|
|
|
91
|
+
increment[K: IsJsValue, V: IsJsValue](key: K, value: V): Unit
|
|
92
|
+
target js sync "self_[key_] += value_"
|
|
93
|
+
|
|
94
|
+
decrement[K: IsJsValue, V: IsJsValue](key: K, value: V): Unit
|
|
95
|
+
target js sync "self_[key_] -= value_"
|
|
96
|
+
|
|
91
97
|
delete[K: IsJsValue](key: K): Unit
|
|
92
98
|
target js sync "delete self_[key_]"
|
|
93
99
|
|
package/core/Task.ff
CHANGED
|
@@ -85,6 +85,14 @@ extend self: Task {
|
|
|
85
85
|
readOr(self.channel()) {_ => }.
|
|
86
86
|
timeout(duration) {}
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
mapList[T, R](list: List[T], body: T => R): List[R] {
|
|
90
|
+
self.all(list.map {x => {body(x)}})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
raceList[T, R](list: List[T], body: T => R): R {
|
|
94
|
+
self.race(list.map {x => {body(x)}})
|
|
95
|
+
}
|
|
88
96
|
|
|
89
97
|
all[T](tasks: List[() => T]): List[T] {
|
|
90
98
|
let successChannel = self.channel()
|
package/fireflysite/Guide.ff
CHANGED
|
@@ -92,12 +92,11 @@ data GuideDocument(
|
|
|
92
92
|
document: Document
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
-
render(lux: Lux,
|
|
95
|
+
render(lux: Lux, httpClient: HttpClient, prefix: String, kebab: String, guides: List[Guide], demos: List[Demo]) {
|
|
96
96
|
let guide = guides.find {_.prefix == prefix}.else {guides.grabFirst()}
|
|
97
97
|
let document = guide.documents.find {d =>
|
|
98
98
|
kebabCase(d.heading()) == kebab
|
|
99
99
|
}.else {guide.documents.grabFirst()}
|
|
100
|
-
browser.js().global().get("document").set("title", document.title(guide))
|
|
101
100
|
let guideDocuments = guides.flatMap {guide => guide.documents.map {document =>
|
|
102
101
|
GuideDocument(guide, document)
|
|
103
102
|
}}
|
|
@@ -111,7 +110,7 @@ render(lux: Lux, browser: BrowserSystem, prefix: String, kebab: String, guides:
|
|
|
111
110
|
lux.cssClass(Styles.guideCss)
|
|
112
111
|
lux.add("main") {
|
|
113
112
|
lux.cssClass(Styles.guideMainCss)
|
|
114
|
-
renderDocument(lux,
|
|
113
|
+
renderDocument(lux, httpClient, prefix, document, demos, nextDocument)
|
|
115
114
|
}
|
|
116
115
|
renderTopbar(lux, menu, setMenu)
|
|
117
116
|
lux.add("div") {
|
package/fireflysite/Main.ff
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WebServer from ff:webserver
|
|
2
2
|
import Lux from ff:lux
|
|
3
|
+
import Css from ff:lux
|
|
3
4
|
import Guide
|
|
4
5
|
import GettingStarted
|
|
5
6
|
import ReferenceAll
|
|
@@ -18,18 +19,6 @@ nodeMain(system: NodeSystem): Unit {
|
|
|
18
19
|
let path = request.readPath()
|
|
19
20
|
let segments = path.split('/').filter {s => s != "" && s != "." && s != ".."}
|
|
20
21
|
segments.{
|
|
21
|
-
| ["reference", ...] =>
|
|
22
|
-
serveGuideHtml("Firefly Reference", request)
|
|
23
|
-
| ["getting-started", ...] =>
|
|
24
|
-
serveGuideHtml("Firefly Getting Started", request)
|
|
25
|
-
| ["examples", ...] =>
|
|
26
|
-
serveGuideHtml("Firefly Examples", request)
|
|
27
|
-
| ["packages", ...] =>
|
|
28
|
-
serveGuideHtml("Firefly Packages", request)
|
|
29
|
-
| ["community", ...] =>
|
|
30
|
-
serveGuideHtml("Firefly Community", request)
|
|
31
|
-
| ["front", ...] =>
|
|
32
|
-
serveGuideHtml("Firefly", request)
|
|
33
22
|
| ["js", ...] =>
|
|
34
23
|
let asset = if(path == "/js/ff/fireflysite/Main.mjs" && system.assets().exists("/js/Main.bundle.js")) {
|
|
35
24
|
"/js/Main.bundle.js"
|
|
@@ -44,6 +33,7 @@ nodeMain(system: NodeSystem): Unit {
|
|
|
44
33
|
| ["assets", "font", "FireflySans.ttf"] =>
|
|
45
34
|
serveAsset(system, cacheSalt, request, "font/ttf", "/assets/font/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf")
|
|
46
35
|
| ["assets", ...rest] => serveAssets(system, cacheSalt, request, rest)
|
|
36
|
+
| _ {serveGuide(system, request)} =>
|
|
47
37
|
| _ =>
|
|
48
38
|
let parameters = if(request.readRawQueryString().size() == 0) {""} else {
|
|
49
39
|
"?" + request.readRawQueryString()
|
|
@@ -54,21 +44,40 @@ nodeMain(system: NodeSystem): Unit {
|
|
|
54
44
|
}
|
|
55
45
|
}
|
|
56
46
|
|
|
47
|
+
guides = [
|
|
48
|
+
Guide("/front/", [FrontPage.new()])
|
|
49
|
+
Guide("/getting-started/", [GettingStarted.new()])
|
|
50
|
+
Guide("/examples/", ExamplesOverview.mock())
|
|
51
|
+
Guide("/reference/", ReferenceAll.mock())
|
|
52
|
+
Guide("/packages/", [PackagesOverview.new()])
|
|
53
|
+
Guide("/community/", [CommunityOverview.new()])
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
serveGuide(system: NodeSystem, request: WebRequest[WebResponse]): Bool {
|
|
57
|
+
let demos = ExamplesOverview.demos()
|
|
58
|
+
mutable served = False
|
|
59
|
+
guides.each {guide =>
|
|
60
|
+
if(request.readPath().startsWith(guide.prefix)):
|
|
61
|
+
let kebab = request.readPath().dropFirst(guide.prefix.size())
|
|
62
|
+
let htmlAndCss = Lux.renderToString(system) {lux =>
|
|
63
|
+
Guide.render(lux, system.httpClient(), guide.prefix, kebab, guides, demos)
|
|
64
|
+
}
|
|
65
|
+
let title = guide.documents.find {d =>
|
|
66
|
+
Guide.kebabCase(d.heading()) == kebab
|
|
67
|
+
}.map {_.title(guide)}.else {guide.title()}
|
|
68
|
+
serveGuideHtml(title, htmlAndCss.first, htmlAndCss.second, request)
|
|
69
|
+
served = True
|
|
70
|
+
}
|
|
71
|
+
served
|
|
72
|
+
}
|
|
73
|
+
|
|
57
74
|
browserMain(system: BrowserSystem): Unit {
|
|
58
75
|
let demos = ExamplesOverview.demos()
|
|
59
|
-
let guides = [
|
|
60
|
-
Guide("/front/", [FrontPage.new()])
|
|
61
|
-
Guide("/getting-started/", [GettingStarted.new()])
|
|
62
|
-
Guide("/examples/", ExamplesOverview.mock())
|
|
63
|
-
Guide("/reference/", ReferenceAll.mock())
|
|
64
|
-
Guide("/packages/", [PackagesOverview.new()])
|
|
65
|
-
Guide("/community/", [CommunityOverview.new()])
|
|
66
|
-
]
|
|
67
76
|
guides.collect {guide =>
|
|
68
77
|
if(system.urlPath().startsWith(guide.prefix)):
|
|
69
78
|
Lux.renderById(system, "main") {lux =>
|
|
70
79
|
let kebab = system.urlPath().dropFirst(guide.prefix.size())
|
|
71
|
-
Guide.render(lux, system, guide.prefix, kebab, guides, demos)
|
|
80
|
+
Guide.render(lux, system.httpClient(), guide.prefix, kebab, guides, demos)
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
}
|
|
@@ -119,10 +128,11 @@ serveAsset(system: NodeSystem, salt: Buffer, request: WebRequest[WebResponse], c
|
|
|
119
128
|
}
|
|
120
129
|
}
|
|
121
130
|
|
|
122
|
-
serveGuideHtml(title: String, request: WebRequest[WebResponse]): Unit {
|
|
131
|
+
serveGuideHtml(title: String, contentHtml: String, styleTags: String, request: WebRequest[WebResponse]): Unit {
|
|
123
132
|
request.writeHeader("Content-Type", "text/html; charset=UTF-8")
|
|
124
133
|
request.writeText("<!doctype html>")
|
|
125
134
|
request.writeText("<html lang='en' style='background-color: #ffffff; color: #333333; width: 100%; height: 100%; color-scheme: light;'>")
|
|
135
|
+
request.writeText("<script type='module' src='/js/ff/fireflysite/Main.mjs'></script>")
|
|
126
136
|
request.writeText("<head>")
|
|
127
137
|
request.writeText("<title>" + title + "</title>")
|
|
128
138
|
request.writeText("<meta name='viewport' content='width=device-width, initial-scale=1.0'>")
|
|
@@ -132,10 +142,10 @@ serveGuideHtml(title: String, request: WebRequest[WebResponse]): Unit {
|
|
|
132
142
|
request.writeText("<link rel='preload' href='/assets/image/firefly-logo-yellow.png' as='image'>")
|
|
133
143
|
request.writeText("<style>@font-face { font-family: 'Firefly Mono'; font-display: fallback; src: url('/assets/font/FireflyMono.ttf'); unicode-range: U+000-5FF; }</style>")
|
|
134
144
|
request.writeText("<style>@font-face { font-family: 'Firefly Sans'; font-display: fallback; src: url('/assets/font/FireflySans.ttf'); unicode-range: U+000-5FF; }</style>")
|
|
145
|
+
request.writeText(styleTags)
|
|
135
146
|
request.writeText("</head>")
|
|
136
147
|
request.writeText("<body style='margin: 0; padding: 0; width: 100%; height: 100%; touch-action: manipulation;'>")
|
|
137
|
-
request.writeText("<div id='main'
|
|
138
|
-
request.writeText("<script type='module' src='/js/ff/fireflysite/Main.mjs'></script>")
|
|
148
|
+
request.writeText("<div id='main'>" + contentHtml + "</div>")
|
|
139
149
|
request.writeText("</body>")
|
|
140
150
|
request.writeText("</html>")
|
|
141
151
|
}
|
|
@@ -12,8 +12,7 @@ mock(): List[Document] {
|
|
|
12
12
|
UnfetchedDocument("Pattern matching")
|
|
13
13
|
UnfetchedDocument("Traits and instances")
|
|
14
14
|
UnfetchedDocument("Exceptions")
|
|
15
|
-
UnfetchedDocument("JavaScript interop")
|
|
16
|
-
UnfetchedDocument("Async I/O")
|
|
17
15
|
UnfetchedDocument("Structured concurrency")
|
|
16
|
+
UnfetchedDocument("JavaScript interop")
|
|
18
17
|
]
|
|
19
18
|
}
|
package/fireflysite/Test.ff
CHANGED
|
@@ -19,6 +19,14 @@ nodeMain(system: NodeSystem) {
|
|
|
19
19
|
|
|
20
20
|
Log.show([1, 2].map(increment))
|
|
21
21
|
|
|
22
|
+
Log.show(p(42))
|
|
23
|
+
let f2 = {42}
|
|
24
|
+
Log.show(f2())
|
|
25
|
+
let pairs = {Pair(_, {_})}
|
|
26
|
+
let pp = pairs(42)
|
|
27
|
+
let foo = {{_ + 1}(_)}
|
|
28
|
+
Log.show(foo(1))
|
|
29
|
+
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
factorial(n: Int): Int {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Emitted JavaScript
|
|
2
|
+
|
|
3
|
+
While most Firefly code maps directly to the JavaScript equivalent, there are two notable exceptions:
|
|
4
|
+
|
|
5
|
+
* I/O appears to be blocking, but compiles down to JavaScript `async`/`await`.
|
|
6
|
+
* Methods are resolved statically in Firefly and become top level functions in JavaScript.
|
|
7
|
+
|
|
8
|
+
In addition, pattern matching doesn't have a direct equivalent in JavaScript, and neither does traits.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Example
|
|
12
|
+
|
|
13
|
+
Consider the following main function:
|
|
14
|
+
|
|
15
|
+
```firefly
|
|
16
|
+
nodeMain(system: NodeSystem) {
|
|
17
|
+
|
|
18
|
+
let files = ["a.txt", "b.txt"]
|
|
19
|
+
|
|
20
|
+
let contents = files.map {file =>
|
|
21
|
+
system.path(file).readText()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let upper = contents.map {content =>
|
|
25
|
+
content.upper()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
system.writeLine("Result: " + upper.join(""))
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The JavaScript that's emitted looks roughly like this:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
export async function nodeMain$(system) {
|
|
37
|
+
|
|
38
|
+
const files = ["a.txt", "b.txt"]
|
|
39
|
+
|
|
40
|
+
const contents = await List_map$(files, async file => {
|
|
41
|
+
return await Path_readText$(await NodeSystem_path$(system, file))
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const upper = List_map(contents, content => {
|
|
45
|
+
return String_upper(content)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
NodeSystem_writeLine$("Result: " + String_join(upper, ""))
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In JavaScript, `nodeMain` becomes an `async` function and gets the `$` suffix to distinguish it from a synchronous function.
|
|
54
|
+
|
|
55
|
+
The `let` keyword in Firefly corresponds to the `const` keyword in JavaScript, and Firefly list literals become JavaScript array literals.
|
|
56
|
+
|
|
57
|
+
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`.
|
|
58
|
+
A static analysis is performed to decide which version to call.
|
|
59
|
+
|
|
60
|
+
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,
|
|
61
|
+
the analysis picks the asynchronous version `List_map$` and uses the `await` keyword.
|
|
62
|
+
|
|
63
|
+
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`.
|
|
64
|
+
|
|
65
|
+
This static analysis is necessarily conservative, and may occasionally call the asynchronous version of a function where the synchrhonous version would suffice.
|
|
66
|
+
When using the VSCode extension, the hover information for a call will note if the call is asynchronous.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Exceptions
|
|
2
|
+
|
|
3
|
+
Exceptions allow a program to transfer control from the point of error to a designated exception handler, separating error-handling logic from regular program flow.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Throwing exceptions
|
|
7
|
+
|
|
8
|
+
Exceptions are thrown using the `throw` function. Example:
|
|
9
|
+
|
|
10
|
+
```firefly
|
|
11
|
+
grabOption[T](option: Option[T]): T {
|
|
12
|
+
| Some(v) => v
|
|
13
|
+
| None => throw(GrabException())
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
In this example, if the argument is `Some(v)`, then `v` is returned. Otherwise, a `GrabException` is thrown.
|
|
18
|
+
|
|
19
|
+
Any type declared with the `data` or `newtype` keyword can be thrown as an exception, since the type will have an instance for the `HasAnyTag` trait.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Catching exceptions
|
|
23
|
+
|
|
24
|
+
When an exception is thrown, it propagates up the call chain to the nearest `try`, where it can be caught:
|
|
25
|
+
|
|
26
|
+
```firefly
|
|
27
|
+
try {
|
|
28
|
+
grabOption(None)
|
|
29
|
+
} catch {| GrabException, error =>
|
|
30
|
+
Log.trace("A GrabException occurred")
|
|
31
|
+
error.rethrow()
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The first argument passed to `catch` is the thrown value, and the second parameter contains the stack trace.
|
|
36
|
+
The `rethrow()` method throws the exception again without altering the stack trace.
|
|
37
|
+
It is used when the exception is caught where it can't be fully handled.
|
|
38
|
+
|
|
39
|
+
If the exception reaches the top of the call stack, the exception value and the stack trace will be printed.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Catching any exception
|
|
43
|
+
|
|
44
|
+
It is also posssible to catch any exception, regardless of its type, using the `catchAny` method:
|
|
45
|
+
|
|
46
|
+
```firefly
|
|
47
|
+
try {
|
|
48
|
+
let result = fragileOperation()
|
|
49
|
+
Some(result)
|
|
50
|
+
} catchAny {error =>
|
|
51
|
+
None
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Cleaning up
|
|
57
|
+
|
|
58
|
+
Cleanup often needs to happen whether or not an exception is thrown.
|
|
59
|
+
This is what the `finally` method guarantees:
|
|
60
|
+
|
|
61
|
+
```firefly
|
|
62
|
+
let fileHandle = path.readHandle()
|
|
63
|
+
try {
|
|
64
|
+
process(fileHandle)
|
|
65
|
+
} finally {
|
|
66
|
+
fileHandle.close()
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If the program is terminated abnormally (force closed, power is lost, etc.) there is no guarantee that `finally` will get called.
|
|
71
|
+
In most environments, the operating system will occasionally terminate the program abnormally, so programs should be designed such that they can recover from abnormal termination.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Catching multiple exceptions
|
|
75
|
+
|
|
76
|
+
The `try` function returns a value of type `Try[T]` that is defined as follows:
|
|
77
|
+
|
|
78
|
+
```firefly
|
|
79
|
+
data Try[T] {
|
|
80
|
+
Success(value: T)
|
|
81
|
+
Failure(error: Error)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `catch`, `catchAny` and `finally` methods are defined on this type. However, since they don't return a `Try[T]` value, they can't be chained.
|
|
86
|
+
|
|
87
|
+
However, the alternative `tryCatch`, `tryCatchAny` and `tryFinally` methods do return a `Try[T]` value, and can thus be chained:
|
|
88
|
+
|
|
89
|
+
```firefly
|
|
90
|
+
try {
|
|
91
|
+
doSomething()
|
|
92
|
+
} tryCatch {| GrabException, error =>
|
|
93
|
+
reportSpecificError()
|
|
94
|
+
} tryCatchAny {error =>
|
|
95
|
+
reportGeneralError()
|
|
96
|
+
} finally {
|
|
97
|
+
performCleanup()
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The last method in the chain here is `finally`. If it was `tryFinally`, a value of type `Try[T]` would be returned.
|