firefly-compiler 0.4.77 → 0.4.79

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.
Files changed (135) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +153 -153
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +257 -257
  6. package/compiler/Compiler.ff +227 -227
  7. package/compiler/Dependencies.ff +187 -187
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/JsEmitter.ff +946 -946
  10. package/compiler/LspHook.ff +202 -202
  11. package/compiler/ModuleCache.ff +178 -178
  12. package/compiler/Workspace.ff +88 -88
  13. package/core/.firefly/include/package-lock.json +564 -394
  14. package/core/.firefly/include/package.json +5 -5
  15. package/core/.firefly/include/prepare.sh +1 -1
  16. package/core/.firefly/package.ff +2 -2
  17. package/core/Array.ff +265 -265
  18. package/core/Atomic.ff +64 -64
  19. package/core/Box.ff +7 -7
  20. package/core/BrowserSystem.ff +40 -40
  21. package/core/BuildSystem.ff +148 -148
  22. package/core/Crypto.ff +96 -96
  23. package/core/Equal.ff +36 -36
  24. package/core/HttpClient.ff +148 -148
  25. package/core/JsSystem.ff +69 -69
  26. package/core/Json.ff +434 -434
  27. package/core/List.ff +486 -486
  28. package/core/Lock.ff +144 -144
  29. package/core/NodeSystem.ff +216 -216
  30. package/core/Ordering.ff +161 -161
  31. package/core/Path.ff +401 -401
  32. package/core/Random.ff +134 -134
  33. package/core/RbMap.ff +216 -216
  34. package/core/Show.ff +43 -43
  35. package/core/SourceLocation.ff +68 -68
  36. package/core/String.ff +16 -0
  37. package/core/Task.ff +141 -141
  38. package/experimental/benchmarks/ListGrab.ff +23 -23
  39. package/experimental/benchmarks/ListGrab.java +55 -55
  40. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  41. package/experimental/benchmarks/Pyrotek45.java +64 -64
  42. package/experimental/bidirectional/Bidi.ff +88 -88
  43. package/experimental/random/Index.ff +53 -53
  44. package/experimental/random/Process.ff +120 -120
  45. package/experimental/random/Scrape.ff +51 -51
  46. package/experimental/random/Symbols.ff +73 -73
  47. package/experimental/random/Tensor.ff +52 -52
  48. package/experimental/random/Units.ff +36 -36
  49. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -38
  50. package/experimental/s3/S3TestPut.ff +16 -15
  51. package/experimental/tests/TestJson.ff +26 -26
  52. package/firefly.sh +0 -0
  53. package/fireflysite/.firefly/package.ff +4 -0
  54. package/fireflysite/CommunityOverview.ff +20 -0
  55. package/fireflysite/CountingButtonDemo.ff +58 -0
  56. package/fireflysite/DocumentParser.ff +218 -0
  57. package/fireflysite/ExamplesOverview.ff +40 -0
  58. package/fireflysite/FrontPage.ff +360 -0
  59. package/fireflysite/Guide.ff +411 -0
  60. package/fireflysite/GuideAll.ff +21 -0
  61. package/fireflysite/GuideBaseTypes.ff +168 -0
  62. package/fireflysite/GuideControlFlow.ff +212 -0
  63. package/fireflysite/GuideIntroduction.ff +52 -0
  64. package/fireflysite/Main.ff +137 -15
  65. package/fireflysite/MatchingPasswordsDemo.ff +82 -0
  66. package/fireflysite/PackagesOverview.ff +49 -0
  67. package/fireflysite/PostgresqlDemo.ff +34 -0
  68. package/fireflysite/Styles.ff +495 -0
  69. package/fireflysite/assets/NotoSansMono-Regular.ttf +0 -0
  70. package/fireflysite/assets/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf +0 -0
  71. package/fireflysite/assets/autocomplete-small.png +0 -0
  72. package/fireflysite/assets/autocomplete.png +0 -0
  73. package/fireflysite/assets/edit-time-error.png +0 -0
  74. package/fireflysite/assets/firefly-logo-notext.png +0 -0
  75. package/fireflysite/assets/firefly-logo-yellow.png +0 -0
  76. package/fireflysite/assets/markdown/ControlFlow.md +136 -0
  77. package/fireflysite/assets/markdown/Example.md +78 -0
  78. package/lsp/.firefly/package.ff +1 -1
  79. package/lsp/CompletionHandler.ff +828 -828
  80. package/lsp/Handler.ff +714 -714
  81. package/lsp/HoverHandler.ff +79 -79
  82. package/lsp/LanguageServer.ff +272 -272
  83. package/lsp/SignatureHelpHandler.ff +55 -55
  84. package/lsp/SymbolHandler.ff +181 -181
  85. package/lsp/TestReferences.ff +17 -17
  86. package/lsp/TestReferencesCase.ff +7 -7
  87. package/lsp/stderr.txt +1 -1
  88. package/lsp/stdout.txt +34 -34
  89. package/lux/.firefly/package.ff +1 -1
  90. package/lux/Css.ff +648 -648
  91. package/lux/CssTest.ff +48 -48
  92. package/lux/Lux.ff +487 -487
  93. package/lux/LuxEvent.ff +116 -116
  94. package/lux/Main.ff +123 -123
  95. package/lux/Main2.ff +143 -144
  96. package/output/js/ff/compiler/Builder.mjs +43 -43
  97. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  98. package/output/js/ff/core/Array.mjs +59 -59
  99. package/output/js/ff/core/Atomic.mjs +36 -36
  100. package/output/js/ff/core/BrowserSystem.mjs +11 -11
  101. package/output/js/ff/core/BuildSystem.mjs +30 -30
  102. package/output/js/ff/core/Crypto.mjs +40 -40
  103. package/output/js/ff/core/HttpClient.mjs +56 -56
  104. package/output/js/ff/core/Json.mjs +147 -147
  105. package/output/js/ff/core/List.mjs +50 -50
  106. package/output/js/ff/core/Lock.mjs +97 -97
  107. package/output/js/ff/core/NodeSystem.mjs +83 -83
  108. package/output/js/ff/core/Ordering.mjs +8 -8
  109. package/output/js/ff/core/Path.mjs +231 -231
  110. package/output/js/ff/core/Random.mjs +56 -56
  111. package/output/js/ff/core/String.mjs +20 -0
  112. package/output/js/ff/core/Task.mjs +31 -31
  113. package/package.json +1 -1
  114. package/rpc/.firefly/package.ff +1 -1
  115. package/rpc/Rpc.ff +70 -70
  116. package/s3/.firefly/package.ff +1 -1
  117. package/s3/S3.ff +94 -94
  118. package/unsafejs/UnsafeJs.ff +19 -19
  119. package/vscode/LICENSE.txt +21 -21
  120. package/vscode/Prepublish.ff +15 -15
  121. package/vscode/README.md +16 -16
  122. package/vscode/client/package.json +22 -22
  123. package/vscode/client/src/extension.ts +104 -104
  124. package/vscode/icons/firefly-icon.svg +10 -10
  125. package/vscode/language-configuration.json +61 -61
  126. package/vscode/package-lock.json +3623 -3623
  127. package/vscode/package.json +15 -1
  128. package/vscode/snippets.json +241 -241
  129. package/vscode/syntaxes/firefly-markdown-injection.json +45 -0
  130. package/webserver/.firefly/include/package-lock.json +22 -16
  131. package/webserver/.firefly/include/package.json +5 -5
  132. package/webserver/.firefly/package.ff +2 -2
  133. package/webserver/WebServer.ff +685 -685
  134. package/websocket/.firefly/package.ff +1 -1
  135. package/websocket/WebSocket.ff +131 -131
