firefly-compiler 0.5.38 → 0.5.40

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 (128) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +157 -157
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +275 -276
  6. package/compiler/Compiler.ff +234 -234
  7. package/compiler/Dependencies.ff +186 -186
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/JsEmitter.ff +1437 -1437
  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.json +5 -5
  14. package/core/.firefly/package.ff +2 -2
  15. package/core/Any.ff +25 -25
  16. package/core/Array.ff +298 -298
  17. package/core/Atomic.ff +63 -63
  18. package/core/Box.ff +7 -7
  19. package/core/BrowserSystem.ff +40 -40
  20. package/core/BuildSystem.ff +156 -156
  21. package/core/Crypto.ff +94 -94
  22. package/core/Equal.ff +41 -41
  23. package/core/Error.ff +25 -25
  24. package/core/HttpClient.ff +142 -142
  25. package/core/Instant.ff +24 -24
  26. package/core/Js.ff +305 -305
  27. package/core/JsSystem.ff +135 -135
  28. package/core/Json.ff +423 -423
  29. package/core/List.ff +482 -482
  30. package/core/Lock.ff +108 -108
  31. package/core/NodeSystem.ff +198 -198
  32. package/core/Ordering.ff +160 -160
  33. package/core/Path.ff +377 -377
  34. package/core/Queue.ff +90 -90
  35. package/core/Random.ff +140 -140
  36. package/core/RbMap.ff +216 -216
  37. package/core/Show.ff +44 -44
  38. package/core/SourceLocation.ff +68 -68
  39. package/core/Task.ff +165 -165
  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/lines/Main.ff +40 -40
  46. package/experimental/random/Index.ff +53 -53
  47. package/experimental/random/Process.ff +120 -120
  48. package/experimental/random/RunLength.ff +65 -65
  49. package/experimental/random/Scrape.ff +51 -51
  50. package/experimental/random/Symbols.ff +73 -73
  51. package/experimental/random/Tensor.ff +52 -52
  52. package/experimental/random/Units.ff +36 -36
  53. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
  54. package/experimental/s3/S3TestPut.ff +16 -16
  55. package/experimental/tests/TestJson.ff +26 -26
  56. package/firefly.sh +0 -0
  57. package/fireflysite/.firefly/package.ff +4 -4
  58. package/fireflysite/CommunityOverview.ff +20 -20
  59. package/fireflysite/CountingButtonDemo.ff +58 -58
  60. package/fireflysite/DocumentParser.ff +325 -325
  61. package/fireflysite/ExamplesOverview.ff +40 -40
  62. package/fireflysite/FrontPage.ff +344 -344
  63. package/fireflysite/GettingStarted.ff +45 -45
  64. package/fireflysite/Guide.ff +456 -456
  65. package/fireflysite/Main.ff +163 -163
  66. package/fireflysite/MatchingPasswordsDemo.ff +82 -82
  67. package/fireflysite/PackagesOverview.ff +49 -49
  68. package/fireflysite/PostgresqlDemo.ff +34 -34
  69. package/fireflysite/ReferenceAll.ff +18 -18
  70. package/fireflysite/ReferenceIntroduction.ff +11 -11
  71. package/fireflysite/Styles.ff +567 -567
  72. package/fireflysite/Test.ff +121 -121
  73. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -209
  74. package/fireflysite/assets/markdown/reference/EmittedJavascript.md +65 -65
  75. package/fireflysite/assets/markdown/reference/Exceptions.md +101 -101
  76. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +364 -364
  77. package/fireflysite/assets/markdown/reference/JavascriptInterop.md +235 -235
  78. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -162
  79. package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -48
  80. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -224
  81. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -86
  82. package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -99
  83. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -100
  84. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -184
  85. package/fireflysite/assets/markdown/scratch/ControlFlow.md +136 -136
  86. package/fireflysite/assets/markdown/scratch/Toc.md +40 -40
  87. package/lsp/.firefly/package.ff +1 -1
  88. package/lsp/CompletionHandler.ff +827 -827
  89. package/lsp/Handler.ff +714 -714
  90. package/lsp/HoverHandler.ff +79 -79
  91. package/lsp/LanguageServer.ff +272 -272
  92. package/lsp/SignatureHelpHandler.ff +55 -55
  93. package/lsp/SymbolHandler.ff +181 -181
  94. package/lsp/TestReferences.ff +17 -17
  95. package/lsp/TestReferencesCase.ff +7 -7
  96. package/lsp/stderr.txt +1 -1
  97. package/lsp/stdout.txt +34 -34
  98. package/lux/.firefly/package.ff +1 -1
  99. package/lux/Css.ff +648 -648
  100. package/lux/CssTest.ff +48 -48
  101. package/lux/Lux.ff +608 -608
  102. package/lux/LuxEvent.ff +79 -79
  103. package/lux/Main.ff +123 -123
  104. package/lux/Main2.ff +143 -143
  105. package/lux/TestDry.ff +28 -28
  106. package/output/js/ff/compiler/Builder.mjs +36 -38
  107. package/package.json +1 -1
  108. package/rpc/.firefly/package.ff +1 -1
  109. package/rpc/Rpc.ff +70 -70
  110. package/s3/.firefly/package.ff +1 -1
  111. package/s3/S3.ff +92 -92
  112. package/vscode/LICENSE.txt +21 -21
  113. package/vscode/Prepublish.ff +15 -15
  114. package/vscode/README.md +16 -16
  115. package/vscode/client/package-lock.json +544 -544
  116. package/vscode/client/package.json +22 -22
  117. package/vscode/client/src/extension.ts +104 -104
  118. package/vscode/icons/firefly-icon.svg +10 -10
  119. package/vscode/language-configuration.json +61 -61
  120. package/vscode/package-lock.json +3623 -3623
  121. package/vscode/package.json +1 -1
  122. package/vscode/snippets.json +241 -241
  123. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  124. package/webserver/.firefly/include/package.json +5 -5
  125. package/webserver/.firefly/package.ff +2 -2
  126. package/webserver/WebServer.ff +647 -647
  127. package/websocket/.firefly/package.ff +1 -1
  128. package/websocket/WebSocket.ff +100 -100
