firefly-compiler 0.4.79 → 0.4.81

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