@@ -1,946 +1,946 @@
1
- import Syntax
2
- import Patterns
3
- import JsImporter
4
-
5
- capability JsEmitter(
6
- otherModules: Map[String, Module]
7
- jsImporter: JsImporter
8
- emitTarget: EmitTarget
9
- isMainModule: Bool
10
- compilerModulePath: Option[Path]
11
- packagePair: PackagePair
12
- moduleName: String
13
- mutable emittingAsync: Bool
14
- mutable tailCallUsed: Bool
15
- )
16
-
17
- data EmitTarget {
18
- EmitNode
19
- EmitBrowser
20
- EmitBuild
21
- EmitExecutable
22
- }
23
-
24
- new(
25
- otherModules: List[Module]
26
- emitTarget: EmitTarget
27
- isMainModule: Bool
28
- compilerModulePath: Option[Path]
29
- packagePair: PackagePair
30
- moduleName: String
31
- ): JsEmitter {
32
- JsEmitter(
33
- otherModules = otherModules.map {m =>
34
- let moduleName = m.packagePair.groupName() + "/" + m.file.dropLast(3)
35
- Pair(moduleName, m)
36
- }.toMap()
37
- jsImporter = JsImporter.new()
38
- emitTarget = emitTarget
39
- isMainModule = isMainModule
40
- compilerModulePath = compilerModulePath
41
- packagePair = packagePair
42
- moduleName = moduleName
43
- emittingAsync = False
44
- tailCallUsed = False
45
- )
46
- }
47
-
48
- fail[T](at: Location, message: String): T {
49
- panic(message + " " + at.show())
50
- }
51
-
52
- extend self: JsEmitter {
53
-
54
- emitModule(packagePair: PackagePair, module: Module): String {
55
- let selfImport =
56
- "import * as " + packagePair.groupName("_") + "_" + module.file.dropLast(3) + " " +
57
- "from \"../../" + packagePair.groupName("/") + "/" + module.file.dropLast(3) + ".mjs\""
58
- let imports = [
59
- self.compilerModulePath.map {"import * as $firefly_compiler from '" + _.url() + "'"}.toList()
60
- module.imports.sortBy {i => Pair(i.package, i.file) }.map {self.emitImportDefinition(_)}
61
- ].flatten()
62
- let parts = [
63
- if(imports.any {_ == selfImport}) {imports} else {[selfImport, ...imports]}
64
- module.types.map {self.emitTypeDefinition(_)}
65
- module.lets.map {"export " + self.emitLetDefinition(_, False, False)}
66
- module.functions.map {"export " + self.emitFunctionDefinition(_, False)}
67
- self.withEmittingAsync {module.functions.map {"export " + self.emitFunctionDefinition(_, True)}}
68
- module.extends.map {self.emitExtendsDefinition(_)}
69
- module.instances.map {self.emitInstanceDefinition(_)}
70
- if(self.isMainModule) {
71
- self.emitRun(module.functions, packagePair, packagePair.group == "ff" && packagePair.name == "compiler")
72
- } else {[]}
73
- ]
74
- let ignoreJsImports = if(
75
- self.emitTarget == EmitExecutable &&
76
- packagePair.group == "ff" &&
77
- packagePair.name == "core"
78
- ) {["esbuild"]} else {[]}
79
- let jsImports = self.jsImporter.generateImports(ignoreJsImports.toSet())
80
- [jsImports, ...parts].map {_.join("\n\n")}.join("\n\n") + "\n"
81
- }
82
-
83
- withEmittingAsync[T](body: () => T): T {
84
- try {
85
- self.emittingAsync = True
86
- body()
87
- } finally {
88
- self.emittingAsync = False
89
- } grab()
90
- }
91
-
92
- emitRun(functions: List[DFunction], mainPackagePair: PackagePair, bootstrapping: Bool): List[String] {
93
- let buildMainFunction = functions.find {_.signature.name == "buildMain"}.filter {_ =>
94
- self.emitTarget != EmitBrowser && self.emitTarget != EmitExecutable
95
- }
96
- let willRunOnNode = self.emitTarget != EmitBrowser
97
- let targetMain = if(willRunOnNode) {"nodeMain"} else {"browserMain"}
98
- let mainFunction =
99
- functions.find {_.signature.name == targetMain}.orElse {functions.find {_.signature.name == "main"}}
100
- mainFunction.map {_.signature.name}.map {mainName => [[
101
- "export async function $run$(fireflyPath_, arguments_) {"
102
- "Error.stackTraceLimit = 50"
103
- "const $task = {controller: new AbortController(), subtasks: new Set(), promise: new Promise(() => {}), started: performance.now() * 0.001}"
104
- ...if(self.emitTarget != EmitBrowser) {[
105
- "let interval = setInterval(() => {}, 24 * 60 * 60 * 1000)" // To prevent deadlocks from exiting node
106
- ]} else {[]}
107
- "let system = {"
108
- "task_: $task,"
109
- "array_: arguments_,"
110
- "fireflyPath_: fireflyPath_,"
111
- "mainPackagePair_: {group_: \"" + mainPackagePair.group + "\", name_: \"" + mainPackagePair.name + "\"},"
112
- "executableMode_: " + if(self.emitTarget == EmitExecutable) {"true"} else {"false"} + ","
113
- "buildMode_: " + if(self.emitTarget == EmitBuild) {"true"} else {"false"}
114
- "}"
115
- "try {"
116
- ...if(!buildMainFunction.isEmpty()) {[
117
- "await buildMain_$(system, $task)"]
118
- } else {[]}
119
- ...if(self.emitTarget != EmitBuild) {[
120
- "await " + mainName + "_$(system, $task)"
121
- ]} else {[]}
122
- ...if(self.emitTarget == EmitBuild) {[
123
- "await $firefly_compiler.internalCreateExecutable_$(system, '.firefly/output/executable/Main.bundle.js', '.firefly/output', ['host'], system.assets_, $task)"
124
- ]} else {[]}
125
- "} finally {"
126
- ...if(self.emitTarget != EmitBrowser) {[
127
- "ff_core_Task.Task_abort$($task)"
128
- "clearInterval(interval)"
129
- ]} else {[]}
130
- "}"
131
- "}"
132
- ...self.emitTarget.{
133
- | EmitBrowser => [
134
- "queueMicrotask(async () => {"
135
- "await $run$(null, [])"
136
- "})"
137
- ]
138
- | EmitNode {bootstrapping} => [
139
- "import * as path from 'node:path'"
140
- "queueMicrotask(async () => {"
141
- "let fireflyPath_ = path.dirname(path.dirname(path.dirname(path.dirname(path.dirname(process.argv[1])))))"
142
- "await $run$(fireflyPath_, process.argv.slice(2))"
143
- "})"
144
- ]
145
- | EmitExecutable => [
146
- "queueMicrotask(async () => {"
147
- "await $run$(null, process.argv.slice(2))"
148
- "})"
149
- ]
150
- | _ => []
151
- }
152
- ].join("\n")]}.else {[]}
153
- }
154
-
155
- emitImportDefinition(definition: DImport): String {
156
- "import * as " + definition.package.group + "_" + definition.package.name + "_" + definition.file + " " +
157
- "from \"../../" + definition.package.group + "/" + definition.package.name + "/" + definition.file + ".mjs\""
158
- }
159
-
160
- emitLetDefinition(definition: DLet, mutable: Bool, async: Bool): String {
161
- let mutability = if(mutable) {"let"} else {"const"}
162
- let valueCode = self.emitTerm(definition.value, async)
163
- mutability + " " + escapeKeyword(definition.name) + " = " + valueCode + ";"
164
- }
165
-
166
- emitExtendsDefinition(definition: DExtend): String {
167
- let typeName = extractTypeName(definition.type).reverse().takeWhile {_ != '.'}.reverse()
168
- let methods = definition.methods.map {method =>
169
- method.DFunction(
170
- signature = method.signature.Signature(
171
- name = typeName + "_" + method.signature.name
172
- )
173
- )
174
- }
175
- let syncMethods = methods.map {"export " + self.emitFunctionDefinition(_, False)}
176
- let asyncMethods = self.withEmittingAsync {methods.map {"export " + self.emitFunctionDefinition(_, True)}}
177
- [...syncMethods, ...asyncMethods].join("\n\n")
178
- }
179
-
180
- emitInstanceDefinition(definition: DInstance): String {
181
- let name = makeDictionaryName(definition.traitName, firstTypeName(definition.typeArguments))
182
- let methods = definition.methods.map {self.emitFunctionDefinition(_, False)}.map {_.dropFirst("function ".size())} // TODO
183
- let asyncMethods = self.withEmittingAsync {
184
- definition.methods.map {self.emitFunctionDefinition(_, True)}.map {"async " + _.dropFirst("async function ".size())} // TODO
185
- }
186
- let body = "{\n" + [...methods, ...asyncMethods].join(",\n") + "\n}"
187
- definition.constraints.{
188
- | [] =>
189
- "export const " + name + " = " + body + ";"
190
- | constraints =>
191
- let dictionaries = constraints.map {c =>
192
- makeDictionaryName(c.name, firstTypeName(c.generics))
193
- }
194
- "export function " + name + "(" + dictionaries.join(", ") + ") { return " + body + "}"
195
- }
196
- }
197
-
198
- emitFunctionDefinition(definition: DFunction, async: Bool, suffix: String = ""): String {
199
- let signature = self.emitSignature(definition.signature, async, suffix)
200
- Pair(async, definition.body).{
201
- | Pair(False, ForeignTarget(None, _)) =>
202
- signature + " {\nthrow new Error('Function " + definition.signature.name + " is missing on this target in sync context.');\n}"
203
- | Pair(True, ForeignTarget(_, None)) =>
204
- signature + " {\nthrow new Error('Function " + definition.signature.name + " is missing on this target in async context.');\n}"
205
- | Pair(False, ForeignTarget(Some(code), _)) =>
206
- signature + " {\n" + self.jsImporter.process(definition.at, code) + "\n}"
207
- | Pair(True, ForeignTarget(_, Some(code))) =>
208
- signature + " {\n" + self.jsImporter.process(definition.at, code) + "\n}"
209
- | Pair(_, FireflyTarget(lambda)) => lambda.{
210
- | Lambda(_, effect, [matchCase]) {
211
- matchCase.patterns.all {
212
- | PVariable(_, None) => True
213
- | _ => False
214
- }
215
- } =>
216
- let body = self.emitTailCall {self.emitStatements(matchCase.body, True, async)}
217
- signature + " {\n" + body + "\n}"
218
- | Lambda(_, effect, cases) =>
219
- Patterns.convertAndCheck(self.otherModules, cases)
220
- let escapedArguments = definition.signature.parameters.map {_.name + "_a"}
221
- let shadowingWorkaround = definition.signature.parameters.map {p =>
222
- "const " + p.name + "_a = " + escapeKeyword(p.name) + ";"
223
- }.join("\n")
224
- let body = self.emitTailCall {
225
- let casesString = cases.map {
226
- self.emitCase(escapedArguments, _, [], [], True, True, async)
227
- }.join("\n")
228
- shadowingWorkaround + "\n" + casesString
229
- }
230
- signature + " {\n" + body + "\n}"
231
- }
232
- }
233
- }
234
-
235
- emitTailCall(body: () => String): String {
236
- let outerTailCallUsed = self.tailCallUsed
237
- self.tailCallUsed = False
238
- let result = body()
239
- let tailCallUsed = self.tailCallUsed
240
- self.tailCallUsed = outerTailCallUsed
241
- if(tailCallUsed) {
242
- "_tailcall: for(;;) {\n" + result + "\nreturn\n}"
243
- } else {
244
- result
245
- }
246
- }
247
-
248
- emitSignature(signature: Signature, async: Bool, suffix: String = ""): String {
249
- let parameterStrings = signature.parameters.map {self.emitParameter(_, async)}
250
- let dictionaryStrings = signature.constraints.map {c =>
251
- makeDictionaryName(c.name, firstTypeName(c.generics))
252
- }
253
- let controller = if(async) {["$task"]} else {[]}
254
- let parameters = "(" + [...parameterStrings, ...dictionaryStrings, ...controller].join(", ") + ")"
255
- let prefix = if(async) {"async "} else {""}
256
- let asyncSuffix = if(async) {"$"} else {""}
257
- prefix + "function " + escapeKeyword(signature.name) + suffix + asyncSuffix + parameters
258
- }
259
-
260
- emitParameter(parameter: Parameter, async: Bool): String {
261
- let defaultValue = parameter.default.map {" = " + self.emitTerm(_, async) }.else {""}
262
- escapeKeyword(parameter.name) + defaultValue
263
- }
264
-
265
- emitTypeDefinition(definition: DType): String {
266
- if(definition.newtype) {"// newtype " + definition.name} else:
267
- "// type " + definition.name + "\n" +
268
- definition.variants.map {self.emitVariantDefinition(definition, _)}.join("\n")
269
- }
270
-
271
- emitVariantDefinition(typeDefinition: DType, definition: Variant): String {
272
- let allFields = [...typeDefinition.commonFields, ...definition.fields]
273
- let fields = allFields.map {escapeKeyword(_.name)}.join(", ")
274
- if(allFields.isEmpty()) {
275
- "const " + definition.name + "$ = {" + definition.name + ": true};\n" +
276
- "export function " + definition.name + "(" + fields + ") {\n" +
277
- "return " + definition.name + "$;\n" +
278
- "}"
279
- } elseIf { typeDefinition.variants.size() == 1 } {
280
- "export function " + definition.name + "(" + fields + ") {\n" +
281
- "return {" + fields + "};\n" +
282
- "}"
283
- } else {
284
- "export function " + definition.name + "(" + fields + ") {\n" +
285
- "return {" + definition.name + ": true, " + fields + "};\n" +
286
- "}"
287
- }
288
- }
289
-
290
- emitTerm(term: Term, async: Bool): String {term.{
291
- | EString(at, value) {value.startsWith("\"\"\"")} =>
292
- "`" + value.dropFirst(3).dropLast(3).replace("`", "\\`") + "`" // TODO: Fix escaping
293
- | EString(at, value) => value
294
- | EChar(at, value) => charLiteralToNumber(value)
295
- | EInt(at, value) => value
296
- | EFloat(at, value) => value
297
- | EVariable(at, name) => escapeResolved(name)
298
- | EList(at, _, items) =>
299
- self.emitList(items, async)
300
- | EVariant(at, "ff:core/Bool.False", _, _) =>
301
- "false"
302
- | EVariant(at, "ff:core/Bool.True", _, _) =>
303
- "true"
304
- | EVariant(at, "ff:core/Unit.Unit", _, _) =>
305
- "(void 0)"
306
- | EVariant(at, name, _, arguments) =>
307
- let argumentsString = arguments.toList().flatten().map {self.emitArgument(at, _, async)}.join(", ")
308
- let newtype = self.processVariant(name)
309
- if(newtype) {argumentsString} else:
310
- escapeResolved(name) + "(" + argumentsString + ")"
311
- | EVariantIs(at, "ff:core/Bool.False", _) =>
312
- "function(_v) { return !_v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
313
- | EVariantIs(at, "ff:core/Bool.True", _) =>
314
- "function(_v) { return _v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
315
- | EVariantIs(at, "ff:core/Unit.Unit", _) =>
316
- "function(_v) { return ff_core_Option.Some(_v); }"
317
- | EVariantIs(at, name, _) =>
318
- let n = name.reverse().takeWhile { _ != '.' }.reverse()
319
- "(function(_v) { " +
320
- "return _v." + escapeResolved(n) + " ? ff_core_Option.Some(_v) : ff_core_Option.None();" +
321
- "})"
322
- | ECopy(at, name, record, fields) =>
323
- let fieldCode = fields.map {f => escapeKeyword(f.name) + " = " + self.emitTerm(f.value, async)}.join(", ")
324
- "{..." + self.emitTerm(record, async) + ", " + fieldCode + "}"
325
- | EField(at, newtype, record, field) =>
326
- if(newtype) {self.emitTerm(record, async)} else:
327
- self.emitTerm(record, async) + "." + escapeKeyword(field)
328
- | ELambda(at, Lambda(_, effect, [MatchCase(_, patterns, [], body)])) {
329
- patterns.all {| PVariable _ => True | _ => False }
330
- } =>
331
- let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
332
- let patternParameters = patterns.map {
333
- | PVariable p => p.name.map(escapeKeyword).else {"_"}
334
- | _ => panic("!")
335
- }
336
- let controller = if(newAsync) {["$task"]} else {[]}
337
- let parameters = [...patternParameters, ...controller].join(", ")
338
- let prefix = if(newAsync) {"async "} else {""}
339
- "(" + prefix + "(" + parameters + ") => {\n" + self.emitStatements(body, True, newAsync) + "\n})"
340
- | ELambda(at, Lambda(_, effect, cases)) =>
341
- let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
342
- let controller = if(newAsync) {["$task"]} else {[]}
343
- Patterns.convertAndCheck(self.otherModules, cases)
344
- let arguments = cases.grab(0).patterns.pairs().map {"_" + (_.first + 1)}
345
- let escapedArguments = arguments.map(escapeKeyword) // emitCase arguments must be preescaped
346
- let caseStrings = cases.map {self.emitCase(escapedArguments, _, [], [], True, True, newAsync)}
347
- let prefix = if(newAsync) {"async "} else {""}
348
- "(" + prefix + "(" + [...escapedArguments, ...controller].join(", ") + ") => " +
349
- "{\n" + caseStrings.join("\n") + "\n})"
350
- | EPipe(at, value, effect, function) =>
351
- let await = async && effectTypeIsAsync(effect)
352
- let c = if(await) {", $task"} else {""}
353
- let call = "(" + self.emitTerm(function, async) + ")(" + self.emitTerm(value, async) + c + ")"
354
- if(await) {"(await " + call + ")"} else {call}
355
- | ECall(at, StaticCall(operator, _, _), _, [], [value], _) {!operator.grabFirst().isAsciiLetter()} =>
356
- "(" + operator + self.emitArgument(at, value, async) + ")"
357
- | ECall(at, StaticCall(operator, _, _), _, [], [left, right], _) {!operator.grabFirst().isAsciiLetter()} =>
358
- "(" + self.emitArgument(at, left, async) + " " + operator + " " + self.emitArgument(at, right, async) + ")"
359
- | ECall(at, StaticCall("ff:core/List.List_grab", _, _), _, _, [Argument(_, _, EVariable(_, x1)), Argument(_, _, EVariable(_, x2))], _) =>
360
- "(" + escapeResolved(x1) + "[" + escapeResolved(x2) + "] ?? " +
361
- "ff_core_List.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
362
- | ECall(at, StaticCall("ff:core/Array.Array_grab", _, _), _, _, [Argument(_, _, EVariable(_, x1)), Argument(_, _, EVariable(_, x2))], _) =>
363
- "(" + escapeResolved(x1) + ".array[" + escapeResolved(x2) + "] ?? " +
364
- "ff_core_Array.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
365
- | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.import", _, _), _, _, [Argument(_, _, EString(_, url))], _) =>
366
- self.jsImporter.add(url.replace("\"", ""))
367
- | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.await", _, _), _, _, [Argument(_, _, body)], _) =>
368
- let emittedBody = self.emitTerm(body, async)
369
- if(async) {"(await " + emittedBody + "($task))"} else {"(" + emittedBody + "())"}
370
- | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.cancelled", _, _), _, _, [], _) =>
371
- if(async) {"$task.controller.signal.aborted"} else {"false"}
372
- | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.throwIfCancelled", _, _), _, _, [], _) =>
373
- if(async) {"((() => ff_core_Task.Task_throwIfAborted($task))())"} else {""}
374
- | ECall(at, StaticCall("ff:core/Equal.equals", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
375
- primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
376
- } =>
377
- "(" + self.emitArgument(at, left, async) + " === " + self.emitArgument(at, right, async) + ")"
378
- | ECall(at, StaticCall("ff:core/Equal.notEquals", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
379
- primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
380
- } =>
381
- "(" + self.emitArgument(at, left, async) + " !== " + self.emitArgument(at, right, async) + ")"
382
- | ECall(at, StaticCall("ff:core/Ordering.before", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
383
- primitiveTypes.contains(typeName)
384
- } =>
385
- "(" + self.emitArgument(at, left, async) + " < " + self.emitArgument(at, right, async) + ")"
386
- | ECall(at, StaticCall("ff:core/Ordering.notBefore", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
387
- primitiveTypes.contains(typeName)
388
- } =>
389
- "(" + self.emitArgument(at, left, async) + " >= " + self.emitArgument(at, right, async) + ")"
390
- | ECall(at, StaticCall("ff:core/Ordering.after", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
391
- primitiveTypes.contains(typeName)
392
- } =>
393
- "(" + self.emitArgument(at, left, async) + " > " + self.emitArgument(at, right, async) + ")"
394
- | ECall(at, StaticCall("ff:core/Ordering.notAfter", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
395
- primitiveTypes.contains(typeName)
396
- } =>
397
- "(" + self.emitArgument(at, left, async) + " <= " + self.emitArgument(at, right, async) + ")"
398
- | ECall(_, StaticCall("ff:core/List.fillBy", _, _), effect, _, [size, Argument(_, _, ELambda(at,
399
- Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)@c])@l
400
- ))], _) {
401
- !effectTypeIsAsync(effect)
402
- } =>
403
- let n = name.map {escapeResolved(_)}.else {"i"}
404
- let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
405
- let await = if(newAsync) {"await "} else {""}
406
- await + "((() => {\n" +
407
- "const size = " + self.emitArgument(at, size, async) + ";\n" + // Not correct if async and body isn't
408
- "const result = [];\n" +
409
- "for(let " + n + " = 0; " + n + " < size; " + n + "++) {\n" +
410
- "result.push(" + self.emitTerm(body, newAsync) + ");\n" +
411
- "}\n" +
412
- "return result;\n" +
413
- "})())"
414
- | ECall(at, StaticCall(name, _, True), effect, typeArguments, arguments, dictionaries) =>
415
- let await = async && effectTypeIsAsync(effect)
416
- let dictionaryStrings = dictionaries.map {self.emitDictionary(_)}
417
- let ds = dictionaryStrings.dropFirst()
418
- let d = dictionaryStrings.grabFirst()
419
- let asyncSuffix = if(await) {"$"} else {""}
420
- let n = escapeKeyword(name.reverse().takeWhile {_ != '.'}.reverse()) + asyncSuffix
421
- let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
422
- let controller = if(await) {["$task"]} else {[]}
423
- let call = d + "." + n + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
424
- if(await) {"(await " + call + ")"} else {call}
425
- | ECall(at, StaticCall(name, _, _), effect, typeArguments, arguments, dictionaries) =>
426
- if(name.contains("bundleForBrowser")) { // TODO: Delete this test (for branch arraysonly)
427
- if(!arguments.grab(0).name.contains("system")) {
428
- Log.debug("Wrong arguments for bundleForBrowser: " + Show.show(arguments.map {_.name}))
429
- throw(GrabException())
430
- }
431
- }
432
- detectIfElse(term).{
433
- | [] =>
434
- let await = async && effectTypeIsAsync(effect)
435
- let ds = dictionaries.map {self.emitDictionary(_)}
436
- let functionCode = escapeResolved(name) + if(await) {"$"} else {""}
437
- let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
438
- let controller = if(await) {["$task"]} else {[]}
439
- let call = functionCode + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
440
- if(await) {"(await " + call + ")"} else {call}
441
- | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
442
- "(" + list.foldLeft(self.emitTerm(elseBody, async)) {| otherwise, Pair(condition, body) =>
443
- self.emitTerm(condition, async) +
444
- "\n? " + self.emitTerm(body, async) + "\n: " + otherwise
445
- } + ")"
446
- | list =>
447
- "(" + list.foldLeft("ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
448
- self.emitTerm(condition, async) +
449
- "\n? ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n: " + otherwise
450
- } + ")"
451
- }
452
- | ECall(at, DynamicCall(function, _), effect, typeArguments, arguments, dictionaries) =>
453
- let await = async && effectTypeIsAsync(effect)
454
- if(!dictionaries.isEmpty()) {fail(at, "Internal error: Dictionaries in lambda call")}
455
- let functionCode = self.emitTerm(function, async)
456
- let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
457
- let controller = if(await) {["$task"]} else {[]}
458
- let call = functionCode + "(" + [...emittedArguments, ...controller].join(", ") + ")"
459
- if(await) {"(await " + call + ")"} else {call}
460
- | ERecord(at, fields) =>
461
- if(fields.isEmpty()) {"{}"} else {
462
- let list = fields.map {f => escapeKeyword(f.name) + ": " + self.emitTerm(f.value, async)}
463
- "{\n" + list.join(",\n") + "\n}"
464
- }
465
- | EWildcard(at, index) =>
466
- if(index == 0) {fail(at, "Unbound wildcard")}
467
- "_w" + index
468
- | _ {async} =>
469
- "(await (async function() {\n" + self.emitStatements(term, True, async) + "\n})())"
470
- | _ =>
471
- "(function() {\n" + self.emitStatements(term, True, async) + "\n})()"
472
- }}
473
-
474
- emitDictionary(d: Dictionary): String {
475
- let m = if(d.moduleName != "") {
476
- d.packagePair.groupName("_") + "_" + d.moduleName.replace("/", "_") + "."
477
- } else {""}
478
- let c = m + makeDictionaryName(d.traitName, d.typeName)
479
- if(d.dictionaries.isEmpty()) {
480
- c
481
- } else {
482
- c + "(" + d.dictionaries.map {self.emitDictionary(_)}.join(", ") + ")"
483
- }
484
- }
485
-
486
- emitStatements(term: Term, last: Bool, async: Bool): String {
487
- term.{
488
- | EFunctions(at, functions, body) =>
489
- let functionStrings = functions.map {f =>
490
- let newAsync = self.emittingAsync && effectTypeIsAsync(f.signature.effect)
491
- self.emitFunctionDefinition(f, newAsync)
492
- }
493
- functionStrings.join("\n") + "\n" + self.emitStatements(body, last, async)
494
- | ELet(at, mutable, name, valueType, value, body) =>
495
- self.emitLetDefinition(DLet(at, name, valueType, value), mutable, async) + "\n" +
496
- self.emitStatements(body, last, async)
497
- | EVariant(at, "ff:core/Unit.Unit", _, _) =>
498
- ""
499
- | ESequential(_, EVariant(_, "ff:core/Unit.Unit", _, _), after) =>
500
- self.emitStatements(after, last, async)
501
- | ESequential(_, before, EVariant(_, "ff:core/Unit.Unit", _, _)) =>
502
- self.emitStatements(before, False, async)
503
- | ESequential(at, before, after) =>
504
- self.emitStatements(before, False, async) + ";\n" + self.emitStatements(after, last, async)
505
- | EAssign(at, operator, name, value) =>
506
- escapeKeyword(name) + " " + operator + "= " + self.emitTerm(value, async)
507
- | EAssignField(at, operator, record, field, value) =>
508
- self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
509
- self.emitTerm(value, async)
510
- | ECall(at, StaticCall("ff:core/Core.while", _, _), _, _, [condition, body], _) =>
511
- "while(" + self.emitTerm(invokeImmediately(condition.value), async) + ") {\n" +
512
- self.emitStatements(invokeImmediately(body.value), False, async) + "\n}"
513
- | ECall(at, StaticCall("ff:core/Core.doWhile", _, _), _, _, [Argument(_, _, doWhileBody)], _) {
514
- invokeImmediately(doWhileBody) | ESequential(_, body, condition)
515
- } =>
516
- "while(true) {\n" +
517
- self.emitStatements(body, False, async) +
518
- "\nif(!" + self.emitTerm(condition, async) + ") break" +
519
- "\n}"
520
- | ECall(at, StaticCall("ff:core/Core.doWhile", _, _), _, _, [Argument(_, _, doWhileBody)], _) {
521
- invokeImmediately(doWhileBody) | body
522
- } =>
523
- "while(" + self.emitTerm(body, async) + ") {}"
524
- | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
525
- "if(" + self.emitTerm(condition.value, async) + ") {\n" +
526
- if(last) {
527
- "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body.value), async) +
528
- ")\n} else return ff_core_Option.None()"
529
- } else {
530
- self.emitStatements(invokeImmediately(body.value), False, async) + "\n}"
531
- }
532
- | ECall(at, StaticCall("ff:core/Core.throw", _, _), _, _, [argument], [dictionary]) =>
533
- let d = self.emitDictionary(dictionary)
534
- let a = self.emitArgument(at, argument, async)
535
- "throw Object.assign(new Error(), {ffException: ff_core_Any.toAny_(" + a + ", " + d + ")})"
536
- | ECall(at, StaticCall("ff:core/Core.try", _, _), _, _, _, _) {!last} =>
537
- throw(CompileError(at, "Statements can't be a try without a grab"))
538
- | ECall(at, StaticCall("ff:core/Try.Try_map", _, _), _, _, _, _) {!last} =>
539
- throw(CompileError(at, "Statements can't be a map without a grab"))
540
- | ECall(at, StaticCall("ff:core/Try.Try_flatMap", _, _), _, _, _, _) {!last} =>
541
- throw(CompileError(at, "Statements can't be a flatMap without a grab"))
542
- | ECall(at, StaticCall("ff:core/Try.Try_flatten", _, _), _, _, _, _) {!last} =>
543
- throw(CompileError(at, "Statements can't be a flatten without a grab"))
544
- | ECall(at, StaticCall("ff:core/Try.Try_catch", _, _), _, _, _, _) {!last} =>
545
- throw(CompileError(at, "Statements can't be a catch without a grab"))
546
- | ECall(at, StaticCall("ff:core/Try.Try_catchAny", _, _), _, _, _, _) {!last} =>
547
- throw(CompileError(at, "Statements can't be a catchAny without a grab"))
548
- | ECall(at, StaticCall("ff:core/Try.Try_finally", _, _), _, _, _, _) {!last} =>
549
- throw(CompileError(at, "Statements can't be a finally without a grab"))
550
- | ECall(at, StaticCall("ff:core/Try.Try_grab", _, _), _, _, [argument], _) {
551
- self.emitTryCatchFinally(argument.value, last, async) | Some(code)
552
- } =>
553
- code
554
- | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.throwIfCancelled", _, _), _, _, [], _) =>
555
- if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""}
556
- | ECall(at, StaticCall(name, True, instanceCall), effect, _, arguments, _) =>
557
- if(instanceCall) {throw(CompileError(at, "Not yet implemented: Tail calls on trait methods."))}
558
- self.tailCallUsed = True
559
- let pair = arguments.map {a =>
560
- Some(Pair(
561
- "const " + escapeKeyword(a.name.grab() + "_r") + " = " + self.emitTerm(a.value, async) + ";"
562
- escapeKeyword(a.name.grab()) + " = " + escapeKeyword(a.name.grab() + "_r")
563
- ))
564
- }.collect {_}.unzip()
565
- "{\n" + pair.first.join("\n") + "\n" + pair.second.join("\n") + "\ncontinue _tailcall\n}"
566
- | EPipe(at, value, _, ELambda(_, Lambda(_, _, cases))) =>
567
- Patterns.convertAndCheck(self.otherModules, cases)
568
- if(!last) {"do "}.else {""} +
569
- "{\nconst _1 = " + self.emitTerm(value, async) + ";\n" +
570
- cases.map {self.emitCase(["_1"], _, [], [], True, last, async)}.join("\n") +
571
- "\n}" + if(!last) {" while(false)"}.else {""}
572
- | _ =>
573
- detectIfElse(term).{
574
- | [] =>
575
- if(last) {"return " + self.emitTerm(term, async)} else {self.emitTerm(term, async)}
576
- | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
577
- let initial = "{\n" + self.emitStatements(elseBody, last, async) + "\n}"
578
- list.foldLeft(initial) {| otherwise, Pair(condition, body) =>
579
- "if(" + self.emitTerm(condition, async) + ") {\n" +
580
- self.emitStatements(body, last, async) + "\n} else " + otherwise
581
- }
582
- | list {!last} =>
583
- list.foldLeft("{}") {| otherwise, Pair(condition, body) =>
584
- "if(" + self.emitTerm(condition, async) + ") {\n" +
585
- self.emitStatements(body, last, async) + "\n} else " + otherwise
586
- }
587
- | list =>
588
- list.foldLeft("return ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
589
- "if(" + self.emitTerm(condition, async) + ") {\n" +
590
- "return ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n} else " + otherwise
591
- }
592
- }
593
- }
594
- }
595
-
596
- emitTryCatchFinally(term: Term, last: Bool, async: Bool): Option[String] {
597
- function emitCatch(catchEffect: Type, cases: List[MatchCase]): String {
598
- let catchAsync = self.emittingAsync && effectTypeIsAsync(catchEffect)
599
- Patterns.convertAndCheck(self.otherModules, cases)
600
- let arguments = ["_exception.value_", "_error"]
601
- cases.{
602
- | [case] =>
603
- self.emitCase(arguments, case, [], [], False, last, catchAsync)
604
- | cs =>
605
- let caseStrings =
606
- cases.map {self.emitCase(arguments, _, [], [], True, last, catchAsync)}
607
- if(last) {caseStrings.join("\n")} else {"do {\n" + caseStrings.join("\n") + "\n} while(false)"}
608
- }
609
- }
610
- term.{
611
- | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
612
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
613
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
614
- ], _))
615
- Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
616
- ], _) =>
617
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
618
- let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
619
- Some(
620
- "try {\n" + self.emitStatements(tryBody, last, tryAsync) +
621
- "\n} finally {\n" + self.emitStatements(finallyBody, last, finallyAsync) + "\n}"
622
- )
623
- | ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
624
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
625
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
626
- ], _))
627
- Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
628
- ], [dictionary]) =>
629
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
630
- let d = self.emitDictionary(dictionary)
631
- Some(
632
- "try {\n" + self.emitStatements(tryBody, last, tryAsync) +
633
- "\n} catch(_error) {\n" +
634
- "if(!_error.ffException) throw _error\n" +
635
- "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
636
- "if(!_exception.Some) throw _error\n" +
637
- emitCatch(catchEffect, cases) +
638
- "\n}"
639
- )
640
- | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
641
- Argument(_, _, ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
642
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
643
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
644
- ], _))
645
- Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
646
- ], [dictionary]))
647
- Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
648
- ], _) =>
649
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
650
- let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
651
- let d = self.emitDictionary(dictionary)
652
- Some(
653
- "try {\n" + self.emitStatements(tryBody, last, tryAsync) +
654
- "\n} catch(_error) {\n" +
655
- "if(!_error.ffException) throw _error\n" +
656
- "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
657
- "if(!_exception.Some) throw _error\n" +
658
- emitCatch(catchEffect, cases) +
659
- "\n} finally {\n" + self.emitStatements(finallyBody, last, finallyAsync) + "\n}"
660
- )
661
- | _ =>
662
- None
663
- }
664
- }
665
-
666
- emitCase(
667
- arguments: List[String]
668
- matchCase: MatchCase
669
- conditions: List[String]
670
- variables: List[String]
671
- jump: Bool
672
- last: Bool
673
- async: Bool
674
- ): String {
675
- function emitWrapper(code: String): String {
676
- if(conditions.isEmpty()) {"{\n"} else {
677
- "if(" + conditions.join(" && ") + ") {\n"
678
- } +
679
- variables.join() +
680
- code +
681
- "\n}"
682
- }
683
- Pair(matchCase.patterns, matchCase.guards).{
684
- | Pair([p, ...ps], _) =>
685
- self.emitPattern(
686
- arguments.grab(0)
687
- p
688
- arguments.dropFirst()
689
- matchCase.MatchCase(patterns = ps)
690
- conditions
691
- variables
692
- jump
693
- last
694
- async
695
- )
696
- | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) {variables.isEmpty()} =>
697
- let newCase = matchCase.MatchCase(patterns = [], guards = [])
698
- self.emitCase([], newCase, [...conditions, self.emitTerm(e, async)], [], jump, last, async)
699
- | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) =>
700
- let newCase = matchCase.MatchCase(patterns = [], guards = [])
701
- let code = self.emitCase([], newCase, [self.emitTerm(e, async)], [], jump, last, async)
702
- emitWrapper(code)
703
- | Pair([], [guard, ...guards]) =>
704
- let guardName = "_guard" + (guards.size() + 1)
705
- let newCase = matchCase.MatchCase(patterns = [guard.pattern], guards = guards)
706
- let code =
707
- "const " + guardName + " = " + self.emitTerm(guard.term, async) + ";\n" +
708
- self.emitCase([guardName], newCase, [], [], jump, last, async)
709
- emitWrapper(code)
710
- | Pair([], []) =>
711
- let statementsCode = self.emitStatements(matchCase.body, last, async)
712
- let lastLine = statementsCode.reverse().takeWhile {_ != '\n'}.reverse()
713
- let returns =
714
- lastLine.startsWith("return ") ||
715
- lastLine.startsWith("break ") ||
716
- lastLine.startsWith("continue ") ||
717
- lastLine.startsWith("return;") ||
718
- lastLine.startsWith("break;") ||
719
- lastLine.startsWith("continue;") ||
720
- lastLine.startsWith("throw ")
721
- let code = statementsCode + if(jump && last && !returns) {
722
- "\nreturn"
723
- } elseIf {jump && !returns} {
724
- "\nbreak"
725
- } else {
726
- ""
727
- }
728
- emitWrapper(code)
729
- }
730
- }
731
-
732
- emitPattern(
733
- argument: String
734
- pattern: MatchPattern
735
- arguments: List[String]
736
- matchCase: MatchCase
737
- conditions: List[String]
738
- variables: List[String]
739
- jump: Bool
740
- last: Bool
741
- async: Bool
742
- ): String {
743
- pattern.{
744
- | PString(_, value) =>
745
- let newConditions = [...conditions, argument + " === " + value]
746
- self.emitCase(arguments, matchCase, newConditions, variables, jump, last, async)
747
- | PInt(_, value) =>
748
- let newConditions = [...conditions, argument + " === " + value]
749
- self.emitCase(arguments, matchCase, newConditions, variables, jump, last, async)
750
- | PChar(_, value) =>
751
- let newConditions = [...conditions, argument + " === " + charLiteralToNumber(value)]
752
- self.emitCase(arguments, matchCase, newConditions, variables, jump, last, async)
753
- | PVariable(_, None) =>
754
- self.emitCase(arguments, matchCase, conditions, variables, jump, last, async)
755
- | PVariable(_, Some(name)) =>
756
- let escaped = escapeKeyword(name)
757
- let newVariables = if(escaped != argument) {
758
- [...variables, "const " + escaped + " = " + argument + ";\n"]
759
- } else {variables}
760
- self.emitCase(arguments, matchCase, conditions, newVariables, jump, last, async)
761
- | PVariant(_, "ff:core/Bool.False", []) =>
762
- self.emitCase(arguments, matchCase, [...conditions, "!" + argument], variables, jump, last, async)
763
- | PVariant(_, "ff:core/Bool.True", []) =>
764
- self.emitCase(arguments, matchCase, [...conditions, argument], variables, jump, last, async)
765
- | PVariant(_, emptyOrLink, _) {emptyOrLink == "List$Empty" || emptyOrLink == "List$Link"} =>
766
- mutable restPattern = None
767
- function listPatterns(matchPattern: MatchPattern): List[MatchPattern] {
768
- | PVariant(_, "List$Empty", []) =>
769
- []
770
- | PVariant(_, "List$Link", [head, tail]) =>
771
- [head, ...listPatterns(tail)]
772
- | p =>
773
- restPattern = Some(p)
774
- []
775
- }
776
- let patterns = listPatterns(pattern)
777
- let itemArguments = patterns.pairs().map {| Pair(i, _) => argument + "[" + i + "]"}
778
- let restArgument = restPattern.map {_ => argument + ".slice(" + patterns.size() + ")"}
779
- let newArguments = [...itemArguments, ...restArgument.toList(), ...arguments]
780
- let newMatchCase = matchCase.MatchCase(
781
- patterns = [...patterns, ...restPattern.toList(), ...matchCase.patterns]
782
- )
783
- let operator = restPattern.map {_ => ">="}.else {"==="}
784
- let newConditions = [...conditions, argument + ".length " + operator + " " + patterns.size()]
785
- self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, async)
786
- | PVariant(_, name, patterns) =>
787
- let processed = self.processVariantCase(name, argument)
788
- let newMatchCase = matchCase.MatchCase(patterns = [...patterns, ...matchCase.patterns])
789
- let newConditions = if(processed.loneVariant) {conditions} else {
790
- [...conditions, argument + "." + processed.variantName]
791
- }
792
- let newArguments = [...processed.arguments, ...arguments]
793
- self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, async)
794
- | PVariantAs(at, name, variableAt, variable) =>
795
- let processed = self.processVariantCase(name, argument)
796
- let newConditions = if(processed.loneVariant) {conditions} else {
797
- [...conditions, argument + "." + processed.variantName]
798
- }
799
- let newVariables = variable.map(escapeKeyword).filter {_ != argument}.map {
800
- [...variables, "const " + _ + " = " + argument + ";\n"]
801
- }.else {[]}
802
- self.emitCase(arguments, matchCase, newConditions, newVariables, jump, last, async)
803
- | PAlias(_, pattern, variable) =>
804
- let escaped = escapeKeyword(variable)
805
- let newVariables = if(escaped != argument) {
806
- [...variables, "const " + escaped + " = " + argument + ";\n"]
807
- } else {variables}
808
- self.emitPattern(argument, pattern, arguments, matchCase, conditions, newVariables, jump, last, async)
809
- }
810
- }
811
-
812
- emitList(items: List[Pair[Term, Bool]], async: Bool): String {
813
- "[" + items.map {
814
- | Pair(item, False) => self.emitTerm(item, async)
815
- | Pair(item, True) => "..." + self.emitTerm(item, async)
816
- }.join(", ") + "]"
817
- }
818
-
819
- processVariantCase(name: String, argument: String): ProcessedVariantCase {
820
- let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
821
- let variantName = escapeKeyword(variantNameUnqualified)
822
- let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
823
- let variantModule = self.otherModules.grab(moduleName)
824
- mutable newtype = False
825
- mutable loneVariant = False
826
- let newArguments = variantModule.types.collectFirst {definition =>
827
- definition.variants.find {_.name == variantName }.map {variant =>
828
- newtype = definition.newtype
829
- loneVariant = definition.variants.size() == 1
830
- [...definition.commonFields.map {_.name}, ...variant.fields.map {_.name}]
831
- }
832
- }.grab().map {field => if(newtype) {argument} else {argument + "." + escapeKeyword(field)}}
833
- ProcessedVariantCase(variantName, newtype, loneVariant, newArguments)
834
- }
835
-
836
- processVariant(name: String): Bool {
837
- if(name.startsWith("List$")) {False} else:
838
- let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
839
- let variantName = escapeKeyword(variantNameUnqualified)
840
- let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
841
- let variantModule = self.otherModules.grab(moduleName)
842
- mutable newtype = False
843
- let newArguments = variantModule.types.collectFirst {definition =>
844
- definition.variants.find {_.name == variantName}.map {variant =>
845
- newtype = definition.newtype
846
- }
847
- }.grab()
848
- newtype
849
- }
850
-
851
- emitArgument(callAt: Location, argument: Argument, async: Bool): String {
852
- argument.value.{
853
- | ECall(_, StaticCall("ff:core/SourceLocation.callSite", _, _), _, _, _, _) =>
854
- "\"" + self.moduleName + ":" + callAt.line + ":" + callAt.column +
855
- "," + self.packagePair.group + "," + self.packagePair.name + "\""
856
- | value =>
857
- self.emitTerm(value, async)
858
- }
859
- }
860
-
861
- }
862
-
863
- data ProcessedVariantCase(
864
- variantName: String
865
- newtype: Bool
866
- loneVariant: Bool
867
- arguments: List[String]
868
- )
869
-
870
- detectIfElse(term: Term): List[Pair[Term, Term]] {
871
- | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
872
- [Pair(condition.value, invokeImmediately(body.value))]
873
- | ECall(at, StaticCall("ff:core/Option.Option_elseIf", _, _), _, _, [option, condition, body], _) =>
874
- let list = detectIfElse(option.value)
875
- if(list.isEmpty()) {[]} else:
876
- [Pair(invokeImmediately(condition.value), invokeImmediately(body.value)), ...list]
877
- | ECall(at, StaticCall("ff:core/Option.Option_else", _, _), _, _, [option, body], _) =>
878
- let list = detectIfElse(option.value)
879
- if(list.isEmpty()) {[]} else:
880
- [Pair(EVariant(at, "ff:core/Bool.True", [], None), invokeImmediately(body.value)), ...list]
881
- | _ =>
882
- []
883
- }
884
-
885
- invokeImmediately(function: Term): Term {
886
- | ELambda(_, Lambda(_, effect, [MatchCase(_, [], [], body)])) =>
887
- body
888
- | _ =>
889
- let effect = TConstructor(function.at, "Q$", []) // Awaits more often than required in async context
890
- ECall(function.at, DynamicCall(function, False), effect, [], [], [])
891
- }
892
-
893
- extractTypeName(type: Type): String {
894
- | TVariable(at, index) =>
895
- fail(at, "Unexpected type variable: $" + index)
896
- | TConstructor t =>
897
- t.name
898
- }
899
-
900
- firstTypeName(types: List[Type]): String {
901
- types.grabFirst().{
902
- | TConstructor t => t.name
903
- | TVariable t => fail(t.at, " is still a unification variable")
904
- }
905
- }
906
-
907
- makeDictionaryName(traitName: String, typeName: String): String {
908
- traitName.replace(".", "_").replace(":", "_").replace("/", "_") + "$" +
909
- typeName.replace(".", "_").replace(":", "_").replace("/", "_")
910
- }
911
-
912
- charLiteralToNumber(charLiteral: String): String {
913
- | "'\\t'" => "9"
914
- | "'\\n'" => "10"
915
- | "'\\r'" => "13"
916
- | "'\\\"'" => "34"
917
- | "'\\''" => "39"
918
- | value => "" + value.grab(1).codeUnit
919
- }
920
-
921
- escapeResolved(word: String): String {
922
- let parts = word.replace(":", ".").replace("/", ".").split('.')
923
- let initialParts = parts.dropLast()
924
- if(initialParts.isEmpty()) {
925
- escapeKeyword(parts.grabLast())
926
- } else {
927
- initialParts.join("_") + "." + escapeKeyword(parts.grabLast())
928
- }
929
- }
930
-
931
- escapeKeyword(word: String): String {
932
- if(word.grabFirst().isAsciiLower()) {word + "_"} else {word}
933
- }
934
-
935
- effectTypeIsAsync(effect: Type): Bool {
936
- | TConstructor(_, "Q$", _) => True
937
- | _ => False
938
- }
939
-
940
- primitiveTypes = [
941
- "ff:core/Bool.Bool"
942
- "ff:core/Char.Char"
943
- "ff:core/Int.Int"
944
- "ff:core/Float.Float"
945
- "ff:core/String.String"
946
- ].toSet()
1
+ import Syntax
2
+ import Patterns
3
+ import JsImporter
4
+
5
+ capability JsEmitter(
6
+ otherModules: Map[String, Module]
7
+ jsImporter: JsImporter
8
+ emitTarget: EmitTarget
9
+ isMainModule: Bool
10
+ compilerModulePath: Option[Path]
11
+ packagePair: PackagePair
12
+ moduleName: String
13
+ mutable emittingAsync: Bool
14
+ mutable tailCallUsed: Bool
15
+ )
16
+
17
+ data EmitTarget {
18
+ EmitNode
19
+ EmitBrowser
20
+ EmitBuild
21
+ EmitExecutable
22
+ }
23
+
24
+ new(
25
+ otherModules: List[Module]
26
+ emitTarget: EmitTarget
27
+ isMainModule: Bool
28
+ compilerModulePath: Option[Path]
29
+ packagePair: PackagePair
30
+ moduleName: String
31
+ ): JsEmitter {
32
+ JsEmitter(
33
+ otherModules = otherModules.map {m =>
34
+ let moduleName = m.packagePair.groupName() + "/" + m.file.dropLast(3)
35
+ Pair(moduleName, m)
36
+ }.toMap()
37
+ jsImporter = JsImporter.new()
38
+ emitTarget = emitTarget
39
+ isMainModule = isMainModule
40
+ compilerModulePath = compilerModulePath
41
+ packagePair = packagePair
42
+ moduleName = moduleName
43
+ emittingAsync = False
44
+ tailCallUsed = False
45
+ )
46
+ }
47
+
48
+ fail[T](at: Location, message: String): T {
49
+ panic(message + " " + at.show())
50
+ }
51
+
52
+ extend self: JsEmitter {
53
+
54
+ emitModule(packagePair: PackagePair, module: Module): String {
55
+ let selfImport =
56
+ "import * as " + packagePair.groupName("_") + "_" + module.file.dropLast(3) + " " +
57
+ "from \"../../" + packagePair.groupName("/") + "/" + module.file.dropLast(3) + ".mjs\""
58
+ let imports = [
59
+ self.compilerModulePath.map {"import * as $firefly_compiler from '" + _.url() + "'"}.toList()
60
+ module.imports.sortBy {i => Pair(i.package, i.file) }.map {self.emitImportDefinition(_)}
61
+ ].flatten()
62
+ let parts = [
63
+ if(imports.any {_ == selfImport}) {imports} else {[selfImport, ...imports]}
64
+ module.types.map {self.emitTypeDefinition(_)}
65
+ module.lets.map {"export " + self.emitLetDefinition(_, False, False)}
66
+ module.functions.map {"export " + self.emitFunctionDefinition(_, False)}
67
+ self.withEmittingAsync {module.functions.map {"export " + self.emitFunctionDefinition(_, True)}}
68
+ module.extends.map {self.emitExtendsDefinition(_)}
69
+ module.instances.map {self.emitInstanceDefinition(_)}
70
+ if(self.isMainModule) {
71
+ self.emitRun(module.functions, packagePair, packagePair.group == "ff" && packagePair.name == "compiler")
72
+ } else {[]}
73
+ ]
74
+ let ignoreJsImports = if(
75
+ self.emitTarget == EmitExecutable &&
76
+ packagePair.group == "ff" &&
77
+ packagePair.name == "core"
78
+ ) {["esbuild"]} else {[]}
79
+ let jsImports = self.jsImporter.generateImports(ignoreJsImports.toSet())
80
+ [jsImports, ...parts].map {_.join("\n\n")}.join("\n\n") + "\n"
81
+ }
82
+
83
+ withEmittingAsync[T](body: () => T): T {
84
+ try {
85
+ self.emittingAsync = True
86
+ body()
87
+ } finally {
88
+ self.emittingAsync = False
89
+ } grab()
90
+ }
91
+
92
+ emitRun(functions: List[DFunction], mainPackagePair: PackagePair, bootstrapping: Bool): List[String] {
93
+ let buildMainFunction = functions.find {_.signature.name == "buildMain"}.filter {_ =>
94
+ self.emitTarget != EmitBrowser && self.emitTarget != EmitExecutable
95
+ }
96
+ let willRunOnNode = self.emitTarget != EmitBrowser
97
+ let targetMain = if(willRunOnNode) {"nodeMain"} else {"browserMain"}
98
+ let mainFunction =
99
+ functions.find {_.signature.name == targetMain}.orElse {functions.find {_.signature.name == "main"}}
100
+ mainFunction.map {_.signature.name}.map {mainName => [[
101
+ "export async function $run$(fireflyPath_, arguments_) {"
102
+ "Error.stackTraceLimit = 50"
103
+ "const $task = {controller: new AbortController(), subtasks: new Set(), promise: new Promise(() => {}), started: performance.now() * 0.001}"
104
+ ...if(self.emitTarget != EmitBrowser) {[
105
+ "let interval = setInterval(() => {}, 24 * 60 * 60 * 1000)" // To prevent deadlocks from exiting node
106
+ ]} else {[]}
107
+ "let system = {"
108
+ "task_: $task,"
109
+ "array_: arguments_,"
110
+ "fireflyPath_: fireflyPath_,"
111
+ "mainPackagePair_: {group_: \"" + mainPackagePair.group + "\", name_: \"" + mainPackagePair.name + "\"},"
112
+ "executableMode_: " + if(self.emitTarget == EmitExecutable) {"true"} else {"false"} + ","
113
+ "buildMode_: " + if(self.emitTarget == EmitBuild) {"true"} else {"false"}
114
+ "}"
115
+ "try {"
116
+ ...if(!buildMainFunction.isEmpty()) {[
117
+ "await buildMain_$(system, $task)"]
118
+ } else {[]}
119
+ ...if(self.emitTarget != EmitBuild) {[
120
+ "await " + mainName + "_$(system, $task)"
121
+ ]} else {[]}
122
+ ...if(self.emitTarget == EmitBuild) {[
123
+ "await $firefly_compiler.internalCreateExecutable_$(system, '.firefly/output/executable/Main.bundle.js', '.firefly/output', ['host'], system.assets_, $task)"
124
+ ]} else {[]}
125
+ "} finally {"
126
+ ...if(self.emitTarget != EmitBrowser) {[
127
+ "ff_core_Task.Task_abort$($task)"
128
+ "clearInterval(interval)"
129
+ ]} else {[]}
130
+ "}"
131
+ "}"
132
+ ...self.emitTarget.{
133
+ | EmitBrowser => [
134
+ "queueMicrotask(async () => {"
135
+ "await $run$(null, [])"
136
+ "})"
137
+ ]
138
+ | EmitNode {bootstrapping} => [
139
+ "import * as path from 'node:path'"
140
+ "queueMicrotask(async () => {"
141
+ "let fireflyPath_ = path.dirname(path.dirname(path.dirname(path.dirname(path.dirname(process.argv[1])))))"
142
+ "await $run$(fireflyPath_, process.argv.slice(2))"
143
+ "})"
144
+ ]
145
+ | EmitExecutable => [
146
+ "queueMicrotask(async () => {"
147
+ "await $run$(null, process.argv.slice(2))"
148
+ "})"
149
+ ]
150
+ | _ => []
151
+ }
152
+ ].join("\n")]}.else {[]}
153
+ }
154
+
155
+ emitImportDefinition(definition: DImport): String {
156
+ "import * as " + definition.package.group + "_" + definition.package.name + "_" + definition.file + " " +
157
+ "from \"../../" + definition.package.group + "/" + definition.package.name + "/" + definition.file + ".mjs\""
158
+ }
159
+
160
+ emitLetDefinition(definition: DLet, mutable: Bool, async: Bool): String {
161
+ let mutability = if(mutable) {"let"} else {"const"}
162
+ let valueCode = self.emitTerm(definition.value, async)
163
+ mutability + " " + escapeKeyword(definition.name) + " = " + valueCode + ";"
164
+ }
165
+
166
+ emitExtendsDefinition(definition: DExtend): String {
167
+ let typeName = extractTypeName(definition.type).reverse().takeWhile {_ != '.'}.reverse()
168
+ let methods = definition.methods.map {method =>
169
+ method.DFunction(
170
+ signature = method.signature.Signature(
171
+ name = typeName + "_" + method.signature.name
172
+ )
173
+ )
174
+ }
175
+ let syncMethods = methods.map {"export " + self.emitFunctionDefinition(_, False)}
176
+ let asyncMethods = self.withEmittingAsync {methods.map {"export " + self.emitFunctionDefinition(_, True)}}
177
+ [...syncMethods, ...asyncMethods].join("\n\n")
178
+ }
179
+
180
+ emitInstanceDefinition(definition: DInstance): String {
181
+ let name = makeDictionaryName(definition.traitName, firstTypeName(definition.typeArguments))
182
+ let methods = definition.methods.map {self.emitFunctionDefinition(_, False)}.map {_.dropFirst("function ".size())} // TODO
183
+ let asyncMethods = self.withEmittingAsync {
184
+ definition.methods.map {self.emitFunctionDefinition(_, True)}.map {"async " + _.dropFirst("async function ".size())} // TODO
185
+ }
186
+ let body = "{\n" + [...methods, ...asyncMethods].join(",\n") + "\n}"
187
+ definition.constraints.{
188
+ | [] =>
189
+ "export const " + name + " = " + body + ";"
190
+ | constraints =>
191
+ let dictionaries = constraints.map {c =>
192
+ makeDictionaryName(c.name, firstTypeName(c.generics))
193
+ }
194
+ "export function " + name + "(" + dictionaries.join(", ") + ") { return " + body + "}"
195
+ }
196
+ }
197
+
198
+ emitFunctionDefinition(definition: DFunction, async: Bool, suffix: String = ""): String {
199
+ let signature = self.emitSignature(definition.signature, async, suffix)
200
+ Pair(async, definition.body).{
201
+ | Pair(False, ForeignTarget(None, _)) =>
202
+ signature + " {\nthrow new Error('Function " + definition.signature.name + " is missing on this target in sync context.');\n}"
203
+ | Pair(True, ForeignTarget(_, None)) =>
204
+ signature + " {\nthrow new Error('Function " + definition.signature.name + " is missing on this target in async context.');\n}"
205
+ | Pair(False, ForeignTarget(Some(code), _)) =>
206
+ signature + " {\n" + self.jsImporter.process(definition.at, code) + "\n}"
207
+ | Pair(True, ForeignTarget(_, Some(code))) =>
208
+ signature + " {\n" + self.jsImporter.process(definition.at, code) + "\n}"
209
+ | Pair(_, FireflyTarget(lambda)) => lambda.{
210
+ | Lambda(_, effect, [matchCase]) {
211
+ matchCase.patterns.all {
212
+ | PVariable(_, None) => True
213
+ | _ => False
214
+ }
215
+ } =>
216
+ let body = self.emitTailCall {self.emitStatements(matchCase.body, True, async)}
217
+ signature + " {\n" + body + "\n}"
218
+ | Lambda(_, effect, cases) =>
219
+ Patterns.convertAndCheck(self.otherModules, cases)
220
+ let escapedArguments = definition.signature.parameters.map {_.name + "_a"}
221
+ let shadowingWorkaround = definition.signature.parameters.map {p =>
222
+ "const " + p.name + "_a = " + escapeKeyword(p.name) + ";"
223
+ }.join("\n")
224
+ let body = self.emitTailCall {
225
+ let casesString = cases.map {
226
+ self.emitCase(escapedArguments, _, [], [], True, True, async)
227
+ }.join("\n")
228
+ shadowingWorkaround + "\n" + casesString
229
+ }
230
+ signature + " {\n" + body + "\n}"
231
+ }
232
+ }
233
+ }
234
+
235
+ emitTailCall(body: () => String): String {
236
+ let outerTailCallUsed = self.tailCallUsed
237
+ self.tailCallUsed = False
238
+ let result = body()
239
+ let tailCallUsed = self.tailCallUsed
240
+ self.tailCallUsed = outerTailCallUsed
241
+ if(tailCallUsed) {
242
+ "_tailcall: for(;;) {\n" + result + "\nreturn\n}"
243
+ } else {
244
+ result
245
+ }
246
+ }
247
+
248
+ emitSignature(signature: Signature, async: Bool, suffix: String = ""): String {
249
+ let parameterStrings = signature.parameters.map {self.emitParameter(_, async)}
250
+ let dictionaryStrings = signature.constraints.map {c =>
251
+ makeDictionaryName(c.name, firstTypeName(c.generics))
252
+ }
253
+ let controller = if(async) {["$task"]} else {[]}
254
+ let parameters = "(" + [...parameterStrings, ...dictionaryStrings, ...controller].join(", ") + ")"
255
+ let prefix = if(async) {"async "} else {""}
256
+ let asyncSuffix = if(async) {"$"} else {""}
257
+ prefix + "function " + escapeKeyword(signature.name) + suffix + asyncSuffix + parameters
258
+ }
259
+
260
+ emitParameter(parameter: Parameter, async: Bool): String {
261
+ let defaultValue = parameter.default.map {" = " + self.emitTerm(_, async) }.else {""}
262
+ escapeKeyword(parameter.name) + defaultValue
263
+ }
264
+
265
+ emitTypeDefinition(definition: DType): String {
266
+ if(definition.newtype) {"// newtype " + definition.name} else:
267
+ "// type " + definition.name + "\n" +
268
+ definition.variants.map {self.emitVariantDefinition(definition, _)}.join("\n")
269
+ }
270
+
271
+ emitVariantDefinition(typeDefinition: DType, definition: Variant): String {
272
+ let allFields = [...typeDefinition.commonFields, ...definition.fields]
273
+ let fields = allFields.map {escapeKeyword(_.name)}.join(", ")
274
+ if(allFields.isEmpty()) {
275
+ "const " + definition.name + "$ = {" + definition.name + ": true};\n" +
276
+ "export function " + definition.name + "(" + fields + ") {\n" +
277
+ "return " + definition.name + "$;\n" +
278
+ "}"
279
+ } elseIf { typeDefinition.variants.size() == 1 } {
280
+ "export function " + definition.name + "(" + fields + ") {\n" +
281
+ "return {" + fields + "};\n" +
282
+ "}"
283
+ } else {
284
+ "export function " + definition.name + "(" + fields + ") {\n" +
285
+ "return {" + definition.name + ": true, " + fields + "};\n" +
286
+ "}"
287
+ }
288
+ }
289
+
290
+ emitTerm(term: Term, async: Bool): String {term.{
291
+ | EString(at, value) {value.startsWith("\"\"\"")} =>
292
+ "`" + value.dropFirst(3).dropLast(3).replace("`", "\\`") + "`" // TODO: Fix escaping
293
+ | EString(at, value) => value
294
+ | EChar(at, value) => charLiteralToNumber(value)
295
+ | EInt(at, value) => value
296
+ | EFloat(at, value) => value
297
+ | EVariable(at, name) => escapeResolved(name)
298
+ | EList(at, _, items) =>
299
+ self.emitList(items, async)
300
+ | EVariant(at, "ff:core/Bool.False", _, _) =>
301
+ "false"
302
+ | EVariant(at, "ff:core/Bool.True", _, _) =>
303
+ "true"
304
+ | EVariant(at, "ff:core/Unit.Unit", _, _) =>
305
+ "(void 0)"
306
+ | EVariant(at, name, _, arguments) =>
307
+ let argumentsString = arguments.toList().flatten().map {self.emitArgument(at, _, async)}.join(", ")
308
+ let newtype = self.processVariant(name)
309
+ if(newtype) {argumentsString} else:
310
+ escapeResolved(name) + "(" + argumentsString + ")"
311
+ | EVariantIs(at, "ff:core/Bool.False", _) =>
312
+ "function(_v) { return !_v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
313
+ | EVariantIs(at, "ff:core/Bool.True", _) =>
314
+ "function(_v) { return _v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
315
+ | EVariantIs(at, "ff:core/Unit.Unit", _) =>
316
+ "function(_v) { return ff_core_Option.Some(_v); }"
317
+ | EVariantIs(at, name, _) =>
318
+ let n = name.reverse().takeWhile { _ != '.' }.reverse()
319
+ "(function(_v) { " +
320
+ "return _v." + escapeResolved(n) + " ? ff_core_Option.Some(_v) : ff_core_Option.None();" +
321
+ "})"
322
+ | ECopy(at, name, record, fields) =>
323
+ let fieldCode = fields.map {f => escapeKeyword(f.name) + " = " + self.emitTerm(f.value, async)}.join(", ")
324
+ "{..." + self.emitTerm(record, async) + ", " + fieldCode + "}"
325
+ | EField(at, newtype, record, field) =>
326
+ if(newtype) {self.emitTerm(record, async)} else:
327
+ self.emitTerm(record, async) + "." + escapeKeyword(field)
328
+ | ELambda(at, Lambda(_, effect, [MatchCase(_, patterns, [], body)])) {
329
+ patterns.all {| PVariable _ => True | _ => False }
330
+ } =>
331
+ let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
332
+ let patternParameters = patterns.map {
333
+ | PVariable p => p.name.map(escapeKeyword).else {"_"}
334
+ | _ => panic("!")
335
+ }
336
+ let controller = if(newAsync) {["$task"]} else {[]}
337
+ let parameters = [...patternParameters, ...controller].join(", ")
338
+ let prefix = if(newAsync) {"async "} else {""}
339
+ "(" + prefix + "(" + parameters + ") => {\n" + self.emitStatements(body, True, newAsync) + "\n})"
340
+ | ELambda(at, Lambda(_, effect, cases)) =>
341
+ let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
342
+ let controller = if(newAsync) {["$task"]} else {[]}
343
+ Patterns.convertAndCheck(self.otherModules, cases)
344
+ let arguments = cases.grab(0).patterns.pairs().map {"_" + (_.first + 1)}
345
+ let escapedArguments = arguments.map(escapeKeyword) // emitCase arguments must be preescaped
346
+ let caseStrings = cases.map {self.emitCase(escapedArguments, _, [], [], True, True, newAsync)}
347
+ let prefix = if(newAsync) {"async "} else {""}
348
+ "(" + prefix + "(" + [...escapedArguments, ...controller].join(", ") + ") => " +
349
+ "{\n" + caseStrings.join("\n") + "\n})"
350
+ | EPipe(at, value, effect, function) =>
351
+ let await = async && effectTypeIsAsync(effect)
352
+ let c = if(await) {", $task"} else {""}
353
+ let call = "(" + self.emitTerm(function, async) + ")(" + self.emitTerm(value, async) + c + ")"
354
+ if(await) {"(await " + call + ")"} else {call}
355
+ | ECall(at, StaticCall(operator, _, _), _, [], [value], _) {!operator.grabFirst().isAsciiLetter()} =>
356
+ "(" + operator + self.emitArgument(at, value, async) + ")"
357
+ | ECall(at, StaticCall(operator, _, _), _, [], [left, right], _) {!operator.grabFirst().isAsciiLetter()} =>
358
+ "(" + self.emitArgument(at, left, async) + " " + operator + " " + self.emitArgument(at, right, async) + ")"
359
+ | ECall(at, StaticCall("ff:core/List.List_grab", _, _), _, _, [Argument(_, _, EVariable(_, x1)), Argument(_, _, EVariable(_, x2))], _) =>
360
+ "(" + escapeResolved(x1) + "[" + escapeResolved(x2) + "] ?? " +
361
+ "ff_core_List.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
362
+ | ECall(at, StaticCall("ff:core/Array.Array_grab", _, _), _, _, [Argument(_, _, EVariable(_, x1)), Argument(_, _, EVariable(_, x2))], _) =>
363
+ "(" + escapeResolved(x1) + ".array[" + escapeResolved(x2) + "] ?? " +
364
+ "ff_core_Array.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
365
+ | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.import", _, _), _, _, [Argument(_, _, EString(_, url))], _) =>
366
+ self.jsImporter.add(url.replace("\"", ""))
367
+ | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.await", _, _), _, _, [Argument(_, _, body)], _) =>
368
+ let emittedBody = self.emitTerm(body, async)
369
+ if(async) {"(await " + emittedBody + "($task))"} else {"(" + emittedBody + "())"}
370
+ | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.cancelled", _, _), _, _, [], _) =>
371
+ if(async) {"$task.controller.signal.aborted"} else {"false"}
372
+ | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.throwIfCancelled", _, _), _, _, [], _) =>
373
+ if(async) {"((() => ff_core_Task.Task_throwIfAborted($task))())"} else {""}
374
+ | ECall(at, StaticCall("ff:core/Equal.equals", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
375
+ primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
376
+ } =>
377
+ "(" + self.emitArgument(at, left, async) + " === " + self.emitArgument(at, right, async) + ")"
378
+ | ECall(at, StaticCall("ff:core/Equal.notEquals", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
379
+ primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
380
+ } =>
381
+ "(" + self.emitArgument(at, left, async) + " !== " + self.emitArgument(at, right, async) + ")"
382
+ | ECall(at, StaticCall("ff:core/Ordering.before", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
383
+ primitiveTypes.contains(typeName)
384
+ } =>
385
+ "(" + self.emitArgument(at, left, async) + " < " + self.emitArgument(at, right, async) + ")"
386
+ | ECall(at, StaticCall("ff:core/Ordering.notBefore", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
387
+ primitiveTypes.contains(typeName)
388
+ } =>
389
+ "(" + self.emitArgument(at, left, async) + " >= " + self.emitArgument(at, right, async) + ")"
390
+ | ECall(at, StaticCall("ff:core/Ordering.after", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
391
+ primitiveTypes.contains(typeName)
392
+ } =>
393
+ "(" + self.emitArgument(at, left, async) + " > " + self.emitArgument(at, right, async) + ")"
394
+ | ECall(at, StaticCall("ff:core/Ordering.notAfter", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
395
+ primitiveTypes.contains(typeName)
396
+ } =>
397
+ "(" + self.emitArgument(at, left, async) + " <= " + self.emitArgument(at, right, async) + ")"
398
+ | ECall(_, StaticCall("ff:core/List.fillBy", _, _), effect, _, [size, Argument(_, _, ELambda(at,
399
+ Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)@c])@l
400
+ ))], _) {
401
+ !effectTypeIsAsync(effect)
402
+ } =>
403
+ let n = name.map {escapeResolved(_)}.else {"i"}
404
+ let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
405
+ let await = if(newAsync) {"await "} else {""}
406
+ await + "((() => {\n" +
407
+ "const size = " + self.emitArgument(at, size, async) + ";\n" + // Not correct if async and body isn't
408
+ "const result = [];\n" +
409
+ "for(let " + n + " = 0; " + n + " < size; " + n + "++) {\n" +
410
+ "result.push(" + self.emitTerm(body, newAsync) + ");\n" +
411
+ "}\n" +
412
+ "return result;\n" +
413
+ "})())"
414
+ | ECall(at, StaticCall(name, _, True), effect, typeArguments, arguments, dictionaries) =>
415
+ let await = async && effectTypeIsAsync(effect)
416
+ let dictionaryStrings = dictionaries.map {self.emitDictionary(_)}
417
+ let ds = dictionaryStrings.dropFirst()
418
+ let d = dictionaryStrings.grabFirst()
419
+ let asyncSuffix = if(await) {"$"} else {""}
420
+ let n = escapeKeyword(name.reverse().takeWhile {_ != '.'}.reverse()) + asyncSuffix
421
+ let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
422
+ let controller = if(await) {["$task"]} else {[]}
423
+ let call = d + "." + n + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
424
+ if(await) {"(await " + call + ")"} else {call}
425
+ | ECall(at, StaticCall(name, _, _), effect, typeArguments, arguments, dictionaries) =>
426
+ if(name.contains("bundleForBrowser")) { // TODO: Delete this test (for branch arraysonly)
427
+ if(!arguments.grab(0).name.contains("system")) {
428
+ Log.debug("Wrong arguments for bundleForBrowser: " + Show.show(arguments.map {_.name}))
429
+ throw(GrabException())
430
+ }
431
+ }
432
+ detectIfElse(term).{
433
+ | [] =>
434
+ let await = async && effectTypeIsAsync(effect)
435
+ let ds = dictionaries.map {self.emitDictionary(_)}
436
+ let functionCode = escapeResolved(name) + if(await) {"$"} else {""}
437
+ let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
438
+ let controller = if(await) {["$task"]} else {[]}
439
+ let call = functionCode + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
440
+ if(await) {"(await " + call + ")"} else {call}
441
+ | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
442
+ "(" + list.foldLeft(self.emitTerm(elseBody, async)) {| otherwise, Pair(condition, body) =>
443
+ self.emitTerm(condition, async) +
444
+ "\n? " + self.emitTerm(body, async) + "\n: " + otherwise
445
+ } + ")"
446
+ | list =>
447
+ "(" + list.foldLeft("ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
448
+ self.emitTerm(condition, async) +
449
+ "\n? ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n: " + otherwise
450
+ } + ")"
451
+ }
452
+ | ECall(at, DynamicCall(function, _), effect, typeArguments, arguments, dictionaries) =>
453
+ let await = async && effectTypeIsAsync(effect)
454
+ if(!dictionaries.isEmpty()) {fail(at, "Internal error: Dictionaries in lambda call")}
455
+ let functionCode = self.emitTerm(function, async)
456
+ let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
457
+ let controller = if(await) {["$task"]} else {[]}
458
+ let call = functionCode + "(" + [...emittedArguments, ...controller].join(", ") + ")"
459
+ if(await) {"(await " + call + ")"} else {call}
460
+ | ERecord(at, fields) =>
461
+ if(fields.isEmpty()) {"{}"} else {
462
+ let list = fields.map {f => escapeKeyword(f.name) + ": " + self.emitTerm(f.value, async)}
463
+ "{\n" + list.join(",\n") + "\n}"
464
+ }
465
+ | EWildcard(at, index) =>
466
+ if(index == 0) {fail(at, "Unbound wildcard")}
467
+ "_w" + index
468
+ | _ {async} =>
469
+ "(await (async function() {\n" + self.emitStatements(term, True, async) + "\n})())"
470
+ | _ =>
471
+ "(function() {\n" + self.emitStatements(term, True, async) + "\n})()"
472
+ }}
473
+
474
+ emitDictionary(d: Dictionary): String {
475
+ let m = if(d.moduleName != "") {
476
+ d.packagePair.groupName("_") + "_" + d.moduleName.replace("/", "_") + "."
477
+ } else {""}
478
+ let c = m + makeDictionaryName(d.traitName, d.typeName)
479
+ if(d.dictionaries.isEmpty()) {
480
+ c
481
+ } else {
482
+ c + "(" + d.dictionaries.map {self.emitDictionary(_)}.join(", ") + ")"
483
+ }
484
+ }
485
+
486
+ emitStatements(term: Term, last: Bool, async: Bool): String {
487
+ term.{
488
+ | EFunctions(at, functions, body) =>
489
+ let functionStrings = functions.map {f =>
490
+ let newAsync = self.emittingAsync && effectTypeIsAsync(f.signature.effect)
491
+ self.emitFunctionDefinition(f, newAsync)
492
+ }
493
+ functionStrings.join("\n") + "\n" + self.emitStatements(body, last, async)
494
+ | ELet(at, mutable, name, valueType, value, body) =>
495
+ self.emitLetDefinition(DLet(at, name, valueType, value), mutable, async) + "\n" +
496
+ self.emitStatements(body, last, async)
497
+ | EVariant(at, "ff:core/Unit.Unit", _, _) =>
498
+ ""
499
+ | ESequential(_, EVariant(_, "ff:core/Unit.Unit", _, _), after) =>
500
+ self.emitStatements(after, last, async)
501
+ | ESequential(_, before, EVariant(_, "ff:core/Unit.Unit", _, _)) =>
502
+ self.emitStatements(before, False, async)
503
+ | ESequential(at, before, after) =>
504
+ self.emitStatements(before, False, async) + ";\n" + self.emitStatements(after, last, async)
505
+ | EAssign(at, operator, name, value) =>
506
+ escapeKeyword(name) + " " + operator + "= " + self.emitTerm(value, async)
507
+ | EAssignField(at, operator, record, field, value) =>
508
+ self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
509
+ self.emitTerm(value, async)
510
+ | ECall(at, StaticCall("ff:core/Core.while", _, _), _, _, [condition, body], _) =>
511
+ "while(" + self.emitTerm(invokeImmediately(condition.value), async) + ") {\n" +
512
+ self.emitStatements(invokeImmediately(body.value), False, async) + "\n}"
513
+ | ECall(at, StaticCall("ff:core/Core.doWhile", _, _), _, _, [Argument(_, _, doWhileBody)], _) {
514
+ invokeImmediately(doWhileBody) | ESequential(_, body, condition)
515
+ } =>
516
+ "while(true) {\n" +
517
+ self.emitStatements(body, False, async) +
518
+ "\nif(!" + self.emitTerm(condition, async) + ") break" +
519
+ "\n}"
520
+ | ECall(at, StaticCall("ff:core/Core.doWhile", _, _), _, _, [Argument(_, _, doWhileBody)], _) {
521
+ invokeImmediately(doWhileBody) | body
522
+ } =>
523
+ "while(" + self.emitTerm(body, async) + ") {}"
524
+ | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
525
+ "if(" + self.emitTerm(condition.value, async) + ") {\n" +
526
+ if(last) {
527
+ "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body.value), async) +
528
+ ")\n} else return ff_core_Option.None()"
529
+ } else {
530
+ self.emitStatements(invokeImmediately(body.value), False, async) + "\n}"
531
+ }
532
+ | ECall(at, StaticCall("ff:core/Core.throw", _, _), _, _, [argument], [dictionary]) =>
533
+ let d = self.emitDictionary(dictionary)
534
+ let a = self.emitArgument(at, argument, async)
535
+ "throw Object.assign(new Error(), {ffException: ff_core_Any.toAny_(" + a + ", " + d + ")})"
536
+ | ECall(at, StaticCall("ff:core/Core.try", _, _), _, _, _, _) {!last} =>
537
+ throw(CompileError(at, "Statements can't be a try without a grab"))
538
+ | ECall(at, StaticCall("ff:core/Try.Try_map", _, _), _, _, _, _) {!last} =>
539
+ throw(CompileError(at, "Statements can't be a map without a grab"))
540
+ | ECall(at, StaticCall("ff:core/Try.Try_flatMap", _, _), _, _, _, _) {!last} =>
541
+ throw(CompileError(at, "Statements can't be a flatMap without a grab"))
542
+ | ECall(at, StaticCall("ff:core/Try.Try_flatten", _, _), _, _, _, _) {!last} =>
543
+ throw(CompileError(at, "Statements can't be a flatten without a grab"))
544
+ | ECall(at, StaticCall("ff:core/Try.Try_catch", _, _), _, _, _, _) {!last} =>
545
+ throw(CompileError(at, "Statements can't be a catch without a grab"))
546
+ | ECall(at, StaticCall("ff:core/Try.Try_catchAny", _, _), _, _, _, _) {!last} =>
547
+ throw(CompileError(at, "Statements can't be a catchAny without a grab"))
548
+ | ECall(at, StaticCall("ff:core/Try.Try_finally", _, _), _, _, _, _) {!last} =>
549
+ throw(CompileError(at, "Statements can't be a finally without a grab"))
550
+ | ECall(at, StaticCall("ff:core/Try.Try_grab", _, _), _, _, [argument], _) {
551
+ self.emitTryCatchFinally(argument.value, last, async) | Some(code)
552
+ } =>
553
+ code
554
+ | ECall(at, StaticCall("ff:unsafejs/UnsafeJs.throwIfCancelled", _, _), _, _, [], _) =>
555
+ if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""}
556
+ | ECall(at, StaticCall(name, True, instanceCall), effect, _, arguments, _) =>
557
+ if(instanceCall) {throw(CompileError(at, "Not yet implemented: Tail calls on trait methods."))}
558
+ self.tailCallUsed = True
559
+ let pair = arguments.map {a =>
560
+ Some(Pair(
561
+ "const " + escapeKeyword(a.name.grab() + "_r") + " = " + self.emitTerm(a.value, async) + ";"
562
+ escapeKeyword(a.name.grab()) + " = " + escapeKeyword(a.name.grab() + "_r")
563
+ ))
564
+ }.collect {_}.unzip()
565
+ "{\n" + pair.first.join("\n") + "\n" + pair.second.join("\n") + "\ncontinue _tailcall\n}"
566
+ | EPipe(at, value, _, ELambda(_, Lambda(_, _, cases))) =>
567
+ Patterns.convertAndCheck(self.otherModules, cases)
568
+ if(!last) {"do "}.else {""} +
569
+ "{\nconst _1 = " + self.emitTerm(value, async) + ";\n" +
570
+ cases.map {self.emitCase(["_1"], _, [], [], True, last, async)}.join("\n") +
571
+ "\n}" + if(!last) {" while(false)"}.else {""}
572
+ | _ =>
573
+ detectIfElse(term).{
574
+ | [] =>
575
+ if(last) {"return " + self.emitTerm(term, async)} else {self.emitTerm(term, async)}
576
+ | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
577
+ let initial = "{\n" + self.emitStatements(elseBody, last, async) + "\n}"
578
+ list.foldLeft(initial) {| otherwise, Pair(condition, body) =>
579
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
580
+ self.emitStatements(body, last, async) + "\n} else " + otherwise
581
+ }
582
+ | list {!last} =>
583
+ list.foldLeft("{}") {| otherwise, Pair(condition, body) =>
584
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
585
+ self.emitStatements(body, last, async) + "\n} else " + otherwise
586
+ }
587
+ | list =>
588
+ list.foldLeft("return ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
589
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
590
+ "return ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n} else " + otherwise
591
+ }
592
+ }
593
+ }
594
+ }
595
+
596
+ emitTryCatchFinally(term: Term, last: Bool, async: Bool): Option[String] {
597
+ function emitCatch(catchEffect: Type, cases: List[MatchCase]): String {
598
+ let catchAsync = self.emittingAsync && effectTypeIsAsync(catchEffect)
599
+ Patterns.convertAndCheck(self.otherModules, cases)
600
+ let arguments = ["_exception.value_", "_error"]
601
+ cases.{
602
+ | [case] =>
603
+ self.emitCase(arguments, case, [], [], False, last, catchAsync)
604
+ | cs =>
605
+ let caseStrings =
606
+ cases.map {self.emitCase(arguments, _, [], [], True, last, catchAsync)}
607
+ if(last) {caseStrings.join("\n")} else {"do {\n" + caseStrings.join("\n") + "\n} while(false)"}
608
+ }
609
+ }
610
+ term.{
611
+ | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
612
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
613
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
614
+ ], _))
615
+ Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
616
+ ], _) =>
617
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
618
+ let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
619
+ Some(
620
+ "try {\n" + self.emitStatements(tryBody, last, tryAsync) +
621
+ "\n} finally {\n" + self.emitStatements(finallyBody, last, finallyAsync) + "\n}"
622
+ )
623
+ | ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
624
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
625
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
626
+ ], _))
627
+ Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
628
+ ], [dictionary]) =>
629
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
630
+ let d = self.emitDictionary(dictionary)
631
+ Some(
632
+ "try {\n" + self.emitStatements(tryBody, last, tryAsync) +
633
+ "\n} catch(_error) {\n" +
634
+ "if(!_error.ffException) throw _error\n" +
635
+ "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
636
+ "if(!_exception.Some) throw _error\n" +
637
+ emitCatch(catchEffect, cases) +
638
+ "\n}"
639
+ )
640
+ | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
641
+ Argument(_, _, ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
642
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
643
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
644
+ ], _))
645
+ Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
646
+ ], [dictionary]))
647
+ Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
648
+ ], _) =>
649
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
650
+ let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
651
+ let d = self.emitDictionary(dictionary)
652
+ Some(
653
+ "try {\n" + self.emitStatements(tryBody, last, tryAsync) +
654
+ "\n} catch(_error) {\n" +
655
+ "if(!_error.ffException) throw _error\n" +
656
+ "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
657
+ "if(!_exception.Some) throw _error\n" +
658
+ emitCatch(catchEffect, cases) +
659
+ "\n} finally {\n" + self.emitStatements(finallyBody, last, finallyAsync) + "\n}"
660
+ )
661
+ | _ =>
662
+ None
663
+ }
664
+ }
665
+
666
+ emitCase(
667
+ arguments: List[String]
668
+ matchCase: MatchCase
669
+ conditions: List[String]
670
+ variables: List[String]
671
+ jump: Bool
672
+ last: Bool
673
+ async: Bool
674
+ ): String {
675
+ function emitWrapper(code: String): String {
676
+ if(conditions.isEmpty()) {"{\n"} else {
677
+ "if(" + conditions.join(" && ") + ") {\n"
678
+ } +
679
+ variables.join() +
680
+ code +
681
+ "\n}"
682
+ }
683
+ Pair(matchCase.patterns, matchCase.guards).{
684
+ | Pair([p, ...ps], _) =>
685
+ self.emitPattern(
686
+ arguments.grab(0)
687
+ p
688
+ arguments.dropFirst()
689
+ matchCase.MatchCase(patterns = ps)
690
+ conditions
691
+ variables
692
+ jump
693
+ last
694
+ async
695
+ )
696
+ | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) {variables.isEmpty()} =>
697
+ let newCase = matchCase.MatchCase(patterns = [], guards = [])
698
+ self.emitCase([], newCase, [...conditions, self.emitTerm(e, async)], [], jump, last, async)
699
+ | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) =>
700
+ let newCase = matchCase.MatchCase(patterns = [], guards = [])
701
+ let code = self.emitCase([], newCase, [self.emitTerm(e, async)], [], jump, last, async)
702
+ emitWrapper(code)
703
+ | Pair([], [guard, ...guards]) =>
704
+ let guardName = "_guard" + (guards.size() + 1)
705
+ let newCase = matchCase.MatchCase(patterns = [guard.pattern], guards = guards)
706
+ let code =
707
+ "const " + guardName + " = " + self.emitTerm(guard.term, async) + ";\n" +
708
+ self.emitCase([guardName], newCase, [], [], jump, last, async)
709
+ emitWrapper(code)
710
+ | Pair([], []) =>
711
+ let statementsCode = self.emitStatements(matchCase.body, last, async)
712
+ let lastLine = statementsCode.reverse().takeWhile {_ != '\n'}.reverse()
713
+ let returns =
714
+ lastLine.startsWith("return ") ||
715
+ lastLine.startsWith("break ") ||
716
+ lastLine.startsWith("continue ") ||
717
+ lastLine.startsWith("return;") ||
718
+ lastLine.startsWith("break;") ||
719
+ lastLine.startsWith("continue;") ||
720
+ lastLine.startsWith("throw ")
721
+ let code = statementsCode + if(jump && last && !returns) {
722
+ "\nreturn"
723
+ } elseIf {jump && !returns} {
724
+ "\nbreak"
725
+ } else {
726
+ ""
727
+ }
728
+ emitWrapper(code)
729
+ }
730
+ }
731
+
732
+ emitPattern(
733
+ argument: String
734
+ pattern: MatchPattern
735
+ arguments: List[String]
736
+ matchCase: MatchCase
737
+ conditions: List[String]
738
+ variables: List[String]
739
+ jump: Bool
740
+ last: Bool
741
+ async: Bool
742
+ ): String {
743
+ pattern.{
744
+ | PString(_, value) =>
745
+ let newConditions = [...conditions, argument + " === " + value]
746
+ self.emitCase(arguments, matchCase, newConditions, variables, jump, last, async)
747
+ | PInt(_, value) =>
748
+ let newConditions = [...conditions, argument + " === " + value]
749
+ self.emitCase(arguments, matchCase, newConditions, variables, jump, last, async)
750
+ | PChar(_, value) =>
751
+ let newConditions = [...conditions, argument + " === " + charLiteralToNumber(value)]
752
+ self.emitCase(arguments, matchCase, newConditions, variables, jump, last, async)
753
+ | PVariable(_, None) =>
754
+ self.emitCase(arguments, matchCase, conditions, variables, jump, last, async)
755
+ | PVariable(_, Some(name)) =>
756
+ let escaped = escapeKeyword(name)
757
+ let newVariables = if(escaped != argument) {
758
+ [...variables, "const " + escaped + " = " + argument + ";\n"]
759
+ } else {variables}
760
+ self.emitCase(arguments, matchCase, conditions, newVariables, jump, last, async)
761
+ | PVariant(_, "ff:core/Bool.False", []) =>
762
+ self.emitCase(arguments, matchCase, [...conditions, "!" + argument], variables, jump, last, async)
763
+ | PVariant(_, "ff:core/Bool.True", []) =>
764
+ self.emitCase(arguments, matchCase, [...conditions, argument], variables, jump, last, async)
765
+ | PVariant(_, emptyOrLink, _) {emptyOrLink == "List$Empty" || emptyOrLink == "List$Link"} =>
766
+ mutable restPattern = None
767
+ function listPatterns(matchPattern: MatchPattern): List[MatchPattern] {
768
+ | PVariant(_, "List$Empty", []) =>
769
+ []
770
+ | PVariant(_, "List$Link", [head, tail]) =>
771
+ [head, ...listPatterns(tail)]
772
+ | p =>
773
+ restPattern = Some(p)
774
+ []
775
+ }
776
+ let patterns = listPatterns(pattern)
777
+ let itemArguments = patterns.pairs().map {| Pair(i, _) => argument + "[" + i + "]"}
778
+ let restArgument = restPattern.map {_ => argument + ".slice(" + patterns.size() + ")"}
779
+ let newArguments = [...itemArguments, ...restArgument.toList(), ...arguments]
780
+ let newMatchCase = matchCase.MatchCase(
781
+ patterns = [...patterns, ...restPattern.toList(), ...matchCase.patterns]
782
+ )
783
+ let operator = restPattern.map {_ => ">="}.else {"==="}
784
+ let newConditions = [...conditions, argument + ".length " + operator + " " + patterns.size()]
785
+ self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, async)
786
+ | PVariant(_, name, patterns) =>
787
+ let processed = self.processVariantCase(name, argument)
788
+ let newMatchCase = matchCase.MatchCase(patterns = [...patterns, ...matchCase.patterns])
789
+ let newConditions = if(processed.loneVariant) {conditions} else {
790
+ [...conditions, argument + "." + processed.variantName]
791
+ }
792
+ let newArguments = [...processed.arguments, ...arguments]
793
+ self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, async)
794
+ | PVariantAs(at, name, variableAt, variable) =>
795
+ let processed = self.processVariantCase(name, argument)
796
+ let newConditions = if(processed.loneVariant) {conditions} else {
797
+ [...conditions, argument + "." + processed.variantName]
798
+ }
799
+ let newVariables = variable.map(escapeKeyword).filter {_ != argument}.map {
800
+ [...variables, "const " + _ + " = " + argument + ";\n"]
801
+ }.else {[]}
802
+ self.emitCase(arguments, matchCase, newConditions, newVariables, jump, last, async)
803
+ | PAlias(_, pattern, variable) =>
804
+ let escaped = escapeKeyword(variable)
805
+ let newVariables = if(escaped != argument) {
806
+ [...variables, "const " + escaped + " = " + argument + ";\n"]
807
+ } else {variables}
808
+ self.emitPattern(argument, pattern, arguments, matchCase, conditions, newVariables, jump, last, async)
809
+ }
810
+ }
811
+
812
+ emitList(items: List[Pair[Term, Bool]], async: Bool): String {
813
+ "[" + items.map {
814
+ | Pair(item, False) => self.emitTerm(item, async)
815
+ | Pair(item, True) => "..." + self.emitTerm(item, async)
816
+ }.join(", ") + "]"
817
+ }
818
+
819
+ processVariantCase(name: String, argument: String): ProcessedVariantCase {
820
+ let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
821
+ let variantName = escapeKeyword(variantNameUnqualified)
822
+ let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
823
+ let variantModule = self.otherModules.grab(moduleName)
824
+ mutable newtype = False
825
+ mutable loneVariant = False
826
+ let newArguments = variantModule.types.collectFirst {definition =>
827
+ definition.variants.find {_.name == variantName }.map {variant =>
828
+ newtype = definition.newtype
829
+ loneVariant = definition.variants.size() == 1
830
+ [...definition.commonFields.map {_.name}, ...variant.fields.map {_.name}]
831
+ }
832
+ }.grab().map {field => if(newtype) {argument} else {argument + "." + escapeKeyword(field)}}
833
+ ProcessedVariantCase(variantName, newtype, loneVariant, newArguments)
834
+ }
835
+
836
+ processVariant(name: String): Bool {
837
+ if(name.startsWith("List$")) {False} else:
838
+ let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
839
+ let variantName = escapeKeyword(variantNameUnqualified)
840
+ let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
841
+ let variantModule = self.otherModules.grab(moduleName)
842
+ mutable newtype = False
843
+ let newArguments = variantModule.types.collectFirst {definition =>
844
+ definition.variants.find {_.name == variantName}.map {variant =>
845
+ newtype = definition.newtype
846
+ }
847
+ }.grab()
848
+ newtype
849
+ }
850
+
851
+ emitArgument(callAt: Location, argument: Argument, async: Bool): String {
852
+ argument.value.{
853
+ | ECall(_, StaticCall("ff:core/SourceLocation.callSite", _, _), _, _, _, _) =>
854
+ "\"" + self.moduleName + ":" + callAt.line + ":" + callAt.column +
855
+ "," + self.packagePair.group + "," + self.packagePair.name + "\""
856
+ | value =>
857
+ self.emitTerm(value, async)
858
+ }
859
+ }
860
+
861
+ }
862
+
863
+ data ProcessedVariantCase(
864
+ variantName: String
865
+ newtype: Bool
866
+ loneVariant: Bool
867
+ arguments: List[String]
868
+ )
869
+
870
+ detectIfElse(term: Term): List[Pair[Term, Term]] {
871
+ | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
872
+ [Pair(condition.value, invokeImmediately(body.value))]
873
+ | ECall(at, StaticCall("ff:core/Option.Option_elseIf", _, _), _, _, [option, condition, body], _) =>
874
+ let list = detectIfElse(option.value)
875
+ if(list.isEmpty()) {[]} else:
876
+ [Pair(invokeImmediately(condition.value), invokeImmediately(body.value)), ...list]
877
+ | ECall(at, StaticCall("ff:core/Option.Option_else", _, _), _, _, [option, body], _) =>
878
+ let list = detectIfElse(option.value)
879
+ if(list.isEmpty()) {[]} else:
880
+ [Pair(EVariant(at, "ff:core/Bool.True", [], None), invokeImmediately(body.value)), ...list]
881
+ | _ =>
882
+ []
883
+ }
884
+
885
+ invokeImmediately(function: Term): Term {
886
+ | ELambda(_, Lambda(_, effect, [MatchCase(_, [], [], body)])) =>
887
+ body
888
+ | _ =>
889
+ let effect = TConstructor(function.at, "Q$", []) // Awaits more often than required in async context
890
+ ECall(function.at, DynamicCall(function, False), effect, [], [], [])
891
+ }
892
+
893
+ extractTypeName(type: Type): String {
894
+ | TVariable(at, index) =>
895
+ fail(at, "Unexpected type variable: $" + index)
896
+ | TConstructor t =>
897
+ t.name
898
+ }
899
+
900
+ firstTypeName(types: List[Type]): String {
901
+ types.grabFirst().{
902
+ | TConstructor t => t.name
903
+ | TVariable t => fail(t.at, " is still a unification variable")
904
+ }
905
+ }
906
+
907
+ makeDictionaryName(traitName: String, typeName: String): String {
908
+ traitName.replace(".", "_").replace(":", "_").replace("/", "_") + "$" +
909
+ typeName.replace(".", "_").replace(":", "_").replace("/", "_")
910
+ }
911
+
912
+ charLiteralToNumber(charLiteral: String): String {
913
+ | "'\\t'" => "9"
914
+ | "'\\n'" => "10"
915
+ | "'\\r'" => "13"
916
+ | "'\\\"'" => "34"
917
+ | "'\\''" => "39"
918
+ | value => "" + value.grab(1).codeUnit
919
+ }
920
+
921
+ escapeResolved(word: String): String {
922
+ let parts = word.replace(":", ".").replace("/", ".").split('.')
923
+ let initialParts = parts.dropLast()
924
+ if(initialParts.isEmpty()) {
925
+ escapeKeyword(parts.grabLast())
926
+ } else {
927
+ initialParts.join("_") + "." + escapeKeyword(parts.grabLast())
928
+ }
929
+ }
930
+
931
+ escapeKeyword(word: String): String {
932
+ if(word.grabFirst().isAsciiLower()) {word + "_"} else {word}
933
+ }
934
+
935
+ effectTypeIsAsync(effect: Type): Bool {
936
+ | TConstructor(_, "Q$", _) => True
937
+ | _ => False
938
+ }
939
+
940
+ primitiveTypes = [
941
+ "ff:core/Bool.Bool"
942
+ "ff:core/Char.Char"
943
+ "ff:core/Int.Int"
944
+ "ff:core/Float.Float"
945
+ "ff:core/String.String"
946
+ ].toSet()