@@ -1,1437 +1,1437 @@
1
- import Syntax
2
- import Patterns
3
- import JsImporter
4
-
5
- class JsEmitter(
6
- otherModules: Map[String, Module]
7
- jsImporter: JsImporter
8
- emitTarget: EmitTarget
9
- isMainModule: Bool
10
- compilerModuleFileUrl: Option[String]
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
- compilerModuleFileUrl: Option[String]
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
- compilerModuleFileUrl = compilerModuleFileUrl
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.compilerModuleFileUrl.map {"import * as $firefly_compiler from '" + _ + "'"}.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
- }
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
- let assignmentCode = if(!mutable || valueCode != "(void 0)") {" = " + valueCode} else {""}
164
- mutability + " " + escapeKeyword(definition.name) + assignmentCode + ";"
165
- }
166
-
167
- emitExtendsDefinition(definition: DExtend): String {
168
- let typeName = extractTypeName(definition.type).reverse().takeWhile {_ != '.'}.reverse()
169
- let methods = definition.methods.map {method =>
170
- method.DFunction(
171
- signature = method.signature.Signature(
172
- name = typeName + "_" + method.signature.name
173
- )
174
- )
175
- }
176
- let syncMethods = methods.map {"export " + self.emitFunctionDefinition(_, False)}
177
- let asyncMethods = self.withEmittingAsync {methods.map {"export " + self.emitFunctionDefinition(_, True)}}
178
- [...syncMethods, ...asyncMethods].join("\n\n")
179
- }
180
-
181
- emitInstanceDefinition(definition: DInstance): String {
182
- let name = makeDictionaryName(definition.traitName, firstTypeName(definition.typeArguments))
183
- let methods = definition.methods.map {self.emitFunctionDefinition(_, False)}.map {_.dropFirst("function ".size())} // TODO
184
- let asyncMethods = self.withEmittingAsync {
185
- definition.methods.map {self.emitFunctionDefinition(_, True)}.map {"async " + _.dropFirst("async function ".size())} // TODO
186
- }
187
- let body = "{\n" + [...methods, ...asyncMethods].join(",\n") + "\n}"
188
- definition.constraints.{
189
- | [] =>
190
- "export const " + name + " = " + body + ";"
191
- | constraints =>
192
- let dictionaries = constraints.map {c =>
193
- makeDictionaryName(c.name, firstTypeName(c.generics))
194
- }
195
- "export function " + name + "(" + dictionaries.join(", ") + ") { return " + body + "}"
196
- }
197
- }
198
-
199
- emitFunctionDefinition(definition: DFunction, async: Bool, suffix: String = ""): String {
200
- let signature = self.emitSignature(definition.signature, async, suffix)
201
- definition.body.{
202
- | Lambda(_, effect, [matchCase]) {
203
- matchCase.patterns.all {
204
- | PVariable(_, None) => True
205
- | _ => False
206
- }
207
- } =>
208
- let body = self.emitTailCall {self.emitStatements(matchCase.body, True, False, async)}
209
- signature + " {\n" + body + "\n}"
210
- | Lambda(_, effect, cases) =>
211
- Patterns.convertAndCheck(self.otherModules, cases)
212
- let escapedArguments = definition.signature.parameters.map {_.name + "_a"}
213
- let shadowingWorkaround = definition.signature.parameters.map {p =>
214
- "const " + p.name + "_a = " + escapeKeyword(p.name) + ";"
215
- }.join("\n")
216
- let body = self.emitTailCall {
217
- let casesString = cases.pairs().map {| Pair(i, c) =>
218
- let lastCase = i == cases.size() - 1
219
- self.emitCase(escapedArguments, c, [], [], True, True, False, lastCase, async)
220
- }.join("\n")
221
- shadowingWorkaround + "\n" + casesString
222
- }
223
- signature + " {\n" + body + "\n}"
224
- }
225
- }
226
-
227
- emitTailCall(body: () => String): String {
228
- let outerTailCallUsed = self.tailCallUsed
229
- self.tailCallUsed = False
230
- let result = body()
231
- let tailCallUsed = self.tailCallUsed
232
- self.tailCallUsed = outerTailCallUsed
233
- if(tailCallUsed) {
234
- "_tailcall: for(;;) {\n" + result + "\nreturn\n}"
235
- } else {
236
- result
237
- }
238
- }
239
-
240
- emitSignature(signature: Signature, async: Bool, suffix: String = ""): String {
241
- let parameterStrings = signature.parameters.map {self.emitParameter(_, async)}
242
- let dictionaryStrings = signature.constraints.map {c =>
243
- makeDictionaryName(c.name, firstTypeName(c.generics))
244
- }
245
- let controller = if(async) {["$task"]} else {[]}
246
- let parameters = "(" + [...parameterStrings, ...dictionaryStrings, ...controller].join(", ") + ")"
247
- let prefix = if(async) {"async "} else {""}
248
- let asyncSuffix = if(async) {"$"} else {""}
249
- prefix + "function " + escapeKeyword(signature.name) + suffix + asyncSuffix + parameters
250
- }
251
-
252
- emitParameter(parameter: Parameter, async: Bool): String {
253
- let defaultValue = parameter.default.map {" = " + self.emitTerm(_, async) }.else {""}
254
- escapeKeyword(parameter.name) + defaultValue
255
- }
256
-
257
- emitTypeDefinition(definition: DType): String {
258
- if(definition.newtype) {"// newtype " + definition.name} else:
259
- "// type " + definition.name + "\n" +
260
- definition.variants.map {self.emitVariantDefinition(definition, _)}.join("\n")
261
- }
262
-
263
- emitVariantDefinition(typeDefinition: DType, definition: Variant): String {
264
- let allFields = [...typeDefinition.commonFields, ...definition.fields]
265
- let fields = allFields.map {escapeKeyword(_.name)}.join(", ")
266
- if(allFields.isEmpty()) {
267
- "const " + definition.name + "$ = {" + definition.name + ": true};\n" +
268
- "export function " + definition.name + "(" + fields + ") {\n" +
269
- "return " + definition.name + "$;\n" +
270
- "}"
271
- } elseIf {typeDefinition.variants.size() == 1} {
272
- "export function " + definition.name + "(" + fields + ") {\n" +
273
- "return {" + fields + "};\n" +
274
- "}"
275
- } else {
276
- "export function " + definition.name + "(" + fields + ") {\n" +
277
- "return {" + definition.name + ": true, " + fields + "};\n" +
278
- "}"
279
- }
280
- }
281
-
282
- emitTerm(term: Term, async: Bool): String {term.{
283
- | EString(at, value) {value.startsWith("\"\"\"")} =>
284
- "`" + value.dropFirst(3).dropLast(3).replace("`", "\\`") + "`" // TODO: Fix escaping
285
- | EString(at, value) => value
286
- | EChar(at, value) => charLiteralToNumber(value)
287
- | EInt(at, value) => value
288
- | EFloat(at, value) => value
289
- | EVariable(at, name) => escapeResolved(name)
290
- | EList(at, _, items) =>
291
- self.emitList(items, async)
292
- | EVariant(at, "ff:core/Bool.False", _, _) =>
293
- "false"
294
- | EVariant(at, "ff:core/Bool.True", _, _) =>
295
- "true"
296
- | EVariant(at, "ff:core/Unit.Unit", _, _) =>
297
- "(void 0)"
298
- | EVariant(at, name, _, arguments) =>
299
- let argumentsString = arguments.toList().flatten().map {self.emitArgument(at, _, async)}.join(", ")
300
- let newtype = self.processVariant(name)
301
- if(newtype) {argumentsString} else:
302
- escapeResolved(name) + "(" + argumentsString + ")"
303
- | EVariantIs(at, "ff:core/Bool.False", _) =>
304
- "function(_v) { return !_v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
305
- | EVariantIs(at, "ff:core/Bool.True", _) =>
306
- "function(_v) { return _v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
307
- | EVariantIs(at, "ff:core/Unit.Unit", _) =>
308
- "function(_v) { return ff_core_Option.Some(_v); }"
309
- | EVariantIs(at, name, _) =>
310
- let n = name.reverse().takeWhile {_ != '.'}.reverse()
311
- "(function(_v) { " +
312
- "return _v." + escapeResolved(n) + " ? ff_core_Option.Some(_v) : ff_core_Option.None();" +
313
- "})"
314
- | ECopy(at, name, record, fields) =>
315
- let fieldCode = fields.map {f => escapeKeyword(f.name) + " = " + self.emitTerm(f.value, async)}.join(", ")
316
- "{..." + self.emitTerm(record, async) + ", " + fieldCode + "}"
317
- | EField(at, newtype, record, field) =>
318
- if(newtype) {self.emitTerm(record, async)} else:
319
- self.emitTerm(record, async) + "." + escapeKeyword(field)
320
- | ELambda(at, Lambda(_, effect, [MatchCase(_, patterns, [], body)])) {
321
- patterns.all {| PVariable _ => True | _ => False }
322
- } =>
323
- let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
324
- let patternParameters = patterns.map {
325
- | PVariable p => p.name.map(escapeKeyword).else {"_"}
326
- | _ => panic("!")
327
- }
328
- let controller = if(newAsync) {["$task"]} else {[]}
329
- let parameters = [...patternParameters, ...controller].join(", ")
330
- let prefix = if(newAsync) {"async "} else {""}
331
- "(" + prefix + "(" + parameters + ") => {\n" + self.emitStatements(body, True, False, newAsync) + "\n})"
332
- | ELambda(at, Lambda(_, effect, cases)) =>
333
- let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
334
- let controller = if(newAsync) {["$task"]} else {[]}
335
- Patterns.convertAndCheck(self.otherModules, cases)
336
- let arguments = cases.grab(0).patterns.pairs().map {"_" + (_.first + 1)}
337
- let escapedArguments = arguments.map(escapeKeyword) // emitCase arguments must be preescaped
338
- let caseStrings = cases.pairs().map {| Pair(i, c) =>
339
- let lastCase = i == cases.size() - 1
340
- self.emitCase(escapedArguments, c, [], [], True, True, False, lastCase, newAsync)
341
- }
342
- let prefix = if(newAsync) {"async "} else {""}
343
- "(" + prefix + "(" + [...escapedArguments, ...controller].join(", ") + ") => " +
344
- "{\n" + caseStrings.join("\n") + "\n})"
345
- | EPipe(at, value, effect, function) =>
346
- let await = async && effectTypeIsAsync(effect)
347
- let c = if(await) {", $task"} else {""}
348
- let call = "(" + self.emitTerm(function, async) + ")(" + self.emitTerm(value, async) + c + ")"
349
- if(await) {"(await " + call + ")"} else {call}
350
- | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
351
- self.emitSpecialCall(term, async, name, arguments.map {_.value}, dictionaries) | Some(code)
352
- } =>
353
- code
354
- | ECall(at, StaticCall(name, _, True), effect, typeArguments, arguments, dictionaries) =>
355
- let await = async && effectTypeIsAsync(effect)
356
- let dictionaryStrings = dictionaries.map {self.emitDictionary(_)}
357
- let ds = dictionaryStrings.dropFirst()
358
- let d = dictionaryStrings.grabFirst()
359
- let asyncSuffix = if(await) {"$"} else {""}
360
- let n = escapeKeyword(name.reverse().takeWhile {_ != '.'}.reverse()) + asyncSuffix
361
- let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
362
- let controller = if(await) {["$task"]} else {[]}
363
- let call = d + "." + n + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
364
- if(await) {"(await " + call + ")"} else {call}
365
- | ECall(at, StaticCall(name, _, _), effect, typeArguments, arguments, dictionaries) =>
366
- if(name.contains("bundleForBrowser")) { // TODO: Delete this test (for branch arraysonly)
367
- if(!arguments.grab(0).name.contains("system")) {
368
- Log.debug("Wrong arguments for bundleForBrowser: " + Show.show(arguments.map {_.name}))
369
- throw(GrabException())
370
- }
371
- }
372
- detectIfElse(term).{
373
- | [] =>
374
- let await = async && effectTypeIsAsync(effect)
375
- let ds = dictionaries.map {self.emitDictionary(_)}
376
- let functionCode = escapeResolved(name) + if(await) {"$"} else {""}
377
- let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
378
- let controller = if(await) {["$task"]} else {[]}
379
- let call = functionCode + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
380
- if(await) {"(await " + call + ")"} else {call}
381
- | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
382
- "(" + list.foldLeft(self.emitComma(elseBody, async)) {| otherwise, Pair(condition, body) =>
383
- self.emitComma(condition, async) +
384
- "\n? " + self.emitComma(body, async) + "\n: " + otherwise
385
- } + ")"
386
- | list =>
387
- "(" + list.foldLeft("ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
388
- self.emitComma(condition, async) +
389
- "\n? ff_core_Option.Some(" + self.emitComma(body, async) + ")\n: " + otherwise
390
- } + ")"
391
- }
392
- | ECall(at, DynamicCall(function, _), effect, typeArguments, arguments, dictionaries) =>
393
- let await = async && effectTypeIsAsync(effect)
394
- if(!dictionaries.isEmpty()) {fail(at, "Internal error: Dictionaries in lambda call")}
395
- let functionCode = self.emitTerm(function, async)
396
- let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
397
- let controller = if(await) {["$task"]} else {[]}
398
- let call = functionCode + "(" + [...emittedArguments, ...controller].join(", ") + ")"
399
- if(await) {"(await " + call + ")"} else {call}
400
- | ERecord(at, fields) =>
401
- if(fields.isEmpty()) {"{}"} else {
402
- let list = fields.map {f => escapeKeyword(f.name) + ": " + self.emitTerm(f.value, async)}
403
- "{\n" + list.join(",\n") + "\n}"
404
- }
405
- | EWildcard(at, index) =>
406
- if(index == 0) {fail(at, "Unbound wildcard")}
407
- "_w" + index
408
- | _ {async} =>
409
- "(await (async function() {\n" + self.emitStatements(term, True, False, async) + "\n})())"
410
- | _ =>
411
- "(function() {\n" + self.emitStatements(term, True, False, async) + "\n})()"
412
- }}
413
-
414
- emitField(term: Term, async: Bool, dot: String = "."): String {
415
- term.{
416
- | EString(_, q) {safeBare(q) | Some(s)} => dot + s
417
- | _ => "[" + self.emitTerm(term, async) + "]"
418
- }
419
- }
420
-
421
- emitDictionary(d: Dictionary): String {
422
- let m = if(d.moduleName != "") {
423
- d.packagePair.groupName("_") + "_" + d.moduleName.replace("/", "_") + "."
424
- } else {""}
425
- let c = m + makeDictionaryName(d.traitName, d.typeName)
426
- if(d.dictionaries.isEmpty()) {
427
- c
428
- } else {
429
- c + "(" + d.dictionaries.map {self.emitDictionary(_)}.join(", ") + ")"
430
- }
431
- }
432
-
433
- emitStatements(term: Term, last: Bool, break: Bool, async: Bool): String {
434
- term.{
435
- | EFunctions(at, functions, body) =>
436
- let functionStrings = functions.map {f =>
437
- let newAsync = self.emittingAsync && effectTypeIsAsync(f.signature.effect)
438
- self.emitFunctionDefinition(f, newAsync)
439
- }
440
- functionStrings.join("\n") + "\n" + self.emitStatements(body, last, break, async)
441
- | ELet(at, mutable, name, valueType, value, body) =>
442
- self.emitLetDefinition(DLet(at, name, valueType, value), mutable, async) + "\n" +
443
- self.emitStatements(body, last, break, async)
444
- | EVariant(at, "ff:core/Unit.Unit", _, _) =>
445
- ""
446
- | ESequential(_, EVariant(_, "ff:core/Unit.Unit", _, _), after) =>
447
- self.emitStatements(after, last, break, async)
448
- | ESequential(_, before, EVariant(_, "ff:core/Unit.Unit", _, _)) =>
449
- self.emitStatements(before, False, break, async)
450
- | ESequential(at, before, after) =>
451
- self.emitStatements(before, False, False, async) + ";\n" +
452
- self.emitStatements(after, last, break, async)
453
- | EAssign(at, operator, name, value) =>
454
- escapeKeyword(name) + " " + operator + "= " + self.emitTerm(value, async)
455
- | EAssignField(at, operator, record, field, value) =>
456
- self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
457
- self.emitTerm(value, async)
458
- | ECall(at, StaticCall(name, True, instanceCall), effect, _, arguments, _) =>
459
- if(instanceCall) {throw(CompileError(at, "Not yet implemented: Tail calls on trait methods."))}
460
- self.tailCallUsed = True
461
- let pair = arguments.map {a =>
462
- Some(Pair(
463
- "const " + escapeKeyword(a.name.grab() + "_r") + " = " + self.emitTerm(a.value, async) + ";"
464
- escapeKeyword(a.name.grab()) + " = " + escapeKeyword(a.name.grab() + "_r")
465
- ))
466
- }.collect {_}.unzip()
467
- "{\n" + pair.first.join("\n") + "\n" + pair.second.join("\n") + "\ncontinue _tailcall\n}"
468
- | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
469
- self.emitSpecialStatement(term, last, async, name, arguments.map {_.value}, dictionaries) | Some(code)
470
- } =>
471
- code
472
- | EPipe(at, value, _, ELambda(_, Lambda(_, _, cases))) =>
473
- Patterns.convertAndCheck(self.otherModules, cases)
474
- if(!last && !break) {"do "}.else {""} +
475
- "{\nconst _1 = " + self.emitTerm(value, async) + ";\n" +
476
- cases.pairs().map {| Pair(i, c) =>
477
- let lastCase = i == cases.size() - 1
478
- self.emitCase(["_1"], c, [], [], True, last, break, lastCase, async)
479
- }.join("\n") +
480
- "\n}" + if(!last && !break) {" while(false)"}.else {""}
481
- | _ =>
482
- detectIfElse(term).{
483
- | [] =>
484
- if(break) {
485
- "if(!" + self.emitComma(term, async) + ") break"
486
- } elseIf {last} {
487
- "return " + self.emitTerm(term, async)
488
- } else {
489
- self.emitTerm(term, async)
490
- }
491
- | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
492
- let initial = "{\n" + self.emitStatements(elseBody, last, break, async) + "\n}"
493
- list.foldLeft(initial) {| otherwise, Pair(condition, body) =>
494
- "if(" + self.emitTerm(condition, async) + ") {\n" +
495
- self.emitStatements(body, last, break, async) + "\n} else " + otherwise
496
- }
497
- | list {!last} =>
498
- list.foldLeft("{}") {| otherwise, Pair(condition, body) =>
499
- "if(" + self.emitTerm(condition, async) + ") {\n" +
500
- self.emitStatements(body, last, break, async) + "\n} else " + otherwise
501
- }
502
- | list =>
503
- list.foldLeft("return ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
504
- "if(" + self.emitTerm(condition, async) + ") {\n" +
505
- "return ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n} else " + otherwise
506
- }
507
- }
508
- }
509
- }
510
-
511
- emitSpecialCall(
512
- term: Term
513
- async: Bool
514
- name: String
515
- arguments: List[Term]
516
- dictionaries: List[Dictionary]
517
- ): Option[String] {
518
- name.{
519
- | operator {!operator.grabFirst().isAsciiLetter()} {arguments | [value]} =>
520
- Some("(" + operator + self.emitTerm(value, async) + ")")
521
- | operator {!operator.grabFirst().isAsciiLetter()} {arguments | [left, right]} =>
522
- Some("(" + self.emitTerm(left, async) + " " + operator + " " + self.emitTerm(right, async) + ")")
523
- | "ff:core/List.List_grab" {arguments | [e1, e2]} {noSideEffects(e1) && noSideEffects(e2)} =>
524
- let code1 = self.emitTerm(e1, async)
525
- let code2 = self.emitTerm(e2, async)
526
- Some(
527
- "(" + code1 + "[" + code2 + "] ?? " +
528
- "ff_core_List.List_grab(" + code1 + ", " + code2 + "))"
529
- )
530
- | "ff:core/Array.Array_grab" {arguments | [e1, e2]} {noSideEffects(e1) && noSideEffects(e2)} =>
531
- let code1 = self.emitTerm(e1, async)
532
- let code2 = self.emitTerm(e2, async)
533
- Some(
534
- "(" + code1 + ".array[" + code2 + "] ?? " +
535
- "ff_core_Array.Array_grab(" + code1 + ", " + code2 + "))"
536
- )
537
- | "ff:core/List.List_size" {arguments | [e]} =>
538
- Some(
539
- self.emitTerm(e, async) + ".length"
540
- )
541
- | "ff:core/Array.Array_size" {arguments | [e]} =>
542
- Some(
543
- self.emitTerm(e, async) + ".array.length"
544
- )
545
- | "ff:core/String.String_size" {arguments | [e]} =>
546
- Some(
547
- self.emitTerm(e, async) + ".length"
548
- )
549
- | "ff:core/Equal.equals" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
550
- primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
551
- } =>
552
- Some("(" + self.emitTerm(left, async) + " === " + self.emitTerm(right, async) + ")")
553
- | "ff:core/Equal.notEquals" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
554
- primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
555
- } =>
556
- Some("(" + self.emitTerm(left, async) + " !== " + self.emitTerm(right, async) + ")")
557
- | "ff:core/Ordering.before" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
558
- primitiveTypes.contains(typeName)
559
- } =>
560
- Some("(" + self.emitTerm(left, async) + " < " + self.emitTerm(right, async) + ")")
561
- | "ff:core/Ordering.notBefore" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
562
- primitiveTypes.contains(typeName)
563
- } =>
564
- Some("(" + self.emitTerm(left, async) + " >= " + self.emitTerm(right, async) + ")")
565
- | "ff:core/Ordering.after" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
566
- primitiveTypes.contains(typeName)
567
- } =>
568
- Some("(" + self.emitTerm(left, async) + " > " + self.emitTerm(right, async) + ")")
569
- | "ff:core/Ordering.notAfter" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
570
- primitiveTypes.contains(typeName)
571
- } =>
572
- Some("(" + self.emitTerm(left, async) + " <= " + self.emitTerm(right, async) + ")")
573
- | "ff:core/List.fillBy" {term | ECall call} {arguments | [size, ELambda(at,
574
- Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)@c])@l
575
- )]} {
576
- !effectTypeIsAsync(call.effect)
577
- } =>
578
- let n = name.map {escapeResolved(_)}.else {"i"}
579
- let newAsync = self.emittingAsync && effectTypeIsAsync(call.effect)
580
- let await = if(newAsync) {"await "} else {""}
581
- Some(
582
- await + "((() => {\n" +
583
- "const size = " + self.emitTerm(size, async) + ";\n" + // Not correct if async and body isn't
584
- "const result = [];\n" +
585
- "for(let " + n + " = 0; " + n + " < size; " + n + "++) {\n" +
586
- "result.push(" + self.emitTerm(body, newAsync) + ");\n" +
587
- "}\n" +
588
- "return result;\n" +
589
- "})())"
590
- )
591
- | "ff:core/Js.import" {arguments | [EString(_, url)]} =>
592
- self.emitTarget.{
593
- | EmitBrowser => Some("(() => {throw new Error('Node.js imports are not supported in the browser')})()")
594
- | _ => Some(self.jsImporter.add(url.replace("\"", "")))
595
- }
596
- | "ff:core/Js.browserImport" {arguments | [EString(_, url)]} =>
597
- self.emitTarget.{
598
- | EmitBrowser => Some(self.jsImporter.add(url.replace("\"", "")))
599
- | _ => Some("(() => {throw new Error('Browser imports are not supported in Node.js')})()")
600
- }
601
- | "ff:core/Js.dynamicImport" {arguments | [url]} =>
602
- Some("import(" + self.emitTerm(url, async) + ")")
603
- | "ff:core/Js.await" {arguments | [body]} =>
604
- if(async) {
605
- Some("(await " + self.emitTerm(body, async) + ")")
606
- } else {
607
- Some(self.emitTerm(body, async))
608
- }
609
- | name {name.removeFirst("ff:core/Js.async") | Some(n)} {n.all {_.isAsciiDigit()}} {
610
- arguments | [ELambda(at, Lambda(_, effect, [MatchCase(_, patterns, [], body)]))]
611
- } {
612
- patterns.all {| PVariable _ => True | _ => False }
613
- } =>
614
- let patternParameters = patterns.map {
615
- | PVariable p => p.name.map(escapeKeyword).else {"_"}
616
- | _ => panic("!")
617
- }
618
- Some(
619
- "async (" + patternParameters.join(", ") + ") => {\n" +
620
- self.emitStatements(body, True, False, False) +
621
- "\n}"
622
- )
623
- | name {name.startsWith("ff:core/Js.async")} =>
624
- throw(CompileError(term.at, "JS async functions must take a simple parameter list"))
625
- | "ff:core/Js.cancelled" =>
626
- Some(if(async) {"$task.controller_.signal.aborted"} else {"false"})
627
- | "ff:core/Js.throwIfCancelled" =>
628
- Some(if(async) {"((() => ff_core_Task.Task_throwIfAborted($task))())"} else {""})
629
- | "ff:core/Js.currentTask" =>
630
- Some("$task")
631
- | "ff:core/Js.controller" =>
632
- Some("$task.controller_")
633
- | "ff:core/Js.setController" {arguments | [a]} =>
634
- Some("($task.controller_ = " + self.emitTerm(a, async) + ")")
635
- | "ff:core/Js.inAsync" =>
636
- Some(if(self.emittingAsync) {"true"} else {"false"})
637
- | "ff:core/Js.inBrowser" =>
638
- Some(if(self.emitTarget == EmitBrowser) {"true"} else {"false"})
639
- | "ff:core/Js.inNode" =>
640
- Some(if(self.emitTarget == EmitNode) {"true"} else {"false"})
641
- | "ff:core/Js.inBuild" =>
642
- Some(if(self.emitTarget == EmitBuild) {"true"} else {"false"})
643
- | "ff:core/Js.value" {arguments | [e]} =>
644
- Some(self.emitTerm(e, async))
645
- | "ff:core/Js.fromValue" {arguments | [e]} =>
646
- Some(self.emitTerm(e, async))
647
- | "ff:core/Js.rawIdentifier" {arguments | [EString(_, op)]} =>
648
- Some(op.replace("\"", ""))
649
- | "ff:core/Js.unaryOperator" {arguments | [EString(_, op), a1]} =>
650
- Some("(" + op.replace("\"", "") + self.emitTerm(a1, async) + ")")
651
- | "ff:core/Js.binaryOperator" {arguments | [EString(_, op), a1, a2]} =>
652
- Some("(" + self.emitTerm(a1, async) + " " + op.replace("\"", "") + " " + self.emitTerm(a2, async) + ")")
653
- | "ff:core/Js.shortCircuitingOperator" {arguments | [EString(_, op), a1, a2]} =>
654
- Some("(" + self.emitTerm(a1, async) + " " + op.replace("\"", "") + " " + self.emitTerm(invokeImmediately(a2), async) + ")")
655
- | "ff:core/JsValue.JsValue_spreadToArray" {arguments | [e1]} =>
656
- Some("[..." + self.emitTerm(e1, async) + "]")
657
- | "ff:core/JsValue.JsValue_typeof" {arguments | [e]} =>
658
- Some("(typeof " + self.emitTerm(e, async) + ")")
659
- | "ff:core/JsValue.JsValue_instanceof" {arguments | [e1, e2]} =>
660
- Some("(" + self.emitTerm(e1, async) + " instanceof " + self.emitTerm(e2, async) + ")")
661
- | "ff:core/JsValue.JsValue_get" {arguments | [e1, e2]} =>
662
- Some(self.emitTerm(e1, async) + self.emitField(e2, async))
663
- | "ff:core/JsValue.JsValue_equals" {arguments | [e1, e2]} =>
664
- Some("(" + self.emitTerm(e1, async) + " === " + self.emitTerm(e2, async) + ")")
665
- | "ff:core/JsValue.JsValue_notEquals" {arguments | [e1, e2]} =>
666
- Some("(" + self.emitTerm(e1, async) + " !== " + self.emitTerm(e2, async) + ")")
667
- | "ff:core/Int.Int_bitAnd" {arguments | [e1, e2]} =>
668
- Some("(" + self.emitTerm(e1, async) + " & " + self.emitTerm(e2, async) + ")")
669
- | "ff:core/Int.Int_bitRightUnsigned" {arguments | [e1, e2]} =>
670
- Some("(" + self.emitTerm(e1, async) + " >>> " + self.emitTerm(e2, async) + ")")
671
- | "ff:core/Int.Int_bitRight" {arguments | [e1, e2]} =>
672
- Some("(" + self.emitTerm(e1, async) + " >> " + self.emitTerm(e2, async) + ")")
673
- | name {name.removeFirst("ff:core/JsValue.JsValue_call") | Some(n)} {n.all {_.isAsciiDigit()}} {
674
- arguments | [e1, e2, ...es]
675
- } =>
676
- let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
677
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + "(" + argumentCode + ")")
678
- | name {name.removeFirst("ff:core/JsValue.JsValue_callValue") | Some(n)} {n.all {_.isAsciiDigit()}} {
679
- arguments | [e1, ...es]
680
- } =>
681
- let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
682
- Some(self.emitTerm(e1, async) + "(" + argumentCode + ")")
683
- | name {name.removeFirst("ff:core/JsValue.JsValue_new") | Some(n)} {n.all {_.isAsciiDigit()}} {
684
- arguments | [e1, ...es]
685
- } =>
686
- let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
687
- Some("(new " + self.emitTerm(e1, async) + "(" + argumentCode + ")" + ")")
688
- | name {name == "ff:core/JsValue.JsValue_with" || name == "ff:core/Json.Json_with"} =>
689
- function go(e: Term, fields: List[Pair[Term, Term]]): String {
690
- e.{
691
- | ECall(_, StaticCall(n, _, _), _, _, [a1, a2, a3], _) {n == name} =>
692
- go(a1.value, [Pair(a2.value, a3.value), ...fields])
693
- | ECall(_, StaticCall(n, _, _), _, _, as, _) {
694
- n == "ff:core/JsSystem.JsSystem_object" ||
695
- n == "ff:core/JsSystem.JsSystem_new0" ||
696
- n == "ff:core/Js.object" ||
697
- n == "ff:core/Js.new0" ||
698
- n == "ff:core/Json.Json_object" ||
699
- n == "ff:core/Json.Json_new0"
700
- } {
701
- as.all {noSideEffects(_.value)}
702
- } =>
703
- "{" + fields.map {p =>
704
- self.emitField(p.first, async, dot = "") + ": " + self.emitTerm(p.second, async)
705
- }.join(", ") + "}"
706
- | _ =>
707
- "{..." + self.emitTerm(e, async) + ", " + fields.map {p =>
708
- self.emitField(p.first, async, dot = "") + ": " + self.emitTerm(p.second, async)
709
- }.join(", ") + "}"
710
- }
711
- }
712
- Some(go(term, []))
713
- | name {name.removeFirst("ff:core/JsSystem.JsSystem_call") | Some(n)} {n.all {_.isAsciiDigit()}} {
714
- arguments | [e1, EString(_, q)@e2, ...es]
715
- } {noSideEffects(e1)} =>
716
- let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
717
- Some(safeBare(q).else {"globalThis[" + self.emitTerm(e2, async) + "]"} + "(" + argumentCode + ")")
718
- | name {name.removeFirst("ff:core/JsSystem.JsSystem_function") | Some(n)} {n.all {_.isAsciiDigit()}} {
719
- arguments | [e1, e2]
720
- } {noSideEffects(e1)} {term | ECall call} {!effectTypeIsAsync(call.effect)} =>
721
- Some(self.emitTerm(e2, async))
722
- | "ff:core/JsSystem.JsSystem_get" {arguments | [e1, EString(_, q)@e2]} {noSideEffects(e1)} =>
723
- Some(safeBare(q).else {"globalThis[" + self.emitTerm(e2, async) + "]"})
724
- | "ff:core/JsSystem.JsSystem_object" {arguments | [e]} {noSideEffects(e)} =>
725
- Some("{}")
726
- | "ff:core/JsSystem.JsSystem_new0" {arguments | [e]} {noSideEffects(e)} =>
727
- Some("{}")
728
- | "ff:core/JsSystem.JsSystem_null" {arguments | [e]} {noSideEffects(e)} =>
729
- Some("null")
730
- | "ff:core/JsSystem.JsSystem_undefined" {arguments | [e]} {noSideEffects(e)} =>
731
- Some("(void 0)")
732
- | name {name.removeFirst("ff:core/Js.call") | Some(n)} {n.all {_.isAsciiDigit()}} {
733
- arguments | [EString(_, q)@e1, ...es]
734
- } =>
735
- let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
736
- Some(safeBare(q).else {"globalThis[" + self.emitTerm(e1, async) + "]"} + "(" + argumentCode + ")")
737
- | name {name.removeFirst("ff:core/Js.function") | Some(n)} {n.all {_.isAsciiDigit()}} {
738
- arguments | [e1]
739
- } {term | ECall call} =>
740
- if(self.emittingAsync && effectTypeIsAsync(call.effect)) {
741
- let argumentCode = 1.to(n.grabInt()).map {"a_" + _}.join(", ")
742
- let taskCode = if(argumentCode == "") {"$task"} else {", $task"}
743
- Some("(async (" + argumentCode + ") => await " + self.emitTerm(e1, async) + "(" + argumentCode + taskCode + "))")
744
- } else {
745
- Some(self.emitTerm(e1, async))
746
- }
747
- | "ff:core/Js.get" {arguments | [EString(_, q)@e1]} =>
748
- Some(safeBare(q).else {"globalThis[" + self.emitTerm(e1, async) + "]"})
749
- | "ff:core/Js.object" =>
750
- Some("{}")
751
- | "ff:core/Js.new0" =>
752
- Some("{}")
753
- | "ff:core/Js.null" =>
754
- Some("null")
755
- | "ff:core/Js.undefined" =>
756
- Some("(void 0)")
757
- | "ff:core/Js.globalThis" =>
758
- Some("globalThis")
759
- | "ff:core/BrowserSystem.BrowserSystem_js" {arguments | [e]} {noSideEffects(e)} =>
760
- Some("globalThis")
761
- | "ff:core/BuildSystem.BuildSystem_js" {arguments | [e]} {noSideEffects(e)} =>
762
- Some("globalThis")
763
- | "ff:core/NodeSystem.NodeSystem_js" {arguments | [e]} {noSideEffects(e)} =>
764
- Some("globalThis")
765
- | "ff:core/Js.jsSystem" =>
766
- Some("globalThis")
767
- | "ff:core/Json.string" {arguments | [e]} =>
768
- Some(self.emitTerm(e, async))
769
- | "ff:core/Json.int" {arguments | [e]} =>
770
- Some(self.emitTerm(e, async))
771
- | "ff:core/Json.float" {arguments | [e]} =>
772
- Some(self.emitTerm(e, async))
773
- | "ff:core/Json.bool" {arguments | [e]} =>
774
- Some(self.emitTerm(e, async))
775
- | "ff:core/Json.array" {arguments | [e]} =>
776
- Some(self.emitTerm(e, async))
777
- | "ff:core/Json.null" {arguments | [e]} =>
778
- Some("null")
779
- | "ff:core/Json.object" {arguments | [e]} =>
780
- Some("{}")
781
- | _ =>
782
- None
783
- }
784
- }
785
-
786
- emitSpecialStatement(
787
- term: Term
788
- last: Bool
789
- async: Bool
790
- name: String
791
- arguments: List[Term]
792
- dictionaries: List[Dictionary]
793
- ): Option[String] {
794
- name.{
795
- | "ff:core/Core.while" {arguments | [condition, body]} =>
796
- Some(
797
- "while(" + self.emitComma(invokeImmediately(condition), async) + ") {\n" +
798
- self.emitStatements(invokeImmediately(body), False, False, async) + "\n}"
799
- )
800
- | "ff:core/Core.doWhile" {arguments | [doWhileBody]} {
801
- invokeImmediately(doWhileBody) | body
802
- } =>
803
- Some(
804
- "while(true) {\n" +
805
- self.emitStatements(body, False, True, async) +
806
- "\n}"
807
- )
808
- | "ff:core/Option.Option_each" {arguments | [list, ELambda(_, Lambda(_, _, [
809
- MatchCase(_, [PVariable(_, name)], [], body)
810
- ]))]} =>
811
- Some(
812
- "{\nconst if_o = " + self.emitTerm(list, async) + "\nif(if_o.Some) {\n" +
813
- name.map {"const " + escapeKeyword(_) + " = if_o.value_;\n"}.else {""} +
814
- self.emitStatements(body, last, False, async) +
815
- "\n}\n}"
816
- )
817
- | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
818
- ECall(_, StaticCall(r, _, _), _, _, [start, end], _)
819
- ELambda(_, Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)]))
820
- ]} {r == "ff:core/Int.Int_until" || r == "ff:core/Int.Int_to"} =>
821
- let startCode = self.emitTerm(start.value, async)
822
- let endCode = self.emitTerm(end.value, async)
823
- let op = if(r == "ff:core/Int.Int_until") {"<"} else {"<="}
824
- Some(
825
- "for(let " +
826
- "for_i = " + startCode + ", for_e = " + endCode + "; for_i " + op + " for_e; for_i++) {\n" +
827
- name.map {"const " + escapeKeyword(_) + " = for_i;\n"}.else {""} +
828
- self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
829
- "\n}"
830
- )
831
- | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
832
- ECall(_, StaticCall("ff:core/List.List_reverse", _, _), _, _, [
833
- Argument(_, _, ECall(_, StaticCall(r, _, _), _, _, [start, end], _))
834
- ], _)
835
- ELambda(_, Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)]))
836
- ]} {r == "ff:core/Int.Int_until" || r == "ff:core/Int.Int_to"} =>
837
- let startCode = self.emitTerm(start.value, async)
838
- let endCode = self.emitTerm(end.value, async)
839
- let delta = if(r == "ff:core/Int.Int_until") {" - 1"} else {""}
840
- Some(
841
- "for(let " +
842
- "for_e = " + startCode + ", for_i = " + endCode + delta + "; for_i >= for_e; for_i--) {\n" +
843
- name.map {"const " + escapeKeyword(_) + " = for_i;\n"}.else {""} +
844
- self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
845
- "\n}"
846
- )
847
- | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
848
- ECall(_, StaticCall("ff:core/List.List_zip", _, _), _, _, [list1, list2], _)
849
- ELambda(_, Lambda(_, _, [MatchCase(_, [
850
- PVariant(_, "ff:core/Pair.Pair", [PVariable(_, name1), PVariable(_, name2)])
851
- ], [], body)]))
852
- ]} =>
853
- let fusion1 = self.emitLightFusion("for_a", list1.value, async)
854
- let fusion2 = self.emitLightFusion("for_a2", list2.value, async)
855
- let start1 = fusion1.second.first
856
- let end1 = fusion1.second.second
857
- let listCode1 = fusion1.first
858
- let start2 = fusion2.second.first
859
- let end2 = fusion2.second.second
860
- let listCode2 = fusion2.first
861
- Some(
862
- "for(let for_a = " + listCode1 + ", for_i = " + start1 + ", for_l = " + end1 + ", " +
863
- "for_a2 = " + listCode2 + ", for_i2 = " + start2 + ", for_l2 = " + end2 +
864
- "; for_i < for_l && for_i2 < for_l2; for_i++, for_i2++) {\n" +
865
- name1.map {"const " + escapeKeyword(_) + " = for_a[for_i];\n"}.else {""} +
866
- name2.map {"const " + escapeKeyword(_) + " = for_a2[for_i2];\n"}.else {""} +
867
- self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
868
- "\n}"
869
- )
870
- | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
871
- ECall(_, StaticCall("ff:core/List.List_pairs", _, _), _, _, [list], _)
872
- ELambda(_, Lambda(_, _, [MatchCase(_, [
873
- PVariant(_, "ff:core/Pair.Pair", [PVariable(_, name1), PVariable(_, name2)])
874
- ], [], body)]))
875
- ]} =>
876
- let fusion = self.emitLightFusion("for_a", list.value, async)
877
- let start = fusion.second.first
878
- let end = fusion.second.second
879
- let listCode = fusion.first
880
- Some(
881
- "for(let for_a = " + listCode + ", for_i = " + start + ", for_l = " + end +
882
- "; for_i < for_l; for_i++) {\n" +
883
- name1.map {"const " + escapeKeyword(_) + " = for_i;\n"}.else {""} +
884
- name2.map {"const " + escapeKeyword(_) + " = for_a[for_i];\n"}.else {""} +
885
- self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
886
- "\n}"
887
- )
888
- | n {
889
- n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile" ||
890
- n == "ff:core/Array.Array_each" || n == "ff:core/Array.Array_eachWhile"
891
- } {
892
- arguments | [list, ELambda(_, Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)]))]
893
- } =>
894
- let fusion = self.emitLightFusion("for_a", list, async)
895
- let start = fusion.second.first
896
- let end = fusion.second.second
897
- let listCode = fusion.first + if(n.startsWith("ff:core/Array.")) {".array"} else {""}
898
- Some(
899
- "for(let for_a = " + listCode + ", for_i = " + start + ", for_l = " + end +
900
- "; for_i < for_l; for_i++) {\n" +
901
- name.map {"const " + escapeKeyword(_) + " = for_a[for_i];\n"}.else {""} +
902
- self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
903
- "\n}"
904
- )
905
- | "ff:core/Array.Array_push" {arguments | [array, value]} =>
906
- Some(self.emitTerm(array, async) + ".array.push(" + self.emitTerm(value, async) + ")")
907
- | "ff:core/Core.if" {arguments | [condition, body]} =>
908
- Some(
909
- "if(" + self.emitComma(condition, async) + ") {\n" +
910
- if(last) {
911
- "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body), async) +
912
- ")\n} else return ff_core_Option.None()"
913
- } else {
914
- self.emitStatements(invokeImmediately(body), False, False, async) + "\n}"
915
- }
916
- )
917
- | "ff:core/Core.throw" {term | ECall c} {c.arguments | [argument]} {dictionaries | [dictionary]} =>
918
- let d = self.emitDictionary(dictionary)
919
- let a = self.emitArgument(term.at, argument, async)
920
- Some("throw Object.assign(new Error(), {ffException: ff_core_Any.toAny_(" + a + ", " + d + ")})")
921
- | "ff:core/Try.Try_catch" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
922
- Some(code)
923
- | "ff:core/Try.Try_catchAny" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
924
- Some(code)
925
- | "ff:core/Try.Try_finally" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
926
- Some(code)
927
- | "ff:core/Js.throwIfCancelled" =>
928
- Some(if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""})
929
- | "ff:core/Js.throw" {term | ECall c} {c.arguments | [argument]} =>
930
- Some("throw " + self.emitTerm(argument.value, async))
931
- | "ff:core/JsValue.JsValue_set" {arguments | [e1, e2, e3]} =>
932
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " = " + self.emitTerm(e3, async))
933
- | "ff:core/JsValue.JsValue_increment" {arguments | [e1, e2, e3]} =>
934
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " += " + self.emitTerm(e3, async))
935
- | "ff:core/JsValue.JsValue_decrement" {arguments | [e1, e2, e3]} =>
936
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " -= " + self.emitTerm(e3, async))
937
- | "ff:core/JsSystem.JsSystem_set" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
938
- Some(s + " = " + self.emitTerm(e3, async))
939
- | "ff:core/JsSystem.JsSystem_increment" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
940
- Some(s + " += " + self.emitTerm(e3, async))
941
- | "ff:core/JsSystem.JsSystem_decrement" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
942
- Some(s + " -= " + self.emitTerm(e3, async))
943
- | "ff:core/Js.set" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
944
- Some(s + " = " + self.emitTerm(e2, async))
945
- | "ff:core/Js.increment" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
946
- Some(s + " += " + self.emitTerm(e2, async))
947
- | "ff:core/Js.decrement" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
948
- Some(s + " -= " + self.emitTerm(e2, async))
949
- | _ =>
950
- None
951
- }
952
- }
953
-
954
- emitLightFusion(listName: String, list: Term, async: Bool): Pair[String, Pair[String, String]] {
955
- mutable start = "0"
956
- mutable end = listName + ".length"
957
- let listCode = list.{
958
- | ECall(_, StaticCall("ff:core/List.List_dropFirst", _, _), _, _, [a1, a2], _) =>
959
- start = self.emitTerm(a2.value, async)
960
- if(!start.all {_.isAsciiDigit()}) {
961
- start = "Math.max(" + start + ", 0)"
962
- }
963
- self.emitTerm(a1.value, async)
964
- | ECall(_, StaticCall("ff:core/List.List_dropLast", _, _), _, _, [a1, a2], _) =>
965
- let count = self.emitTerm(a2.value, async)
966
- if(!count.all {_.isAsciiDigit()}) {
967
- end = end + " - Math.max(" + count + ", 0)"
968
- } else {
969
- end = end + " - " + count
970
- }
971
- self.emitTerm(a1.value, async)
972
- | ECall(_, StaticCall("ff:core/List.List_takeFirst", _, _), _, _, [a1, a2], _) =>
973
- end = self.emitTerm(a2.value, async)
974
- if(!end.all {_.isAsciiDigit()}) {
975
- end = "Math.max(" + end + ", 0)"
976
- }
977
- end = "Math.min(" + end + ", " + listName + ".length)"
978
- self.emitTerm(a1.value, async)
979
- | ECall(_, StaticCall("ff:core/List.List_takeLast", _, _), _, _, [a1, a2], _) =>
980
- let count = self.emitTerm(a2.value, async)
981
- if(!count.all {_.isAsciiDigit()}) {
982
- start = "Math.max(" + listName + ".length - Math.max(" + count + ", 0), 0)"
983
- } else {
984
- start = "Math.max(" + listName + ".length - " + count + ", 0)"
985
- }
986
- self.emitTerm(a1.value, async)
987
- | _ =>
988
- self.emitTerm(list, async)
989
- }
990
- Pair(listCode, Pair(start, end))
991
- }
992
-
993
- emitTryCatchFinally(term: Term, last: Bool, async: Bool): Option[String] {
994
- function emitCatch(catchEffect: Type, cases: List[MatchCase]): String {
995
- let catchAsync = self.emittingAsync && effectTypeIsAsync(catchEffect)
996
- Patterns.convertAndCheck(self.otherModules, cases)
997
- let arguments = ["_exception.value_", "_error"]
998
- cases.{
999
- | [case] =>
1000
- self.emitCase(arguments, case, [], [], False, last, False, True, catchAsync)
1001
- | cs =>
1002
- let caseStrings = cases.pairs().map {| Pair(i, c) =>
1003
- let lastCase = i == cases.size() - 1
1004
- self.emitCase(arguments, c, [], [], True, last, False, lastCase, catchAsync)
1005
- }
1006
- if(last) {caseStrings.join("\n")} else {"do {\n" + caseStrings.join("\n") + "\n} while(false)"}
1007
- }
1008
- }
1009
- term.{
1010
- | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
1011
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1012
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1013
- ], _))
1014
- Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
1015
- ], _) =>
1016
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1017
- let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
1018
- Some(
1019
- "try {\n" + self.emitStatements(tryBody, last, False, tryAsync) +
1020
- "\n} finally {\n" + self.emitStatements(finallyBody, last, False, finallyAsync) + "\n}"
1021
- )
1022
- | ECall(_, StaticCall("ff:core/Try.Try_catchAny", _, _), _, _, [
1023
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1024
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1025
- ], _))
1026
- Argument(_, _, ELambda(_, Lambda(_, catchEffect, [MatchCase(_, [PVariable(_, name)], [], catchBody)])))
1027
- ], _) =>
1028
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1029
- Some(
1030
- "try {\n" +
1031
- self.emitStatements(tryBody, last, False, tryAsync) +
1032
- "\n} catch" + name.map {"(" + escapeKeyword(_) + ")"}.else {""} + " {\n" +
1033
- self.emitStatements(catchBody, last, False, tryAsync) +
1034
- "\n}"
1035
- )
1036
- | ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
1037
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1038
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1039
- ], _))
1040
- Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
1041
- ], [dictionary]) =>
1042
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1043
- let d = self.emitDictionary(dictionary)
1044
- Some(
1045
- "try {\n" + self.emitStatements(tryBody, last, False, tryAsync) +
1046
- "\n} catch(_error) {\n" +
1047
- "if(!_error.ffException) throw _error\n" +
1048
- "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
1049
- "if(!_exception.Some) throw _error\n" +
1050
- emitCatch(catchEffect, cases) +
1051
- "\n}"
1052
- )
1053
- | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
1054
- Argument(_, _, ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
1055
- Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1056
- Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1057
- ], _))
1058
- Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
1059
- ], [dictionary]))
1060
- Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
1061
- ], _) =>
1062
- let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1063
- let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
1064
- let d = self.emitDictionary(dictionary)
1065
- Some(
1066
- "try {\n" + self.emitStatements(tryBody, last, False, tryAsync) +
1067
- "\n} catch(_error) {\n" +
1068
- "if(!_error.ffException) throw _error\n" +
1069
- "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
1070
- "if(!_exception.Some) throw _error\n" +
1071
- emitCatch(catchEffect, cases) +
1072
- "\n} finally {\n" + self.emitStatements(finallyBody, last, False, finallyAsync) + "\n}"
1073
- )
1074
- | _ =>
1075
- None
1076
- }
1077
- }
1078
-
1079
- emitCase(
1080
- arguments: List[String]
1081
- matchCase: MatchCase
1082
- conditions: List[String]
1083
- variables: List[String]
1084
- jump: Bool
1085
- last: Bool
1086
- break: Bool
1087
- lastCase: Bool
1088
- async: Bool
1089
- ): String {
1090
- function emitWrapper(code: String): String {
1091
- if(conditions.isEmpty()) {"{\n"} else {
1092
- "if(" + conditions.join(" && ") + ") {\n"
1093
- } +
1094
- variables.join() +
1095
- code +
1096
- "\n}"
1097
- }
1098
- Pair(matchCase.patterns, matchCase.guards).{
1099
- | Pair([p, ...ps], _) =>
1100
- self.emitPattern(
1101
- arguments.grab(0)
1102
- p
1103
- arguments.dropFirst()
1104
- matchCase.MatchCase(patterns = ps)
1105
- conditions
1106
- variables
1107
- jump
1108
- last
1109
- break
1110
- lastCase
1111
- async
1112
- )
1113
- | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) {variables.isEmpty()} =>
1114
- let newCase = matchCase.MatchCase(patterns = [], guards = [])
1115
- self.emitCase([], newCase, [...conditions, self.emitTerm(e, async)], [], jump, last, break, lastCase, async)
1116
- | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) =>
1117
- let newCase = matchCase.MatchCase(patterns = [], guards = [])
1118
- let code = self.emitCase([], newCase, [self.emitTerm(e, async)], [], jump, last, break, lastCase, async)
1119
- emitWrapper(code)
1120
- | Pair([], [guard, ...guards]) =>
1121
- let guardName = "_guard" + (guards.size() + 1)
1122
- let newCase = matchCase.MatchCase(patterns = [guard.pattern], guards = guards)
1123
- let code =
1124
- "const " + guardName + " = " + self.emitTerm(guard.term, async) + ";\n" +
1125
- self.emitCase([guardName], newCase, [], [], jump, last, break, lastCase, async)
1126
- emitWrapper(code)
1127
- | Pair([], []) =>
1128
- let statementsCode = self.emitStatements(matchCase.body, last, break, async)
1129
- let lastLine = statementsCode.reverse().takeWhile {_ != '\n'}.reverse()
1130
- let returns =
1131
- lastLine.startsWith("return ") ||
1132
- lastLine.startsWith("break ") ||
1133
- lastLine.startsWith("continue ") ||
1134
- lastLine.startsWith("return;") ||
1135
- lastLine.startsWith("break;") ||
1136
- lastLine.startsWith("continue;") ||
1137
- lastLine.startsWith("throw ")
1138
- let code = statementsCode + if(jump && last && !returns) {
1139
- "\nreturn"
1140
- } elseIf {jump && !returns && !lastCase} {
1141
- if(break) {"\ncontinue"} else {"\nbreak"}
1142
- } else {
1143
- ""
1144
- }
1145
- emitWrapper(code)
1146
- }
1147
- }
1148
-
1149
- emitPattern(
1150
- argument: String
1151
- pattern: MatchPattern
1152
- arguments: List[String]
1153
- matchCase: MatchCase
1154
- conditions: List[String]
1155
- variables: List[String]
1156
- jump: Bool
1157
- last: Bool
1158
- break: Bool
1159
- lastCase: Bool
1160
- async: Bool
1161
- ): String {
1162
- function addCondition(condition: String): List[String] {
1163
- if(lastCase) {conditions} else {[...conditions, condition]}
1164
- }
1165
- pattern.{
1166
- | PString(_, value) =>
1167
- let newConditions = addCondition(argument + " === " + value)
1168
- self.emitCase(arguments, matchCase, newConditions, variables, jump, last, break, lastCase, async)
1169
- | PInt(_, value) =>
1170
- let newConditions = addCondition(argument + " === " + value)
1171
- self.emitCase(arguments, matchCase, newConditions, variables, jump, last, break, lastCase, async)
1172
- | PChar(_, value) =>
1173
- let newConditions = addCondition(argument + " === " + charLiteralToNumber(value))
1174
- self.emitCase(arguments, matchCase, newConditions, variables, jump, last, break, lastCase, async)
1175
- | PVariable(_, None) =>
1176
- self.emitCase(arguments, matchCase, conditions, variables, jump, last, break, lastCase, async)
1177
- | PVariable(_, Some(name)) =>
1178
- let escaped = escapeKeyword(name)
1179
- let newVariables = if(escaped != argument) {
1180
- [...variables, "const " + escaped + " = " + argument + ";\n"]
1181
- } else {variables}
1182
- self.emitCase(arguments, matchCase, conditions, newVariables, jump, last, break, lastCase, async)
1183
- | PVariant(_, "ff:core/Bool.False", []) =>
1184
- self.emitCase(arguments, matchCase, addCondition("!" + argument), variables, jump, last, break, lastCase, async)
1185
- | PVariant(_, "ff:core/Bool.True", []) =>
1186
- self.emitCase(arguments, matchCase, addCondition(argument), variables, jump, last, break, lastCase, async)
1187
- | PVariant(_, emptyOrLink, _) {emptyOrLink == "List$Empty" || emptyOrLink == "List$Link"} =>
1188
- mutable restPattern = None
1189
- function listPatterns(matchPattern: MatchPattern): List[MatchPattern] {
1190
- | PVariant(_, "List$Empty", []) =>
1191
- []
1192
- | PVariant(_, "List$Link", [head, tail]) =>
1193
- [head, ...listPatterns(tail)]
1194
- | p =>
1195
- restPattern = Some(p)
1196
- []
1197
- }
1198
- let patterns = listPatterns(pattern)
1199
- let itemArguments = patterns.pairs().map {| Pair(i, _) => argument + "[" + i + "]"}
1200
- let restArgument = restPattern.map {_ => argument + ".slice(" + patterns.size() + ")"}
1201
- let newArguments = [...itemArguments, ...restArgument.toList(), ...arguments]
1202
- let newMatchCase = matchCase.MatchCase(
1203
- patterns = [...patterns, ...restPattern.toList(), ...matchCase.patterns]
1204
- )
1205
- let operator = restPattern.map {_ => ">="}.else {"==="}
1206
- let newConditions = addCondition(argument + ".length " + operator + " " + patterns.size())
1207
- self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, break, lastCase, async)
1208
- | PVariant(_, name, patterns) =>
1209
- let processed = self.processVariantCase(name, argument)
1210
- let newMatchCase = matchCase.MatchCase(patterns = [...patterns, ...matchCase.patterns])
1211
- let newConditions = if(processed.loneVariant || lastCase) {conditions} else {
1212
- [...conditions, argument + "." + processed.variantName]
1213
- }
1214
- let newArguments = [...processed.arguments, ...arguments]
1215
- self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, break, lastCase, async)
1216
- | PVariantAs(at, name, variableAt, variable) =>
1217
- let processed = self.processVariantCase(name, argument)
1218
- let newConditions = if(processed.loneVariant || lastCase) {conditions} else {
1219
- [...conditions, argument + "." + processed.variantName]
1220
- }
1221
- let newVariables = variable.map(escapeKeyword).filter {_ != argument}.map {
1222
- [...variables, "const " + _ + " = " + argument + ";\n"]
1223
- }.else {[]}
1224
- self.emitCase(arguments, matchCase, newConditions, newVariables, jump, last, break, lastCase, async)
1225
- | PAlias(_, pattern, variable) =>
1226
- let escaped = escapeKeyword(variable)
1227
- let newVariables = if(escaped != argument) {
1228
- [...variables, "const " + escaped + " = " + argument + ";\n"]
1229
- } else {variables}
1230
- self.emitPattern(argument, pattern, arguments, matchCase, conditions, newVariables, jump, last, break, lastCase, async)
1231
- }
1232
- }
1233
-
1234
- emitList(items: List[Pair[Term, Bool]], async: Bool): String {
1235
- "[" + items.map {
1236
- | Pair(item, False) => self.emitTerm(item, async)
1237
- | Pair(item, True) => "..." + self.emitTerm(item, async)
1238
- }.join(", ") + "]"
1239
- }
1240
-
1241
- processVariantCase(name: String, argument: String): ProcessedVariantCase {
1242
- let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
1243
- let variantName = escapeKeyword(variantNameUnqualified)
1244
- let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
1245
- let variantModule = self.otherModules.grab(moduleName)
1246
- mutable newtype = False
1247
- mutable loneVariant = False
1248
- let newArguments = variantModule.types.collectFirst {definition =>
1249
- definition.variants.find {_.name == variantName }.map {variant =>
1250
- newtype = definition.newtype
1251
- loneVariant = definition.variants.size() == 1
1252
- [...definition.commonFields.map {_.name}, ...variant.fields.map {_.name}]
1253
- }
1254
- }.grab().map {field => if(newtype) {argument} else {argument + "." + escapeKeyword(field)}}
1255
- ProcessedVariantCase(variantName, newtype, loneVariant, newArguments)
1256
- }
1257
-
1258
- processVariant(name: String): Bool {
1259
- if(name.startsWith("List$")) {False} else:
1260
- let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
1261
- let variantName = escapeKeyword(variantNameUnqualified)
1262
- let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
1263
- let variantModule = self.otherModules.grab(moduleName)
1264
- mutable newtype = False
1265
- let newArguments = variantModule.types.collectFirst {definition =>
1266
- definition.variants.find {_.name == variantName}.map {variant =>
1267
- newtype = definition.newtype
1268
- }
1269
- }.grab()
1270
- newtype
1271
- }
1272
-
1273
- emitArgument(callAt: Location, argument: Argument, async: Bool): String {
1274
- argument.value.{
1275
- | ECall(_, StaticCall("ff:core/SourceLocation.callSite", _, _), _, _, _, _) =>
1276
- "\"" + self.moduleName + ":" + callAt.line + ":" + callAt.column +
1277
- "," + self.packagePair.group + "," + self.packagePair.name + "\""
1278
- | value =>
1279
- self.emitTerm(value, async)
1280
- }
1281
- }
1282
-
1283
- emitComma(term: Term, async: Bool): String {
1284
- term.{
1285
- | ESequential(_, ESequential(_, ESequential(_, before1, before2), before3), after) {
1286
- safeCommable(before1) && safeCommable(before2) && safeCommable(before3) && safeCommable(after)
1287
- } =>
1288
- "(" + self.emitStatements(before1, False, False, async) + ", " +
1289
- self.emitStatements(before2, False, False, async) + ", " +
1290
- self.emitStatements(before3, False, False, async) + ", " +
1291
- self.emitTerm(after, async) + ")"
1292
- | ESequential(_, ESequential(_, before1, before2), after) {
1293
- safeCommable(before1) && safeCommable(before2) && safeCommable(after)
1294
- } =>
1295
- "(" + self.emitStatements(before1, False, False, async) + ", " +
1296
- self.emitStatements(before2, False, False, async) + ", " +
1297
- self.emitTerm(after, async) + ")"
1298
- | ESequential(_, before, after) {
1299
- safeCommable(before) && safeCommable(after)
1300
- } =>
1301
- "(" + self.emitStatements(before, False, False, async) + ", " +
1302
- self.emitTerm(after, async) + ")"
1303
- | _ =>
1304
- self.emitTerm(term, async)
1305
- }
1306
- }
1307
-
1308
- }
1309
-
1310
- data ProcessedVariantCase(
1311
- variantName: String
1312
- newtype: Bool
1313
- loneVariant: Bool
1314
- arguments: List[String]
1315
- )
1316
-
1317
- detectIfElse(term: Term): List[Pair[Term, Term]] {
1318
- | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
1319
- [Pair(condition.value, invokeImmediately(body.value))]
1320
- | ECall(at, StaticCall("ff:core/Option.Option_elseIf", _, _), _, _, [option, condition, body], _) =>
1321
- let list = detectIfElse(option.value)
1322
- if(list.isEmpty()) {[]} else:
1323
- [Pair(invokeImmediately(condition.value), invokeImmediately(body.value)), ...list]
1324
- | ECall(at, StaticCall("ff:core/Option.Option_else", _, _), _, _, [option, body], _) =>
1325
- let list = detectIfElse(option.value)
1326
- if(list.isEmpty()) {[]} else:
1327
- [Pair(EVariant(at, "ff:core/Bool.True", [], None), invokeImmediately(body.value)), ...list]
1328
- | _ =>
1329
- []
1330
- }
1331
-
1332
- invokeImmediately(function: Term): Term {
1333
- | ELambda(_, Lambda(_, effect, [MatchCase(_, [], [], body)])) =>
1334
- body
1335
- | _ =>
1336
- let effect = TConstructor(function.at, "Q$", []) // Awaits more often than required in async context
1337
- ECall(function.at, DynamicCall(function, False), effect, [], [], [])
1338
- }
1339
-
1340
- safeCommable(term: Term): Bool {
1341
- term.{
1342
- | EField _ => True
1343
- | EVariable _ => True
1344
- | EAssign _ => True
1345
- | EAssignField _ => True
1346
- | ECall _ => True
1347
- | ECopy _ => True
1348
- | EVariant _ => True
1349
- | EString(_, _) => True
1350
- | EInt(_, _) => True
1351
- | EChar(_, _) => True
1352
- | EFloat(_, _) => True
1353
- | EList _ => True
1354
- | EPipe _ => True
1355
- | ERecord _ => True
1356
- | EWildcard _ => True
1357
- | _ => False
1358
- }
1359
- }
1360
-
1361
- extractTypeName(type: Type): String {
1362
- | TVariable(at, index) =>
1363
- fail(at, "Unexpected type variable: $" + index)
1364
- | TConstructor t =>
1365
- t.name
1366
- }
1367
-
1368
- firstTypeName(types: List[Type]): String {
1369
- types.grabFirst().{
1370
- | TConstructor t => t.name
1371
- | TVariable t => fail(t.at, " is still a unification variable")
1372
- }
1373
- }
1374
-
1375
- makeDictionaryName(traitName: String, typeName: String): String {
1376
- traitName.replace(".", "_").replace(":", "_").replace("/", "_") + "$" +
1377
- typeName.replace(".", "_").replace(":", "_").replace("/", "_")
1378
- }
1379
-
1380
- charLiteralToNumber(charLiteral: String): String {
1381
- | "'\\t'" => "9"
1382
- | "'\\n'" => "10"
1383
- | "'\\r'" => "13"
1384
- | "'\\\"'" => "34"
1385
- | "'\\''" => "39"
1386
- | value => "" + value.grab(1).codeUnit
1387
- }
1388
-
1389
- escapeResolved(word: String): String {
1390
- let parts = word.replace(":", ".").replace("/", ".").split('.')
1391
- let initialParts = parts.dropLast()
1392
- if(initialParts.isEmpty()) {
1393
- escapeKeyword(parts.grabLast())
1394
- } else {
1395
- initialParts.join("_") + "." + escapeKeyword(parts.grabLast())
1396
- }
1397
- }
1398
-
1399
- escapeKeyword(word: String): String {
1400
- if(word.grabFirst().isAsciiLower()) {word + "_"} else {word}
1401
- }
1402
-
1403
- effectTypeIsAsync(effect: Type): Bool {
1404
- | TConstructor(_, "Q$", _) => True
1405
- | _ => False
1406
- }
1407
-
1408
- safeBare(quotedString: String): Option[String] {
1409
- // TODO: And not a reserved word in JS
1410
- quotedString.removeFirst("\"").flatMap {_.removeLast("\"")}.filter {s =>
1411
- s.first().any {_.isAsciiLetter()} && s.all {_.isAsciiLetterOrDigit()}
1412
- }
1413
- }
1414
-
1415
- noSideEffects(term: Term): Bool {
1416
- term.{
1417
- | EField(_, _, e, _) => noSideEffects(e)
1418
- | EVariable(_, _) => True
1419
- | ECall(_, StaticCall("ff:core/BrowserSystem.BrowserSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1420
- | ECall(_, StaticCall("ff:core/BuildSystem.BuildSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1421
- | ECall(_, StaticCall("ff:core/NodeSystem.NodeSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1422
- | ECall(_, StaticCall("ff:core/Js.jsSystem", _, _), _, _, _, _) => True
1423
- | EString(_, _) => True
1424
- | EInt(_, _) => True
1425
- | EChar(_, _) => True
1426
- | EFloat(_, _) => True
1427
- | _ => False
1428
- }
1429
- }
1430
-
1431
- primitiveTypes = [
1432
- "ff:core/Bool.Bool"
1433
- "ff:core/Char.Char"
1434
- "ff:core/Int.Int"
1435
- "ff:core/Float.Float"
1436
- "ff:core/String.String"
1437
- ].toSet()
1
+ import Syntax
2
+ import Patterns
3
+ import JsImporter
4
+
5
+ class JsEmitter(
6
+ otherModules: Map[String, Module]
7
+ jsImporter: JsImporter
8
+ emitTarget: EmitTarget
9
+ isMainModule: Bool
10
+ compilerModuleFileUrl: Option[String]
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
+ compilerModuleFileUrl: Option[String]
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
+ compilerModuleFileUrl = compilerModuleFileUrl
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.compilerModuleFileUrl.map {"import * as $firefly_compiler from '" + _ + "'"}.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
+ }
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
+ let assignmentCode = if(!mutable || valueCode != "(void 0)") {" = " + valueCode} else {""}
164
+ mutability + " " + escapeKeyword(definition.name) + assignmentCode + ";"
165
+ }
166
+
167
+ emitExtendsDefinition(definition: DExtend): String {
168
+ let typeName = extractTypeName(definition.type).reverse().takeWhile {_ != '.'}.reverse()
169
+ let methods = definition.methods.map {method =>
170
+ method.DFunction(
171
+ signature = method.signature.Signature(
172
+ name = typeName + "_" + method.signature.name
173
+ )
174
+ )
175
+ }
176
+ let syncMethods = methods.map {"export " + self.emitFunctionDefinition(_, False)}
177
+ let asyncMethods = self.withEmittingAsync {methods.map {"export " + self.emitFunctionDefinition(_, True)}}
178
+ [...syncMethods, ...asyncMethods].join("\n\n")
179
+ }
180
+
181
+ emitInstanceDefinition(definition: DInstance): String {
182
+ let name = makeDictionaryName(definition.traitName, firstTypeName(definition.typeArguments))
183
+ let methods = definition.methods.map {self.emitFunctionDefinition(_, False)}.map {_.dropFirst("function ".size())} // TODO
184
+ let asyncMethods = self.withEmittingAsync {
185
+ definition.methods.map {self.emitFunctionDefinition(_, True)}.map {"async " + _.dropFirst("async function ".size())} // TODO
186
+ }
187
+ let body = "{\n" + [...methods, ...asyncMethods].join(",\n") + "\n}"
188
+ definition.constraints.{
189
+ | [] =>
190
+ "export const " + name + " = " + body + ";"
191
+ | constraints =>
192
+ let dictionaries = constraints.map {c =>
193
+ makeDictionaryName(c.name, firstTypeName(c.generics))
194
+ }
195
+ "export function " + name + "(" + dictionaries.join(", ") + ") { return " + body + "}"
196
+ }
197
+ }
198
+
199
+ emitFunctionDefinition(definition: DFunction, async: Bool, suffix: String = ""): String {
200
+ let signature = self.emitSignature(definition.signature, async, suffix)
201
+ definition.body.{
202
+ | Lambda(_, effect, [matchCase]) {
203
+ matchCase.patterns.all {
204
+ | PVariable(_, None) => True
205
+ | _ => False
206
+ }
207
+ } =>
208
+ let body = self.emitTailCall {self.emitStatements(matchCase.body, True, False, async)}
209
+ signature + " {\n" + body + "\n}"
210
+ | Lambda(_, effect, cases) =>
211
+ Patterns.convertAndCheck(self.otherModules, cases)
212
+ let escapedArguments = definition.signature.parameters.map {_.name + "_a"}
213
+ let shadowingWorkaround = definition.signature.parameters.map {p =>
214
+ "const " + p.name + "_a = " + escapeKeyword(p.name) + ";"
215
+ }.join("\n")
216
+ let body = self.emitTailCall {
217
+ let casesString = cases.pairs().map {| Pair(i, c) =>
218
+ let lastCase = i == cases.size() - 1
219
+ self.emitCase(escapedArguments, c, [], [], True, True, False, lastCase, async)
220
+ }.join("\n")
221
+ shadowingWorkaround + "\n" + casesString
222
+ }
223
+ signature + " {\n" + body + "\n}"
224
+ }
225
+ }
226
+
227
+ emitTailCall(body: () => String): String {
228
+ let outerTailCallUsed = self.tailCallUsed
229
+ self.tailCallUsed = False
230
+ let result = body()
231
+ let tailCallUsed = self.tailCallUsed
232
+ self.tailCallUsed = outerTailCallUsed
233
+ if(tailCallUsed) {
234
+ "_tailcall: for(;;) {\n" + result + "\nreturn\n}"
235
+ } else {
236
+ result
237
+ }
238
+ }
239
+
240
+ emitSignature(signature: Signature, async: Bool, suffix: String = ""): String {
241
+ let parameterStrings = signature.parameters.map {self.emitParameter(_, async)}
242
+ let dictionaryStrings = signature.constraints.map {c =>
243
+ makeDictionaryName(c.name, firstTypeName(c.generics))
244
+ }
245
+ let controller = if(async) {["$task"]} else {[]}
246
+ let parameters = "(" + [...parameterStrings, ...dictionaryStrings, ...controller].join(", ") + ")"
247
+ let prefix = if(async) {"async "} else {""}
248
+ let asyncSuffix = if(async) {"$"} else {""}
249
+ prefix + "function " + escapeKeyword(signature.name) + suffix + asyncSuffix + parameters
250
+ }
251
+
252
+ emitParameter(parameter: Parameter, async: Bool): String {
253
+ let defaultValue = parameter.default.map {" = " + self.emitTerm(_, async) }.else {""}
254
+ escapeKeyword(parameter.name) + defaultValue
255
+ }
256
+
257
+ emitTypeDefinition(definition: DType): String {
258
+ if(definition.newtype) {"// newtype " + definition.name} else:
259
+ "// type " + definition.name + "\n" +
260
+ definition.variants.map {self.emitVariantDefinition(definition, _)}.join("\n")
261
+ }
262
+
263
+ emitVariantDefinition(typeDefinition: DType, definition: Variant): String {
264
+ let allFields = [...typeDefinition.commonFields, ...definition.fields]
265
+ let fields = allFields.map {escapeKeyword(_.name)}.join(", ")
266
+ if(allFields.isEmpty()) {
267
+ "const " + definition.name + "$ = {" + definition.name + ": true};\n" +
268
+ "export function " + definition.name + "(" + fields + ") {\n" +
269
+ "return " + definition.name + "$;\n" +
270
+ "}"
271
+ } elseIf {typeDefinition.variants.size() == 1} {
272
+ "export function " + definition.name + "(" + fields + ") {\n" +
273
+ "return {" + fields + "};\n" +
274
+ "}"
275
+ } else {
276
+ "export function " + definition.name + "(" + fields + ") {\n" +
277
+ "return {" + definition.name + ": true, " + fields + "};\n" +
278
+ "}"
279
+ }
280
+ }
281
+
282
+ emitTerm(term: Term, async: Bool): String {term.{
283
+ | EString(at, value) {value.startsWith("\"\"\"")} =>
284
+ "`" + value.dropFirst(3).dropLast(3).replace("`", "\\`") + "`" // TODO: Fix escaping
285
+ | EString(at, value) => value
286
+ | EChar(at, value) => charLiteralToNumber(value)
287
+ | EInt(at, value) => value
288
+ | EFloat(at, value) => value
289
+ | EVariable(at, name) => escapeResolved(name)
290
+ | EList(at, _, items) =>
291
+ self.emitList(items, async)
292
+ | EVariant(at, "ff:core/Bool.False", _, _) =>
293
+ "false"
294
+ | EVariant(at, "ff:core/Bool.True", _, _) =>
295
+ "true"
296
+ | EVariant(at, "ff:core/Unit.Unit", _, _) =>
297
+ "(void 0)"
298
+ | EVariant(at, name, _, arguments) =>
299
+ let argumentsString = arguments.toList().flatten().map {self.emitArgument(at, _, async)}.join(", ")
300
+ let newtype = self.processVariant(name)
301
+ if(newtype) {argumentsString} else:
302
+ escapeResolved(name) + "(" + argumentsString + ")"
303
+ | EVariantIs(at, "ff:core/Bool.False", _) =>
304
+ "function(_v) { return !_v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
305
+ | EVariantIs(at, "ff:core/Bool.True", _) =>
306
+ "function(_v) { return _v ? ff_core_Option.Some(_v) : ff_core_Option.None(); }"
307
+ | EVariantIs(at, "ff:core/Unit.Unit", _) =>
308
+ "function(_v) { return ff_core_Option.Some(_v); }"
309
+ | EVariantIs(at, name, _) =>
310
+ let n = name.reverse().takeWhile {_ != '.'}.reverse()
311
+ "(function(_v) { " +
312
+ "return _v." + escapeResolved(n) + " ? ff_core_Option.Some(_v) : ff_core_Option.None();" +
313
+ "})"
314
+ | ECopy(at, name, record, fields) =>
315
+ let fieldCode = fields.map {f => escapeKeyword(f.name) + " = " + self.emitTerm(f.value, async)}.join(", ")
316
+ "{..." + self.emitTerm(record, async) + ", " + fieldCode + "}"
317
+ | EField(at, newtype, record, field) =>
318
+ if(newtype) {self.emitTerm(record, async)} else:
319
+ self.emitTerm(record, async) + "." + escapeKeyword(field)
320
+ | ELambda(at, Lambda(_, effect, [MatchCase(_, patterns, [], body)])) {
321
+ patterns.all {| PVariable _ => True | _ => False }
322
+ } =>
323
+ let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
324
+ let patternParameters = patterns.map {
325
+ | PVariable p => p.name.map(escapeKeyword).else {"_"}
326
+ | _ => panic("!")
327
+ }
328
+ let controller = if(newAsync) {["$task"]} else {[]}
329
+ let parameters = [...patternParameters, ...controller].join(", ")
330
+ let prefix = if(newAsync) {"async "} else {""}
331
+ "(" + prefix + "(" + parameters + ") => {\n" + self.emitStatements(body, True, False, newAsync) + "\n})"
332
+ | ELambda(at, Lambda(_, effect, cases)) =>
333
+ let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
334
+ let controller = if(newAsync) {["$task"]} else {[]}
335
+ Patterns.convertAndCheck(self.otherModules, cases)
336
+ let arguments = cases.grab(0).patterns.pairs().map {"_" + (_.first + 1)}
337
+ let escapedArguments = arguments.map(escapeKeyword) // emitCase arguments must be preescaped
338
+ let caseStrings = cases.pairs().map {| Pair(i, c) =>
339
+ let lastCase = i == cases.size() - 1
340
+ self.emitCase(escapedArguments, c, [], [], True, True, False, lastCase, newAsync)
341
+ }
342
+ let prefix = if(newAsync) {"async "} else {""}
343
+ "(" + prefix + "(" + [...escapedArguments, ...controller].join(", ") + ") => " +
344
+ "{\n" + caseStrings.join("\n") + "\n})"
345
+ | EPipe(at, value, effect, function) =>
346
+ let await = async && effectTypeIsAsync(effect)
347
+ let c = if(await) {", $task"} else {""}
348
+ let call = "(" + self.emitTerm(function, async) + ")(" + self.emitTerm(value, async) + c + ")"
349
+ if(await) {"(await " + call + ")"} else {call}
350
+ | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
351
+ self.emitSpecialCall(term, async, name, arguments.map {_.value}, dictionaries) | Some(code)
352
+ } =>
353
+ code
354
+ | ECall(at, StaticCall(name, _, True), effect, typeArguments, arguments, dictionaries) =>
355
+ let await = async && effectTypeIsAsync(effect)
356
+ let dictionaryStrings = dictionaries.map {self.emitDictionary(_)}
357
+ let ds = dictionaryStrings.dropFirst()
358
+ let d = dictionaryStrings.grabFirst()
359
+ let asyncSuffix = if(await) {"$"} else {""}
360
+ let n = escapeKeyword(name.reverse().takeWhile {_ != '.'}.reverse()) + asyncSuffix
361
+ let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
362
+ let controller = if(await) {["$task"]} else {[]}
363
+ let call = d + "." + n + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
364
+ if(await) {"(await " + call + ")"} else {call}
365
+ | ECall(at, StaticCall(name, _, _), effect, typeArguments, arguments, dictionaries) =>
366
+ if(name.contains("bundleForBrowser")) { // TODO: Delete this test (for branch arraysonly)
367
+ if(!arguments.grab(0).name.contains("system")) {
368
+ Log.debug("Wrong arguments for bundleForBrowser: " + Show.show(arguments.map {_.name}))
369
+ throw(GrabException())
370
+ }
371
+ }
372
+ detectIfElse(term).{
373
+ | [] =>
374
+ let await = async && effectTypeIsAsync(effect)
375
+ let ds = dictionaries.map {self.emitDictionary(_)}
376
+ let functionCode = escapeResolved(name) + if(await) {"$"} else {""}
377
+ let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
378
+ let controller = if(await) {["$task"]} else {[]}
379
+ let call = functionCode + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
380
+ if(await) {"(await " + call + ")"} else {call}
381
+ | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
382
+ "(" + list.foldLeft(self.emitComma(elseBody, async)) {| otherwise, Pair(condition, body) =>
383
+ self.emitComma(condition, async) +
384
+ "\n? " + self.emitComma(body, async) + "\n: " + otherwise
385
+ } + ")"
386
+ | list =>
387
+ "(" + list.foldLeft("ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
388
+ self.emitComma(condition, async) +
389
+ "\n? ff_core_Option.Some(" + self.emitComma(body, async) + ")\n: " + otherwise
390
+ } + ")"
391
+ }
392
+ | ECall(at, DynamicCall(function, _), effect, typeArguments, arguments, dictionaries) =>
393
+ let await = async && effectTypeIsAsync(effect)
394
+ if(!dictionaries.isEmpty()) {fail(at, "Internal error: Dictionaries in lambda call")}
395
+ let functionCode = self.emitTerm(function, async)
396
+ let emittedArguments = arguments.map {self.emitArgument(at, _, async)}
397
+ let controller = if(await) {["$task"]} else {[]}
398
+ let call = functionCode + "(" + [...emittedArguments, ...controller].join(", ") + ")"
399
+ if(await) {"(await " + call + ")"} else {call}
400
+ | ERecord(at, fields) =>
401
+ if(fields.isEmpty()) {"{}"} else {
402
+ let list = fields.map {f => escapeKeyword(f.name) + ": " + self.emitTerm(f.value, async)}
403
+ "{\n" + list.join(",\n") + "\n}"
404
+ }
405
+ | EWildcard(at, index) =>
406
+ if(index == 0) {fail(at, "Unbound wildcard")}
407
+ "_w" + index
408
+ | _ {async} =>
409
+ "(await (async function() {\n" + self.emitStatements(term, True, False, async) + "\n})())"
410
+ | _ =>
411
+ "(function() {\n" + self.emitStatements(term, True, False, async) + "\n})()"
412
+ }}
413
+
414
+ emitField(term: Term, async: Bool, dot: String = "."): String {
415
+ term.{
416
+ | EString(_, q) {safeBare(q) | Some(s)} => dot + s
417
+ | _ => "[" + self.emitTerm(term, async) + "]"
418
+ }
419
+ }
420
+
421
+ emitDictionary(d: Dictionary): String {
422
+ let m = if(d.moduleName != "") {
423
+ d.packagePair.groupName("_") + "_" + d.moduleName.replace("/", "_") + "."
424
+ } else {""}
425
+ let c = m + makeDictionaryName(d.traitName, d.typeName)
426
+ if(d.dictionaries.isEmpty()) {
427
+ c
428
+ } else {
429
+ c + "(" + d.dictionaries.map {self.emitDictionary(_)}.join(", ") + ")"
430
+ }
431
+ }
432
+
433
+ emitStatements(term: Term, last: Bool, break: Bool, async: Bool): String {
434
+ term.{
435
+ | EFunctions(at, functions, body) =>
436
+ let functionStrings = functions.map {f =>
437
+ let newAsync = self.emittingAsync && effectTypeIsAsync(f.signature.effect)
438
+ self.emitFunctionDefinition(f, newAsync)
439
+ }
440
+ functionStrings.join("\n") + "\n" + self.emitStatements(body, last, break, async)
441
+ | ELet(at, mutable, name, valueType, value, body) =>
442
+ self.emitLetDefinition(DLet(at, name, valueType, value), mutable, async) + "\n" +
443
+ self.emitStatements(body, last, break, async)
444
+ | EVariant(at, "ff:core/Unit.Unit", _, _) =>
445
+ ""
446
+ | ESequential(_, EVariant(_, "ff:core/Unit.Unit", _, _), after) =>
447
+ self.emitStatements(after, last, break, async)
448
+ | ESequential(_, before, EVariant(_, "ff:core/Unit.Unit", _, _)) =>
449
+ self.emitStatements(before, False, break, async)
450
+ | ESequential(at, before, after) =>
451
+ self.emitStatements(before, False, False, async) + ";\n" +
452
+ self.emitStatements(after, last, break, async)
453
+ | EAssign(at, operator, name, value) =>
454
+ escapeKeyword(name) + " " + operator + "= " + self.emitTerm(value, async)
455
+ | EAssignField(at, operator, record, field, value) =>
456
+ self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
457
+ self.emitTerm(value, async)
458
+ | ECall(at, StaticCall(name, True, instanceCall), effect, _, arguments, _) =>
459
+ if(instanceCall) {throw(CompileError(at, "Not yet implemented: Tail calls on trait methods."))}
460
+ self.tailCallUsed = True
461
+ let pair = arguments.map {a =>
462
+ Some(Pair(
463
+ "const " + escapeKeyword(a.name.grab() + "_r") + " = " + self.emitTerm(a.value, async) + ";"
464
+ escapeKeyword(a.name.grab()) + " = " + escapeKeyword(a.name.grab() + "_r")
465
+ ))
466
+ }.collect {_}.unzip()
467
+ "{\n" + pair.first.join("\n") + "\n" + pair.second.join("\n") + "\ncontinue _tailcall\n}"
468
+ | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
469
+ self.emitSpecialStatement(term, last, async, name, arguments.map {_.value}, dictionaries) | Some(code)
470
+ } =>
471
+ code
472
+ | EPipe(at, value, _, ELambda(_, Lambda(_, _, cases))) =>
473
+ Patterns.convertAndCheck(self.otherModules, cases)
474
+ if(!last && !break) {"do "}.else {""} +
475
+ "{\nconst _1 = " + self.emitTerm(value, async) + ";\n" +
476
+ cases.pairs().map {| Pair(i, c) =>
477
+ let lastCase = i == cases.size() - 1
478
+ self.emitCase(["_1"], c, [], [], True, last, break, lastCase, async)
479
+ }.join("\n") +
480
+ "\n}" + if(!last && !break) {" while(false)"}.else {""}
481
+ | _ =>
482
+ detectIfElse(term).{
483
+ | [] =>
484
+ if(break) {
485
+ "if(!" + self.emitComma(term, async) + ") break"
486
+ } elseIf {last} {
487
+ "return " + self.emitTerm(term, async)
488
+ } else {
489
+ self.emitTerm(term, async)
490
+ }
491
+ | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
492
+ let initial = "{\n" + self.emitStatements(elseBody, last, break, async) + "\n}"
493
+ list.foldLeft(initial) {| otherwise, Pair(condition, body) =>
494
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
495
+ self.emitStatements(body, last, break, async) + "\n} else " + otherwise
496
+ }
497
+ | list {!last} =>
498
+ list.foldLeft("{}") {| otherwise, Pair(condition, body) =>
499
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
500
+ self.emitStatements(body, last, break, async) + "\n} else " + otherwise
501
+ }
502
+ | list =>
503
+ list.foldLeft("return ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
504
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
505
+ "return ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n} else " + otherwise
506
+ }
507
+ }
508
+ }
509
+ }
510
+
511
+ emitSpecialCall(
512
+ term: Term
513
+ async: Bool
514
+ name: String
515
+ arguments: List[Term]
516
+ dictionaries: List[Dictionary]
517
+ ): Option[String] {
518
+ name.{
519
+ | operator {!operator.grabFirst().isAsciiLetter()} {arguments | [value]} =>
520
+ Some("(" + operator + self.emitTerm(value, async) + ")")
521
+ | operator {!operator.grabFirst().isAsciiLetter()} {arguments | [left, right]} =>
522
+ Some("(" + self.emitTerm(left, async) + " " + operator + " " + self.emitTerm(right, async) + ")")
523
+ | "ff:core/List.List_grab" {arguments | [e1, e2]} {noSideEffects(e1) && noSideEffects(e2)} =>
524
+ let code1 = self.emitTerm(e1, async)
525
+ let code2 = self.emitTerm(e2, async)
526
+ Some(
527
+ "(" + code1 + "[" + code2 + "] ?? " +
528
+ "ff_core_List.List_grab(" + code1 + ", " + code2 + "))"
529
+ )
530
+ | "ff:core/Array.Array_grab" {arguments | [e1, e2]} {noSideEffects(e1) && noSideEffects(e2)} =>
531
+ let code1 = self.emitTerm(e1, async)
532
+ let code2 = self.emitTerm(e2, async)
533
+ Some(
534
+ "(" + code1 + ".array[" + code2 + "] ?? " +
535
+ "ff_core_Array.Array_grab(" + code1 + ", " + code2 + "))"
536
+ )
537
+ | "ff:core/List.List_size" {arguments | [e]} =>
538
+ Some(
539
+ self.emitTerm(e, async) + ".length"
540
+ )
541
+ | "ff:core/Array.Array_size" {arguments | [e]} =>
542
+ Some(
543
+ self.emitTerm(e, async) + ".array.length"
544
+ )
545
+ | "ff:core/String.String_size" {arguments | [e]} =>
546
+ Some(
547
+ self.emitTerm(e, async) + ".length"
548
+ )
549
+ | "ff:core/Equal.equals" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
550
+ primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
551
+ } =>
552
+ Some("(" + self.emitTerm(left, async) + " === " + self.emitTerm(right, async) + ")")
553
+ | "ff:core/Equal.notEquals" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
554
+ primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
555
+ } =>
556
+ Some("(" + self.emitTerm(left, async) + " !== " + self.emitTerm(right, async) + ")")
557
+ | "ff:core/Ordering.before" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
558
+ primitiveTypes.contains(typeName)
559
+ } =>
560
+ Some("(" + self.emitTerm(left, async) + " < " + self.emitTerm(right, async) + ")")
561
+ | "ff:core/Ordering.notBefore" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
562
+ primitiveTypes.contains(typeName)
563
+ } =>
564
+ Some("(" + self.emitTerm(left, async) + " >= " + self.emitTerm(right, async) + ")")
565
+ | "ff:core/Ordering.after" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
566
+ primitiveTypes.contains(typeName)
567
+ } =>
568
+ Some("(" + self.emitTerm(left, async) + " > " + self.emitTerm(right, async) + ")")
569
+ | "ff:core/Ordering.notAfter" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
570
+ primitiveTypes.contains(typeName)
571
+ } =>
572
+ Some("(" + self.emitTerm(left, async) + " <= " + self.emitTerm(right, async) + ")")
573
+ | "ff:core/List.fillBy" {term | ECall call} {arguments | [size, ELambda(at,
574
+ Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)@c])@l
575
+ )]} {
576
+ !effectTypeIsAsync(call.effect)
577
+ } =>
578
+ let n = name.map {escapeResolved(_)}.else {"i"}
579
+ let newAsync = self.emittingAsync && effectTypeIsAsync(call.effect)
580
+ let await = if(newAsync) {"await "} else {""}
581
+ Some(
582
+ await + "((() => {\n" +
583
+ "const size = " + self.emitTerm(size, async) + ";\n" + // Not correct if async and body isn't
584
+ "const result = [];\n" +
585
+ "for(let " + n + " = 0; " + n + " < size; " + n + "++) {\n" +
586
+ "result.push(" + self.emitTerm(body, newAsync) + ");\n" +
587
+ "}\n" +
588
+ "return result;\n" +
589
+ "})())"
590
+ )
591
+ | "ff:core/Js.import" {arguments | [EString(_, url)]} =>
592
+ self.emitTarget.{
593
+ | EmitBrowser => Some("(() => {throw new Error('Node.js imports are not supported in the browser')})()")
594
+ | _ => Some(self.jsImporter.add(url.replace("\"", "")))
595
+ }
596
+ | "ff:core/Js.browserImport" {arguments | [EString(_, url)]} =>
597
+ self.emitTarget.{
598
+ | EmitBrowser => Some(self.jsImporter.add(url.replace("\"", "")))
599
+ | _ => Some("(() => {throw new Error('Browser imports are not supported in Node.js')})()")
600
+ }
601
+ | "ff:core/Js.dynamicImport" {arguments | [url]} =>
602
+ Some("import(" + self.emitTerm(url, async) + ")")
603
+ | "ff:core/Js.await" {arguments | [body]} =>
604
+ if(async) {
605
+ Some("(await " + self.emitTerm(body, async) + ")")
606
+ } else {
607
+ Some(self.emitTerm(body, async))
608
+ }
609
+ | name {name.removeFirst("ff:core/Js.async") | Some(n)} {n.all {_.isAsciiDigit()}} {
610
+ arguments | [ELambda(at, Lambda(_, effect, [MatchCase(_, patterns, [], body)]))]
611
+ } {
612
+ patterns.all {| PVariable _ => True | _ => False }
613
+ } =>
614
+ let patternParameters = patterns.map {
615
+ | PVariable p => p.name.map(escapeKeyword).else {"_"}
616
+ | _ => panic("!")
617
+ }
618
+ Some(
619
+ "async (" + patternParameters.join(", ") + ") => {\n" +
620
+ self.emitStatements(body, True, False, False) +
621
+ "\n}"
622
+ )
623
+ | name {name.startsWith("ff:core/Js.async")} =>
624
+ throw(CompileError(term.at, "JS async functions must take a simple parameter list"))
625
+ | "ff:core/Js.cancelled" =>
626
+ Some(if(async) {"$task.controller_.signal.aborted"} else {"false"})
627
+ | "ff:core/Js.throwIfCancelled" =>
628
+ Some(if(async) {"((() => ff_core_Task.Task_throwIfAborted($task))())"} else {""})
629
+ | "ff:core/Js.currentTask" =>
630
+ Some("$task")
631
+ | "ff:core/Js.controller" =>
632
+ Some("$task.controller_")
633
+ | "ff:core/Js.setController" {arguments | [a]} =>
634
+ Some("($task.controller_ = " + self.emitTerm(a, async) + ")")
635
+ | "ff:core/Js.inAsync" =>
636
+ Some(if(self.emittingAsync) {"true"} else {"false"})
637
+ | "ff:core/Js.inBrowser" =>
638
+ Some(if(self.emitTarget == EmitBrowser) {"true"} else {"false"})
639
+ | "ff:core/Js.inNode" =>
640
+ Some(if(self.emitTarget == EmitNode) {"true"} else {"false"})
641
+ | "ff:core/Js.inBuild" =>
642
+ Some(if(self.emitTarget == EmitBuild) {"true"} else {"false"})
643
+ | "ff:core/Js.value" {arguments | [e]} =>
644
+ Some(self.emitTerm(e, async))
645
+ | "ff:core/Js.fromValue" {arguments | [e]} =>
646
+ Some(self.emitTerm(e, async))
647
+ | "ff:core/Js.rawIdentifier" {arguments | [EString(_, op)]} =>
648
+ Some(op.replace("\"", ""))
649
+ | "ff:core/Js.unaryOperator" {arguments | [EString(_, op), a1]} =>
650
+ Some("(" + op.replace("\"", "") + self.emitTerm(a1, async) + ")")
651
+ | "ff:core/Js.binaryOperator" {arguments | [EString(_, op), a1, a2]} =>
652
+ Some("(" + self.emitTerm(a1, async) + " " + op.replace("\"", "") + " " + self.emitTerm(a2, async) + ")")
653
+ | "ff:core/Js.shortCircuitingOperator" {arguments | [EString(_, op), a1, a2]} =>
654
+ Some("(" + self.emitTerm(a1, async) + " " + op.replace("\"", "") + " " + self.emitTerm(invokeImmediately(a2), async) + ")")
655
+ | "ff:core/JsValue.JsValue_spreadToArray" {arguments | [e1]} =>
656
+ Some("[..." + self.emitTerm(e1, async) + "]")
657
+ | "ff:core/JsValue.JsValue_typeof" {arguments | [e]} =>
658
+ Some("(typeof " + self.emitTerm(e, async) + ")")
659
+ | "ff:core/JsValue.JsValue_instanceof" {arguments | [e1, e2]} =>
660
+ Some("(" + self.emitTerm(e1, async) + " instanceof " + self.emitTerm(e2, async) + ")")
661
+ | "ff:core/JsValue.JsValue_get" {arguments | [e1, e2]} =>
662
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async))
663
+ | "ff:core/JsValue.JsValue_equals" {arguments | [e1, e2]} =>
664
+ Some("(" + self.emitTerm(e1, async) + " === " + self.emitTerm(e2, async) + ")")
665
+ | "ff:core/JsValue.JsValue_notEquals" {arguments | [e1, e2]} =>
666
+ Some("(" + self.emitTerm(e1, async) + " !== " + self.emitTerm(e2, async) + ")")
667
+ | "ff:core/Int.Int_bitAnd" {arguments | [e1, e2]} =>
668
+ Some("(" + self.emitTerm(e1, async) + " & " + self.emitTerm(e2, async) + ")")
669
+ | "ff:core/Int.Int_bitRightUnsigned" {arguments | [e1, e2]} =>
670
+ Some("(" + self.emitTerm(e1, async) + " >>> " + self.emitTerm(e2, async) + ")")
671
+ | "ff:core/Int.Int_bitRight" {arguments | [e1, e2]} =>
672
+ Some("(" + self.emitTerm(e1, async) + " >> " + self.emitTerm(e2, async) + ")")
673
+ | name {name.removeFirst("ff:core/JsValue.JsValue_call") | Some(n)} {n.all {_.isAsciiDigit()}} {
674
+ arguments | [e1, e2, ...es]
675
+ } =>
676
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
677
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + "(" + argumentCode + ")")
678
+ | name {name.removeFirst("ff:core/JsValue.JsValue_callValue") | Some(n)} {n.all {_.isAsciiDigit()}} {
679
+ arguments | [e1, ...es]
680
+ } =>
681
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
682
+ Some(self.emitTerm(e1, async) + "(" + argumentCode + ")")
683
+ | name {name.removeFirst("ff:core/JsValue.JsValue_new") | Some(n)} {n.all {_.isAsciiDigit()}} {
684
+ arguments | [e1, ...es]
685
+ } =>
686
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
687
+ Some("(new " + self.emitTerm(e1, async) + "(" + argumentCode + ")" + ")")
688
+ | name {name == "ff:core/JsValue.JsValue_with" || name == "ff:core/Json.Json_with"} =>
689
+ function go(e: Term, fields: List[Pair[Term, Term]]): String {
690
+ e.{
691
+ | ECall(_, StaticCall(n, _, _), _, _, [a1, a2, a3], _) {n == name} =>
692
+ go(a1.value, [Pair(a2.value, a3.value), ...fields])
693
+ | ECall(_, StaticCall(n, _, _), _, _, as, _) {
694
+ n == "ff:core/JsSystem.JsSystem_object" ||
695
+ n == "ff:core/JsSystem.JsSystem_new0" ||
696
+ n == "ff:core/Js.object" ||
697
+ n == "ff:core/Js.new0" ||
698
+ n == "ff:core/Json.Json_object" ||
699
+ n == "ff:core/Json.Json_new0"
700
+ } {
701
+ as.all {noSideEffects(_.value)}
702
+ } =>
703
+ "{" + fields.map {p =>
704
+ self.emitField(p.first, async, dot = "") + ": " + self.emitTerm(p.second, async)
705
+ }.join(", ") + "}"
706
+ | _ =>
707
+ "{..." + self.emitTerm(e, async) + ", " + fields.map {p =>
708
+ self.emitField(p.first, async, dot = "") + ": " + self.emitTerm(p.second, async)
709
+ }.join(", ") + "}"
710
+ }
711
+ }
712
+ Some(go(term, []))
713
+ | name {name.removeFirst("ff:core/JsSystem.JsSystem_call") | Some(n)} {n.all {_.isAsciiDigit()}} {
714
+ arguments | [e1, EString(_, q)@e2, ...es]
715
+ } {noSideEffects(e1)} =>
716
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
717
+ Some(safeBare(q).else {"globalThis[" + self.emitTerm(e2, async) + "]"} + "(" + argumentCode + ")")
718
+ | name {name.removeFirst("ff:core/JsSystem.JsSystem_function") | Some(n)} {n.all {_.isAsciiDigit()}} {
719
+ arguments | [e1, e2]
720
+ } {noSideEffects(e1)} {term | ECall call} {!effectTypeIsAsync(call.effect)} =>
721
+ Some(self.emitTerm(e2, async))
722
+ | "ff:core/JsSystem.JsSystem_get" {arguments | [e1, EString(_, q)@e2]} {noSideEffects(e1)} =>
723
+ Some(safeBare(q).else {"globalThis[" + self.emitTerm(e2, async) + "]"})
724
+ | "ff:core/JsSystem.JsSystem_object" {arguments | [e]} {noSideEffects(e)} =>
725
+ Some("{}")
726
+ | "ff:core/JsSystem.JsSystem_new0" {arguments | [e]} {noSideEffects(e)} =>
727
+ Some("{}")
728
+ | "ff:core/JsSystem.JsSystem_null" {arguments | [e]} {noSideEffects(e)} =>
729
+ Some("null")
730
+ | "ff:core/JsSystem.JsSystem_undefined" {arguments | [e]} {noSideEffects(e)} =>
731
+ Some("(void 0)")
732
+ | name {name.removeFirst("ff:core/Js.call") | Some(n)} {n.all {_.isAsciiDigit()}} {
733
+ arguments | [EString(_, q)@e1, ...es]
734
+ } =>
735
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
736
+ Some(safeBare(q).else {"globalThis[" + self.emitTerm(e1, async) + "]"} + "(" + argumentCode + ")")
737
+ | name {name.removeFirst("ff:core/Js.function") | Some(n)} {n.all {_.isAsciiDigit()}} {
738
+ arguments | [e1]
739
+ } {term | ECall call} =>
740
+ if(self.emittingAsync && effectTypeIsAsync(call.effect)) {
741
+ let argumentCode = 1.to(n.grabInt()).map {"a_" + _}.join(", ")
742
+ let taskCode = if(argumentCode == "") {"$task"} else {", $task"}
743
+ Some("(async (" + argumentCode + ") => await " + self.emitTerm(e1, async) + "(" + argumentCode + taskCode + "))")
744
+ } else {
745
+ Some(self.emitTerm(e1, async))
746
+ }
747
+ | "ff:core/Js.get" {arguments | [EString(_, q)@e1]} =>
748
+ Some(safeBare(q).else {"globalThis[" + self.emitTerm(e1, async) + "]"})
749
+ | "ff:core/Js.object" =>
750
+ Some("{}")
751
+ | "ff:core/Js.new0" =>
752
+ Some("{}")
753
+ | "ff:core/Js.null" =>
754
+ Some("null")
755
+ | "ff:core/Js.undefined" =>
756
+ Some("(void 0)")
757
+ | "ff:core/Js.globalThis" =>
758
+ Some("globalThis")
759
+ | "ff:core/BrowserSystem.BrowserSystem_js" {arguments | [e]} {noSideEffects(e)} =>
760
+ Some("globalThis")
761
+ | "ff:core/BuildSystem.BuildSystem_js" {arguments | [e]} {noSideEffects(e)} =>
762
+ Some("globalThis")
763
+ | "ff:core/NodeSystem.NodeSystem_js" {arguments | [e]} {noSideEffects(e)} =>
764
+ Some("globalThis")
765
+ | "ff:core/Js.jsSystem" =>
766
+ Some("globalThis")
767
+ | "ff:core/Json.string" {arguments | [e]} =>
768
+ Some(self.emitTerm(e, async))
769
+ | "ff:core/Json.int" {arguments | [e]} =>
770
+ Some(self.emitTerm(e, async))
771
+ | "ff:core/Json.float" {arguments | [e]} =>
772
+ Some(self.emitTerm(e, async))
773
+ | "ff:core/Json.bool" {arguments | [e]} =>
774
+ Some(self.emitTerm(e, async))
775
+ | "ff:core/Json.array" {arguments | [e]} =>
776
+ Some(self.emitTerm(e, async))
777
+ | "ff:core/Json.null" {arguments | [e]} =>
778
+ Some("null")
779
+ | "ff:core/Json.object" {arguments | [e]} =>
780
+ Some("{}")
781
+ | _ =>
782
+ None
783
+ }
784
+ }
785
+
786
+ emitSpecialStatement(
787
+ term: Term
788
+ last: Bool
789
+ async: Bool
790
+ name: String
791
+ arguments: List[Term]
792
+ dictionaries: List[Dictionary]
793
+ ): Option[String] {
794
+ name.{
795
+ | "ff:core/Core.while" {arguments | [condition, body]} =>
796
+ Some(
797
+ "while(" + self.emitComma(invokeImmediately(condition), async) + ") {\n" +
798
+ self.emitStatements(invokeImmediately(body), False, False, async) + "\n}"
799
+ )
800
+ | "ff:core/Core.doWhile" {arguments | [doWhileBody]} {
801
+ invokeImmediately(doWhileBody) | body
802
+ } =>
803
+ Some(
804
+ "while(true) {\n" +
805
+ self.emitStatements(body, False, True, async) +
806
+ "\n}"
807
+ )
808
+ | "ff:core/Option.Option_each" {arguments | [list, ELambda(_, Lambda(_, _, [
809
+ MatchCase(_, [PVariable(_, name)], [], body)
810
+ ]))]} =>
811
+ Some(
812
+ "{\nconst if_o = " + self.emitTerm(list, async) + "\nif(if_o.Some) {\n" +
813
+ name.map {"const " + escapeKeyword(_) + " = if_o.value_;\n"}.else {""} +
814
+ self.emitStatements(body, last, False, async) +
815
+ "\n}\n}"
816
+ )
817
+ | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
818
+ ECall(_, StaticCall(r, _, _), _, _, [start, end], _)
819
+ ELambda(_, Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)]))
820
+ ]} {r == "ff:core/Int.Int_until" || r == "ff:core/Int.Int_to"} =>
821
+ let startCode = self.emitTerm(start.value, async)
822
+ let endCode = self.emitTerm(end.value, async)
823
+ let op = if(r == "ff:core/Int.Int_until") {"<"} else {"<="}
824
+ Some(
825
+ "for(let " +
826
+ "for_i = " + startCode + ", for_e = " + endCode + "; for_i " + op + " for_e; for_i++) {\n" +
827
+ name.map {"const " + escapeKeyword(_) + " = for_i;\n"}.else {""} +
828
+ self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
829
+ "\n}"
830
+ )
831
+ | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
832
+ ECall(_, StaticCall("ff:core/List.List_reverse", _, _), _, _, [
833
+ Argument(_, _, ECall(_, StaticCall(r, _, _), _, _, [start, end], _))
834
+ ], _)
835
+ ELambda(_, Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)]))
836
+ ]} {r == "ff:core/Int.Int_until" || r == "ff:core/Int.Int_to"} =>
837
+ let startCode = self.emitTerm(start.value, async)
838
+ let endCode = self.emitTerm(end.value, async)
839
+ let delta = if(r == "ff:core/Int.Int_until") {" - 1"} else {""}
840
+ Some(
841
+ "for(let " +
842
+ "for_e = " + startCode + ", for_i = " + endCode + delta + "; for_i >= for_e; for_i--) {\n" +
843
+ name.map {"const " + escapeKeyword(_) + " = for_i;\n"}.else {""} +
844
+ self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
845
+ "\n}"
846
+ )
847
+ | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
848
+ ECall(_, StaticCall("ff:core/List.List_zip", _, _), _, _, [list1, list2], _)
849
+ ELambda(_, Lambda(_, _, [MatchCase(_, [
850
+ PVariant(_, "ff:core/Pair.Pair", [PVariable(_, name1), PVariable(_, name2)])
851
+ ], [], body)]))
852
+ ]} =>
853
+ let fusion1 = self.emitLightFusion("for_a", list1.value, async)
854
+ let fusion2 = self.emitLightFusion("for_a2", list2.value, async)
855
+ let start1 = fusion1.second.first
856
+ let end1 = fusion1.second.second
857
+ let listCode1 = fusion1.first
858
+ let start2 = fusion2.second.first
859
+ let end2 = fusion2.second.second
860
+ let listCode2 = fusion2.first
861
+ Some(
862
+ "for(let for_a = " + listCode1 + ", for_i = " + start1 + ", for_l = " + end1 + ", " +
863
+ "for_a2 = " + listCode2 + ", for_i2 = " + start2 + ", for_l2 = " + end2 +
864
+ "; for_i < for_l && for_i2 < for_l2; for_i++, for_i2++) {\n" +
865
+ name1.map {"const " + escapeKeyword(_) + " = for_a[for_i];\n"}.else {""} +
866
+ name2.map {"const " + escapeKeyword(_) + " = for_a2[for_i2];\n"}.else {""} +
867
+ self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
868
+ "\n}"
869
+ )
870
+ | n {n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile"} {arguments | [
871
+ ECall(_, StaticCall("ff:core/List.List_pairs", _, _), _, _, [list], _)
872
+ ELambda(_, Lambda(_, _, [MatchCase(_, [
873
+ PVariant(_, "ff:core/Pair.Pair", [PVariable(_, name1), PVariable(_, name2)])
874
+ ], [], body)]))
875
+ ]} =>
876
+ let fusion = self.emitLightFusion("for_a", list.value, async)
877
+ let start = fusion.second.first
878
+ let end = fusion.second.second
879
+ let listCode = fusion.first
880
+ Some(
881
+ "for(let for_a = " + listCode + ", for_i = " + start + ", for_l = " + end +
882
+ "; for_i < for_l; for_i++) {\n" +
883
+ name1.map {"const " + escapeKeyword(_) + " = for_i;\n"}.else {""} +
884
+ name2.map {"const " + escapeKeyword(_) + " = for_a[for_i];\n"}.else {""} +
885
+ self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
886
+ "\n}"
887
+ )
888
+ | n {
889
+ n == "ff:core/List.List_each" || n == "ff:core/List.List_eachWhile" ||
890
+ n == "ff:core/Array.Array_each" || n == "ff:core/Array.Array_eachWhile"
891
+ } {
892
+ arguments | [list, ELambda(_, Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)]))]
893
+ } =>
894
+ let fusion = self.emitLightFusion("for_a", list, async)
895
+ let start = fusion.second.first
896
+ let end = fusion.second.second
897
+ let listCode = fusion.first + if(n.startsWith("ff:core/Array.")) {".array"} else {""}
898
+ Some(
899
+ "for(let for_a = " + listCode + ", for_i = " + start + ", for_l = " + end +
900
+ "; for_i < for_l; for_i++) {\n" +
901
+ name.map {"const " + escapeKeyword(_) + " = for_a[for_i];\n"}.else {""} +
902
+ self.emitStatements(body, last, n.endsWith("eachWhile"), async) +
903
+ "\n}"
904
+ )
905
+ | "ff:core/Array.Array_push" {arguments | [array, value]} =>
906
+ Some(self.emitTerm(array, async) + ".array.push(" + self.emitTerm(value, async) + ")")
907
+ | "ff:core/Core.if" {arguments | [condition, body]} =>
908
+ Some(
909
+ "if(" + self.emitComma(condition, async) + ") {\n" +
910
+ if(last) {
911
+ "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body), async) +
912
+ ")\n} else return ff_core_Option.None()"
913
+ } else {
914
+ self.emitStatements(invokeImmediately(body), False, False, async) + "\n}"
915
+ }
916
+ )
917
+ | "ff:core/Core.throw" {term | ECall c} {c.arguments | [argument]} {dictionaries | [dictionary]} =>
918
+ let d = self.emitDictionary(dictionary)
919
+ let a = self.emitArgument(term.at, argument, async)
920
+ Some("throw Object.assign(new Error(), {ffException: ff_core_Any.toAny_(" + a + ", " + d + ")})")
921
+ | "ff:core/Try.Try_catch" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
922
+ Some(code)
923
+ | "ff:core/Try.Try_catchAny" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
924
+ Some(code)
925
+ | "ff:core/Try.Try_finally" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
926
+ Some(code)
927
+ | "ff:core/Js.throwIfCancelled" =>
928
+ Some(if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""})
929
+ | "ff:core/Js.throw" {term | ECall c} {c.arguments | [argument]} =>
930
+ Some("throw " + self.emitTerm(argument.value, async))
931
+ | "ff:core/JsValue.JsValue_set" {arguments | [e1, e2, e3]} =>
932
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " = " + self.emitTerm(e3, async))
933
+ | "ff:core/JsValue.JsValue_increment" {arguments | [e1, e2, e3]} =>
934
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " += " + self.emitTerm(e3, async))
935
+ | "ff:core/JsValue.JsValue_decrement" {arguments | [e1, e2, e3]} =>
936
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " -= " + self.emitTerm(e3, async))
937
+ | "ff:core/JsSystem.JsSystem_set" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
938
+ Some(s + " = " + self.emitTerm(e3, async))
939
+ | "ff:core/JsSystem.JsSystem_increment" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
940
+ Some(s + " += " + self.emitTerm(e3, async))
941
+ | "ff:core/JsSystem.JsSystem_decrement" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
942
+ Some(s + " -= " + self.emitTerm(e3, async))
943
+ | "ff:core/Js.set" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
944
+ Some(s + " = " + self.emitTerm(e2, async))
945
+ | "ff:core/Js.increment" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
946
+ Some(s + " += " + self.emitTerm(e2, async))
947
+ | "ff:core/Js.decrement" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
948
+ Some(s + " -= " + self.emitTerm(e2, async))
949
+ | _ =>
950
+ None
951
+ }
952
+ }
953
+
954
+ emitLightFusion(listName: String, list: Term, async: Bool): Pair[String, Pair[String, String]] {
955
+ mutable start = "0"
956
+ mutable end = listName + ".length"
957
+ let listCode = list.{
958
+ | ECall(_, StaticCall("ff:core/List.List_dropFirst", _, _), _, _, [a1, a2], _) =>
959
+ start = self.emitTerm(a2.value, async)
960
+ if(!start.all {_.isAsciiDigit()}) {
961
+ start = "Math.max(" + start + ", 0)"
962
+ }
963
+ self.emitTerm(a1.value, async)
964
+ | ECall(_, StaticCall("ff:core/List.List_dropLast", _, _), _, _, [a1, a2], _) =>
965
+ let count = self.emitTerm(a2.value, async)
966
+ if(!count.all {_.isAsciiDigit()}) {
967
+ end = end + " - Math.max(" + count + ", 0)"
968
+ } else {
969
+ end = end + " - " + count
970
+ }
971
+ self.emitTerm(a1.value, async)
972
+ | ECall(_, StaticCall("ff:core/List.List_takeFirst", _, _), _, _, [a1, a2], _) =>
973
+ end = self.emitTerm(a2.value, async)
974
+ if(!end.all {_.isAsciiDigit()}) {
975
+ end = "Math.max(" + end + ", 0)"
976
+ }
977
+ end = "Math.min(" + end + ", " + listName + ".length)"
978
+ self.emitTerm(a1.value, async)
979
+ | ECall(_, StaticCall("ff:core/List.List_takeLast", _, _), _, _, [a1, a2], _) =>
980
+ let count = self.emitTerm(a2.value, async)
981
+ if(!count.all {_.isAsciiDigit()}) {
982
+ start = "Math.max(" + listName + ".length - Math.max(" + count + ", 0), 0)"
983
+ } else {
984
+ start = "Math.max(" + listName + ".length - " + count + ", 0)"
985
+ }
986
+ self.emitTerm(a1.value, async)
987
+ | _ =>
988
+ self.emitTerm(list, async)
989
+ }
990
+ Pair(listCode, Pair(start, end))
991
+ }
992
+
993
+ emitTryCatchFinally(term: Term, last: Bool, async: Bool): Option[String] {
994
+ function emitCatch(catchEffect: Type, cases: List[MatchCase]): String {
995
+ let catchAsync = self.emittingAsync && effectTypeIsAsync(catchEffect)
996
+ Patterns.convertAndCheck(self.otherModules, cases)
997
+ let arguments = ["_exception.value_", "_error"]
998
+ cases.{
999
+ | [case] =>
1000
+ self.emitCase(arguments, case, [], [], False, last, False, True, catchAsync)
1001
+ | cs =>
1002
+ let caseStrings = cases.pairs().map {| Pair(i, c) =>
1003
+ let lastCase = i == cases.size() - 1
1004
+ self.emitCase(arguments, c, [], [], True, last, False, lastCase, catchAsync)
1005
+ }
1006
+ if(last) {caseStrings.join("\n")} else {"do {\n" + caseStrings.join("\n") + "\n} while(false)"}
1007
+ }
1008
+ }
1009
+ term.{
1010
+ | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
1011
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1012
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1013
+ ], _))
1014
+ Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
1015
+ ], _) =>
1016
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1017
+ let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
1018
+ Some(
1019
+ "try {\n" + self.emitStatements(tryBody, last, False, tryAsync) +
1020
+ "\n} finally {\n" + self.emitStatements(finallyBody, last, False, finallyAsync) + "\n}"
1021
+ )
1022
+ | ECall(_, StaticCall("ff:core/Try.Try_catchAny", _, _), _, _, [
1023
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1024
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1025
+ ], _))
1026
+ Argument(_, _, ELambda(_, Lambda(_, catchEffect, [MatchCase(_, [PVariable(_, name)], [], catchBody)])))
1027
+ ], _) =>
1028
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1029
+ Some(
1030
+ "try {\n" +
1031
+ self.emitStatements(tryBody, last, False, tryAsync) +
1032
+ "\n} catch" + name.map {"(" + escapeKeyword(_) + ")"}.else {""} + " {\n" +
1033
+ self.emitStatements(catchBody, last, False, tryAsync) +
1034
+ "\n}"
1035
+ )
1036
+ | ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
1037
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1038
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1039
+ ], _))
1040
+ Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
1041
+ ], [dictionary]) =>
1042
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1043
+ let d = self.emitDictionary(dictionary)
1044
+ Some(
1045
+ "try {\n" + self.emitStatements(tryBody, last, False, tryAsync) +
1046
+ "\n} catch(_error) {\n" +
1047
+ "if(!_error.ffException) throw _error\n" +
1048
+ "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
1049
+ "if(!_exception.Some) throw _error\n" +
1050
+ emitCatch(catchEffect, cases) +
1051
+ "\n}"
1052
+ )
1053
+ | ECall(_, StaticCall("ff:core/Try.Try_finally", _, _), _, _, [
1054
+ Argument(_, _, ECall(_, StaticCall("ff:core/Try.Try_catch", _, _), _, _, [
1055
+ Argument(_, _, ECall(_, StaticCall("ff:core/Core.try", _, _), _, _, [
1056
+ Argument(_, _, ELambda(_, Lambda(_, tryEffect, [MatchCase(_, [], [], tryBody)])))
1057
+ ], _))
1058
+ Argument(_, _, ELambda(_, Lambda(_, catchEffect, cases)))
1059
+ ], [dictionary]))
1060
+ Argument(_, _, ELambda(_, Lambda(_, finallyEffect, [MatchCase(_, [], [], finallyBody)])))
1061
+ ], _) =>
1062
+ let tryAsync = self.emittingAsync && effectTypeIsAsync(tryEffect)
1063
+ let finallyAsync = self.emittingAsync && effectTypeIsAsync(finallyEffect)
1064
+ let d = self.emitDictionary(dictionary)
1065
+ Some(
1066
+ "try {\n" + self.emitStatements(tryBody, last, False, tryAsync) +
1067
+ "\n} catch(_error) {\n" +
1068
+ "if(!_error.ffException) throw _error\n" +
1069
+ "const _exception = ff_core_Any.fromAny_(_error.ffException, " + d + ")\n" +
1070
+ "if(!_exception.Some) throw _error\n" +
1071
+ emitCatch(catchEffect, cases) +
1072
+ "\n} finally {\n" + self.emitStatements(finallyBody, last, False, finallyAsync) + "\n}"
1073
+ )
1074
+ | _ =>
1075
+ None
1076
+ }
1077
+ }
1078
+
1079
+ emitCase(
1080
+ arguments: List[String]
1081
+ matchCase: MatchCase
1082
+ conditions: List[String]
1083
+ variables: List[String]
1084
+ jump: Bool
1085
+ last: Bool
1086
+ break: Bool
1087
+ lastCase: Bool
1088
+ async: Bool
1089
+ ): String {
1090
+ function emitWrapper(code: String): String {
1091
+ if(conditions.isEmpty()) {"{\n"} else {
1092
+ "if(" + conditions.join(" && ") + ") {\n"
1093
+ } +
1094
+ variables.join() +
1095
+ code +
1096
+ "\n}"
1097
+ }
1098
+ Pair(matchCase.patterns, matchCase.guards).{
1099
+ | Pair([p, ...ps], _) =>
1100
+ self.emitPattern(
1101
+ arguments.grab(0)
1102
+ p
1103
+ arguments.dropFirst()
1104
+ matchCase.MatchCase(patterns = ps)
1105
+ conditions
1106
+ variables
1107
+ jump
1108
+ last
1109
+ break
1110
+ lastCase
1111
+ async
1112
+ )
1113
+ | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) {variables.isEmpty()} =>
1114
+ let newCase = matchCase.MatchCase(patterns = [], guards = [])
1115
+ self.emitCase([], newCase, [...conditions, self.emitTerm(e, async)], [], jump, last, break, lastCase, async)
1116
+ | Pair([], [MatchGuard(_, e, PVariant(_, "ff:core/Bool.True", _))]) =>
1117
+ let newCase = matchCase.MatchCase(patterns = [], guards = [])
1118
+ let code = self.emitCase([], newCase, [self.emitTerm(e, async)], [], jump, last, break, lastCase, async)
1119
+ emitWrapper(code)
1120
+ | Pair([], [guard, ...guards]) =>
1121
+ let guardName = "_guard" + (guards.size() + 1)
1122
+ let newCase = matchCase.MatchCase(patterns = [guard.pattern], guards = guards)
1123
+ let code =
1124
+ "const " + guardName + " = " + self.emitTerm(guard.term, async) + ";\n" +
1125
+ self.emitCase([guardName], newCase, [], [], jump, last, break, lastCase, async)
1126
+ emitWrapper(code)
1127
+ | Pair([], []) =>
1128
+ let statementsCode = self.emitStatements(matchCase.body, last, break, async)
1129
+ let lastLine = statementsCode.reverse().takeWhile {_ != '\n'}.reverse()
1130
+ let returns =
1131
+ lastLine.startsWith("return ") ||
1132
+ lastLine.startsWith("break ") ||
1133
+ lastLine.startsWith("continue ") ||
1134
+ lastLine.startsWith("return;") ||
1135
+ lastLine.startsWith("break;") ||
1136
+ lastLine.startsWith("continue;") ||
1137
+ lastLine.startsWith("throw ")
1138
+ let code = statementsCode + if(jump && last && !returns) {
1139
+ "\nreturn"
1140
+ } elseIf {jump && !returns && !lastCase} {
1141
+ if(break) {"\ncontinue"} else {"\nbreak"}
1142
+ } else {
1143
+ ""
1144
+ }
1145
+ emitWrapper(code)
1146
+ }
1147
+ }
1148
+
1149
+ emitPattern(
1150
+ argument: String
1151
+ pattern: MatchPattern
1152
+ arguments: List[String]
1153
+ matchCase: MatchCase
1154
+ conditions: List[String]
1155
+ variables: List[String]
1156
+ jump: Bool
1157
+ last: Bool
1158
+ break: Bool
1159
+ lastCase: Bool
1160
+ async: Bool
1161
+ ): String {
1162
+ function addCondition(condition: String): List[String] {
1163
+ if(lastCase) {conditions} else {[...conditions, condition]}
1164
+ }
1165
+ pattern.{
1166
+ | PString(_, value) =>
1167
+ let newConditions = addCondition(argument + " === " + value)
1168
+ self.emitCase(arguments, matchCase, newConditions, variables, jump, last, break, lastCase, async)
1169
+ | PInt(_, value) =>
1170
+ let newConditions = addCondition(argument + " === " + value)
1171
+ self.emitCase(arguments, matchCase, newConditions, variables, jump, last, break, lastCase, async)
1172
+ | PChar(_, value) =>
1173
+ let newConditions = addCondition(argument + " === " + charLiteralToNumber(value))
1174
+ self.emitCase(arguments, matchCase, newConditions, variables, jump, last, break, lastCase, async)
1175
+ | PVariable(_, None) =>
1176
+ self.emitCase(arguments, matchCase, conditions, variables, jump, last, break, lastCase, async)
1177
+ | PVariable(_, Some(name)) =>
1178
+ let escaped = escapeKeyword(name)
1179
+ let newVariables = if(escaped != argument) {
1180
+ [...variables, "const " + escaped + " = " + argument + ";\n"]
1181
+ } else {variables}
1182
+ self.emitCase(arguments, matchCase, conditions, newVariables, jump, last, break, lastCase, async)
1183
+ | PVariant(_, "ff:core/Bool.False", []) =>
1184
+ self.emitCase(arguments, matchCase, addCondition("!" + argument), variables, jump, last, break, lastCase, async)
1185
+ | PVariant(_, "ff:core/Bool.True", []) =>
1186
+ self.emitCase(arguments, matchCase, addCondition(argument), variables, jump, last, break, lastCase, async)
1187
+ | PVariant(_, emptyOrLink, _) {emptyOrLink == "List$Empty" || emptyOrLink == "List$Link"} =>
1188
+ mutable restPattern = None
1189
+ function listPatterns(matchPattern: MatchPattern): List[MatchPattern] {
1190
+ | PVariant(_, "List$Empty", []) =>
1191
+ []
1192
+ | PVariant(_, "List$Link", [head, tail]) =>
1193
+ [head, ...listPatterns(tail)]
1194
+ | p =>
1195
+ restPattern = Some(p)
1196
+ []
1197
+ }
1198
+ let patterns = listPatterns(pattern)
1199
+ let itemArguments = patterns.pairs().map {| Pair(i, _) => argument + "[" + i + "]"}
1200
+ let restArgument = restPattern.map {_ => argument + ".slice(" + patterns.size() + ")"}
1201
+ let newArguments = [...itemArguments, ...restArgument.toList(), ...arguments]
1202
+ let newMatchCase = matchCase.MatchCase(
1203
+ patterns = [...patterns, ...restPattern.toList(), ...matchCase.patterns]
1204
+ )
1205
+ let operator = restPattern.map {_ => ">="}.else {"==="}
1206
+ let newConditions = addCondition(argument + ".length " + operator + " " + patterns.size())
1207
+ self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, break, lastCase, async)
1208
+ | PVariant(_, name, patterns) =>
1209
+ let processed = self.processVariantCase(name, argument)
1210
+ let newMatchCase = matchCase.MatchCase(patterns = [...patterns, ...matchCase.patterns])
1211
+ let newConditions = if(processed.loneVariant || lastCase) {conditions} else {
1212
+ [...conditions, argument + "." + processed.variantName]
1213
+ }
1214
+ let newArguments = [...processed.arguments, ...arguments]
1215
+ self.emitCase(newArguments, newMatchCase, newConditions, variables, jump, last, break, lastCase, async)
1216
+ | PVariantAs(at, name, variableAt, variable) =>
1217
+ let processed = self.processVariantCase(name, argument)
1218
+ let newConditions = if(processed.loneVariant || lastCase) {conditions} else {
1219
+ [...conditions, argument + "." + processed.variantName]
1220
+ }
1221
+ let newVariables = variable.map(escapeKeyword).filter {_ != argument}.map {
1222
+ [...variables, "const " + _ + " = " + argument + ";\n"]
1223
+ }.else {[]}
1224
+ self.emitCase(arguments, matchCase, newConditions, newVariables, jump, last, break, lastCase, async)
1225
+ | PAlias(_, pattern, variable) =>
1226
+ let escaped = escapeKeyword(variable)
1227
+ let newVariables = if(escaped != argument) {
1228
+ [...variables, "const " + escaped + " = " + argument + ";\n"]
1229
+ } else {variables}
1230
+ self.emitPattern(argument, pattern, arguments, matchCase, conditions, newVariables, jump, last, break, lastCase, async)
1231
+ }
1232
+ }
1233
+
1234
+ emitList(items: List[Pair[Term, Bool]], async: Bool): String {
1235
+ "[" + items.map {
1236
+ | Pair(item, False) => self.emitTerm(item, async)
1237
+ | Pair(item, True) => "..." + self.emitTerm(item, async)
1238
+ }.join(", ") + "]"
1239
+ }
1240
+
1241
+ processVariantCase(name: String, argument: String): ProcessedVariantCase {
1242
+ let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
1243
+ let variantName = escapeKeyword(variantNameUnqualified)
1244
+ let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
1245
+ let variantModule = self.otherModules.grab(moduleName)
1246
+ mutable newtype = False
1247
+ mutable loneVariant = False
1248
+ let newArguments = variantModule.types.collectFirst {definition =>
1249
+ definition.variants.find {_.name == variantName }.map {variant =>
1250
+ newtype = definition.newtype
1251
+ loneVariant = definition.variants.size() == 1
1252
+ [...definition.commonFields.map {_.name}, ...variant.fields.map {_.name}]
1253
+ }
1254
+ }.grab().map {field => if(newtype) {argument} else {argument + "." + escapeKeyword(field)}}
1255
+ ProcessedVariantCase(variantName, newtype, loneVariant, newArguments)
1256
+ }
1257
+
1258
+ processVariant(name: String): Bool {
1259
+ if(name.startsWith("List$")) {False} else:
1260
+ let variantNameUnqualified = name.reverse().takeWhile {_ != '.'}.reverse()
1261
+ let variantName = escapeKeyword(variantNameUnqualified)
1262
+ let moduleName = name.dropLast(variantNameUnqualified.size() + 1)
1263
+ let variantModule = self.otherModules.grab(moduleName)
1264
+ mutable newtype = False
1265
+ let newArguments = variantModule.types.collectFirst {definition =>
1266
+ definition.variants.find {_.name == variantName}.map {variant =>
1267
+ newtype = definition.newtype
1268
+ }
1269
+ }.grab()
1270
+ newtype
1271
+ }
1272
+
1273
+ emitArgument(callAt: Location, argument: Argument, async: Bool): String {
1274
+ argument.value.{
1275
+ | ECall(_, StaticCall("ff:core/SourceLocation.callSite", _, _), _, _, _, _) =>
1276
+ "\"" + self.moduleName + ":" + callAt.line + ":" + callAt.column +
1277
+ "," + self.packagePair.group + "," + self.packagePair.name + "\""
1278
+ | value =>
1279
+ self.emitTerm(value, async)
1280
+ }
1281
+ }
1282
+
1283
+ emitComma(term: Term, async: Bool): String {
1284
+ term.{
1285
+ | ESequential(_, ESequential(_, ESequential(_, before1, before2), before3), after) {
1286
+ safeCommable(before1) && safeCommable(before2) && safeCommable(before3) && safeCommable(after)
1287
+ } =>
1288
+ "(" + self.emitStatements(before1, False, False, async) + ", " +
1289
+ self.emitStatements(before2, False, False, async) + ", " +
1290
+ self.emitStatements(before3, False, False, async) + ", " +
1291
+ self.emitTerm(after, async) + ")"
1292
+ | ESequential(_, ESequential(_, before1, before2), after) {
1293
+ safeCommable(before1) && safeCommable(before2) && safeCommable(after)
1294
+ } =>
1295
+ "(" + self.emitStatements(before1, False, False, async) + ", " +
1296
+ self.emitStatements(before2, False, False, async) + ", " +
1297
+ self.emitTerm(after, async) + ")"
1298
+ | ESequential(_, before, after) {
1299
+ safeCommable(before) && safeCommable(after)
1300
+ } =>
1301
+ "(" + self.emitStatements(before, False, False, async) + ", " +
1302
+ self.emitTerm(after, async) + ")"
1303
+ | _ =>
1304
+ self.emitTerm(term, async)
1305
+ }
1306
+ }
1307
+
1308
+ }
1309
+
1310
+ data ProcessedVariantCase(
1311
+ variantName: String
1312
+ newtype: Bool
1313
+ loneVariant: Bool
1314
+ arguments: List[String]
1315
+ )
1316
+
1317
+ detectIfElse(term: Term): List[Pair[Term, Term]] {
1318
+ | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
1319
+ [Pair(condition.value, invokeImmediately(body.value))]
1320
+ | ECall(at, StaticCall("ff:core/Option.Option_elseIf", _, _), _, _, [option, condition, body], _) =>
1321
+ let list = detectIfElse(option.value)
1322
+ if(list.isEmpty()) {[]} else:
1323
+ [Pair(invokeImmediately(condition.value), invokeImmediately(body.value)), ...list]
1324
+ | ECall(at, StaticCall("ff:core/Option.Option_else", _, _), _, _, [option, body], _) =>
1325
+ let list = detectIfElse(option.value)
1326
+ if(list.isEmpty()) {[]} else:
1327
+ [Pair(EVariant(at, "ff:core/Bool.True", [], None), invokeImmediately(body.value)), ...list]
1328
+ | _ =>
1329
+ []
1330
+ }
1331
+
1332
+ invokeImmediately(function: Term): Term {
1333
+ | ELambda(_, Lambda(_, effect, [MatchCase(_, [], [], body)])) =>
1334
+ body
1335
+ | _ =>
1336
+ let effect = TConstructor(function.at, "Q$", []) // Awaits more often than required in async context
1337
+ ECall(function.at, DynamicCall(function, False), effect, [], [], [])
1338
+ }
1339
+
1340
+ safeCommable(term: Term): Bool {
1341
+ term.{
1342
+ | EField _ => True
1343
+ | EVariable _ => True
1344
+ | EAssign _ => True
1345
+ | EAssignField _ => True
1346
+ | ECall _ => True
1347
+ | ECopy _ => True
1348
+ | EVariant _ => True
1349
+ | EString(_, _) => True
1350
+ | EInt(_, _) => True
1351
+ | EChar(_, _) => True
1352
+ | EFloat(_, _) => True
1353
+ | EList _ => True
1354
+ | EPipe _ => True
1355
+ | ERecord _ => True
1356
+ | EWildcard _ => True
1357
+ | _ => False
1358
+ }
1359
+ }
1360
+
1361
+ extractTypeName(type: Type): String {
1362
+ | TVariable(at, index) =>
1363
+ fail(at, "Unexpected type variable: $" + index)
1364
+ | TConstructor t =>
1365
+ t.name
1366
+ }
1367
+
1368
+ firstTypeName(types: List[Type]): String {
1369
+ types.grabFirst().{
1370
+ | TConstructor t => t.name
1371
+ | TVariable t => fail(t.at, " is still a unification variable")
1372
+ }
1373
+ }
1374
+
1375
+ makeDictionaryName(traitName: String, typeName: String): String {
1376
+ traitName.replace(".", "_").replace(":", "_").replace("/", "_") + "$" +
1377
+ typeName.replace(".", "_").replace(":", "_").replace("/", "_")
1378
+ }
1379
+
1380
+ charLiteralToNumber(charLiteral: String): String {
1381
+ | "'\\t'" => "9"
1382
+ | "'\\n'" => "10"
1383
+ | "'\\r'" => "13"
1384
+ | "'\\\"'" => "34"
1385
+ | "'\\''" => "39"
1386
+ | value => "" + value.grab(1).codeUnit
1387
+ }
1388
+
1389
+ escapeResolved(word: String): String {
1390
+ let parts = word.replace(":", ".").replace("/", ".").split('.')
1391
+ let initialParts = parts.dropLast()
1392
+ if(initialParts.isEmpty()) {
1393
+ escapeKeyword(parts.grabLast())
1394
+ } else {
1395
+ initialParts.join("_") + "." + escapeKeyword(parts.grabLast())
1396
+ }
1397
+ }
1398
+
1399
+ escapeKeyword(word: String): String {
1400
+ if(word.grabFirst().isAsciiLower()) {word + "_"} else {word}
1401
+ }
1402
+
1403
+ effectTypeIsAsync(effect: Type): Bool {
1404
+ | TConstructor(_, "Q$", _) => True
1405
+ | _ => False
1406
+ }
1407
+
1408
+ safeBare(quotedString: String): Option[String] {
1409
+ // TODO: And not a reserved word in JS
1410
+ quotedString.removeFirst("\"").flatMap {_.removeLast("\"")}.filter {s =>
1411
+ s.first().any {_.isAsciiLetter()} && s.all {_.isAsciiLetterOrDigit()}
1412
+ }
1413
+ }
1414
+
1415
+ noSideEffects(term: Term): Bool {
1416
+ term.{
1417
+ | EField(_, _, e, _) => noSideEffects(e)
1418
+ | EVariable(_, _) => True
1419
+ | ECall(_, StaticCall("ff:core/BrowserSystem.BrowserSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1420
+ | ECall(_, StaticCall("ff:core/BuildSystem.BuildSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1421
+ | ECall(_, StaticCall("ff:core/NodeSystem.NodeSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1422
+ | ECall(_, StaticCall("ff:core/Js.jsSystem", _, _), _, _, _, _) => True
1423
+ | EString(_, _) => True
1424
+ | EInt(_, _) => True
1425
+ | EChar(_, _) => True
1426
+ | EFloat(_, _) => True
1427
+ | _ => False
1428
+ }
1429
+ }
1430
+
1431
+ primitiveTypes = [
1432
+ "ff:core/Bool.Bool"
1433
+ "ff:core/Char.Char"
1434
+ "ff:core/Int.Int"
1435
+ "ff:core/Float.Float"
1436
+ "ff:core/String.String"
1437
+ ].toSet()