firefly-compiler 0.4.36 → 0.4.46

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 (116) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +99 -49
  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 +186 -186
  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 +394 -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 -37
  21. package/core/Buffer.ff +3 -3
  22. package/core/BuildSystem.ff +148 -145
  23. package/core/Crypto.ff +96 -95
  24. package/core/Equal.ff +36 -36
  25. package/core/HttpClient.ff +87 -87
  26. package/core/Instant.ff +17 -0
  27. package/core/JsSystem.ff +69 -69
  28. package/core/Json.ff +434 -434
  29. package/core/List.ff +415 -415
  30. package/core/Lock.ff +144 -144
  31. package/core/NodeSystem.ff +189 -189
  32. package/core/Ordering.ff +161 -161
  33. package/core/Path.ff +401 -401
  34. package/core/Random.ff +134 -134
  35. package/core/RbMap.ff +216 -216
  36. package/core/Show.ff +43 -43
  37. package/core/SourceLocation.ff +68 -68
  38. package/core/Stream.ff +1 -1
  39. package/core/Task.ff +141 -141
  40. package/experimental/benchmarks/ListGrab.ff +23 -23
  41. package/experimental/benchmarks/ListGrab.java +55 -55
  42. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  43. package/experimental/benchmarks/Pyrotek45.java +64 -64
  44. package/experimental/bidirectional/Bidi.ff +88 -88
  45. package/experimental/random/Index.ff +53 -53
  46. package/experimental/random/Process.ff +120 -120
  47. package/experimental/random/RunLength.ff +3 -3
  48. package/experimental/random/Scrape.ff +51 -51
  49. package/experimental/random/Symbols.ff +73 -73
  50. package/experimental/random/Tensor.ff +52 -52
  51. package/experimental/random/Units.ff +36 -36
  52. package/experimental/s3/S3TestAuthorizationHeader.ff +38 -38
  53. package/experimental/s3/S3TestPut.ff +15 -15
  54. package/experimental/tests/TestJson.ff +26 -26
  55. package/firefly.sh +0 -0
  56. package/fireflysite/Main.ff +13 -13
  57. package/lsp/.firefly/package.ff +1 -1
  58. package/lsp/CompletionHandler.ff +811 -811
  59. package/lsp/Handler.ff +714 -714
  60. package/lsp/HoverHandler.ff +79 -79
  61. package/lsp/LanguageServer.ff +272 -272
  62. package/lsp/SignatureHelpHandler.ff +55 -55
  63. package/lsp/SymbolHandler.ff +181 -181
  64. package/lsp/TestReferences.ff +16 -16
  65. package/lsp/TestReferencesCase.ff +7 -7
  66. package/lsp/stderr.txt +1 -1
  67. package/lsp/stdout.txt +34 -34
  68. package/lux/.firefly/package.ff +1 -1
  69. package/lux/Css.ff +648 -648
  70. package/lux/CssTest.ff +48 -48
  71. package/lux/Lux.ff +487 -487
  72. package/lux/LuxEvent.ff +116 -116
  73. package/lux/Main.ff +128 -128
  74. package/lux/Main2.ff +144 -144
  75. package/output/js/ff/compiler/Builder.mjs +43 -43
  76. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  77. package/output/js/ff/core/Array.mjs +59 -59
  78. package/output/js/ff/core/Atomic.mjs +36 -36
  79. package/output/js/ff/core/BrowserSystem.mjs +19 -11
  80. package/output/js/ff/core/Buffer.mjs +7 -7
  81. package/output/js/ff/core/BuildSystem.mjs +38 -30
  82. package/output/js/ff/core/Crypto.mjs +67 -68
  83. package/output/js/ff/core/HttpClient.mjs +24 -24
  84. package/output/js/ff/core/Instant.mjs +38 -0
  85. package/output/js/ff/core/Json.mjs +147 -147
  86. package/output/js/ff/core/List.mjs +50 -50
  87. package/output/js/ff/core/Lock.mjs +97 -97
  88. package/output/js/ff/core/NodeSystem.mjs +77 -77
  89. package/output/js/ff/core/Ordering.mjs +8 -8
  90. package/output/js/ff/core/Path.mjs +231 -231
  91. package/output/js/ff/core/Random.mjs +56 -56
  92. package/output/js/ff/core/Stream.mjs +2 -2
  93. package/output/js/ff/core/Task.mjs +31 -31
  94. package/package.json +29 -29
  95. package/rpc/.firefly/package.ff +1 -1
  96. package/rpc/Rpc.ff +69 -69
  97. package/s3/.firefly/package.ff +1 -0
  98. package/{experimental/s3 → s3}/S3.ff +92 -92
  99. package/unsafejs/UnsafeJs.ff +19 -19
  100. package/vscode/LICENSE.txt +21 -21
  101. package/vscode/Prepublish.ff +15 -15
  102. package/vscode/README.md +16 -16
  103. package/vscode/client/package.json +22 -22
  104. package/vscode/client/src/extension.ts +104 -104
  105. package/vscode/icons/firefly-icon.svg +10 -10
  106. package/vscode/language-configuration.json +61 -61
  107. package/vscode/package-lock.json +3623 -3623
  108. package/vscode/package.json +160 -160
  109. package/vscode/snippets.json +241 -241
  110. package/webserver/.firefly/include/package-lock.json +16 -16
  111. package/webserver/.firefly/include/package.json +5 -5
  112. package/webserver/.firefly/package.ff +2 -2
  113. package/webserver/WebServer.ff +685 -685
  114. package/websocket/.firefly/package.ff +1 -1
  115. package/websocket/WebSocket.ff +131 -131
  116. package/crypto/SubtleCrypto.ff +0 -149
@@ -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()