firefly-compiler 0.4.79 → 0.4.80

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 (158) 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 +141 -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 +443 -411
  67. package/fireflysite/Main.ff +141 -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 +19 -0
  72. package/fireflysite/ReferenceIntroduction.ff +11 -0
  73. package/fireflysite/Styles.ff +567 -495
  74. package/fireflysite/Test.ff +38 -0
  75. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -0
  76. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +208 -0
  77. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +168 -0
  78. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -0
  79. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -0
  80. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -0
  81. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -0
  82. package/fireflysite/assets/markdown/{ControlFlow.md → scratch/ControlFlow.md} +136 -136
  83. package/fireflysite/assets/markdown/scratch/Toc.md +41 -0
  84. package/lsp/.firefly/package.ff +1 -1
  85. package/lsp/CompletionHandler.ff +828 -828
  86. package/lsp/Handler.ff +714 -714
  87. package/lsp/HoverHandler.ff +79 -79
  88. package/lsp/LanguageServer.ff +272 -272
  89. package/lsp/SignatureHelpHandler.ff +55 -55
  90. package/lsp/SymbolHandler.ff +181 -181
  91. package/lsp/TestReferences.ff +17 -17
  92. package/lsp/TestReferencesCase.ff +7 -7
  93. package/lsp/stderr.txt +1 -1
  94. package/lsp/stdout.txt +34 -34
  95. package/lux/.firefly/package.ff +1 -1
  96. package/lux/Css.ff +648 -648
  97. package/lux/CssTest.ff +48 -48
  98. package/lux/Lux.ff +487 -487
  99. package/lux/LuxEvent.ff +116 -116
  100. package/lux/Main.ff +123 -123
  101. package/lux/Main2.ff +143 -143
  102. package/output/js/ff/compiler/Builder.mjs +47 -47
  103. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  104. package/output/js/ff/compiler/Inference.mjs +2 -2
  105. package/output/js/ff/compiler/JsEmitter.mjs +18 -72
  106. package/output/js/ff/compiler/Main.mjs +4 -4
  107. package/output/js/ff/compiler/ModuleCache.mjs +4 -4
  108. package/output/js/ff/core/Array.mjs +59 -59
  109. package/output/js/ff/core/Atomic.mjs +36 -36
  110. package/output/js/ff/core/BrowserSystem.mjs +11 -11
  111. package/output/js/ff/core/BuildSystem.mjs +30 -30
  112. package/output/js/ff/core/Crypto.mjs +40 -40
  113. package/output/js/ff/core/Float.mjs +50 -0
  114. package/output/js/ff/core/HttpClient.mjs +56 -56
  115. package/output/js/ff/core/Json.mjs +147 -147
  116. package/output/js/ff/core/List.mjs +50 -50
  117. package/output/js/ff/core/Lock.mjs +97 -97
  118. package/output/js/ff/core/NodeSystem.mjs +87 -87
  119. package/output/js/ff/core/Ordering.mjs +8 -8
  120. package/output/js/ff/core/Path.mjs +231 -231
  121. package/output/js/ff/core/Random.mjs +56 -56
  122. package/output/js/ff/core/Task.mjs +39 -39
  123. package/output/js/ff/core/Try.mjs +98 -4
  124. package/package.json +1 -1
  125. package/postgresql/Pg.ff +1 -1
  126. package/rpc/.firefly/package.ff +1 -1
  127. package/rpc/Rpc.ff +70 -70
  128. package/s3/.firefly/package.ff +1 -1
  129. package/s3/S3.ff +94 -94
  130. package/unsafejs/UnsafeJs.ff +19 -19
  131. package/vscode/LICENSE.txt +21 -21
  132. package/vscode/Prepublish.ff +15 -15
  133. package/vscode/README.md +16 -16
  134. package/vscode/client/package.json +22 -22
  135. package/vscode/client/src/extension.ts +104 -104
  136. package/vscode/icons/firefly-icon.svg +10 -10
  137. package/vscode/language-configuration.json +61 -61
  138. package/vscode/package-lock.json +3623 -3623
  139. package/vscode/package.json +1 -1
  140. package/vscode/snippets.json +241 -241
  141. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  142. package/webserver/.firefly/include/package-lock.json +22 -22
  143. package/webserver/.firefly/include/package.json +5 -5
  144. package/webserver/.firefly/package.ff +2 -2
  145. package/webserver/WebServer.ff +685 -685
  146. package/websocket/.firefly/package.ff +1 -1
  147. package/websocket/WebSocket.ff +131 -131
  148. package/fireflysite/GuideAll.ff +0 -21
  149. package/fireflysite/GuideBaseTypes.ff +0 -168
  150. package/fireflysite/GuideControlFlow.ff +0 -212
  151. package/fireflysite/assets/markdown/Example.md +0 -78
  152. /package/fireflysite/assets/{NotoSansMono-Regular.ttf → font/NotoSansMono-Regular.ttf} +0 -0
  153. /package/fireflysite/assets/{NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf → font/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf} +0 -0
  154. /package/fireflysite/assets/{autocomplete-small.png → image/autocomplete-small.png} +0 -0
  155. /package/fireflysite/assets/{autocomplete.png → image/autocomplete.png} +0 -0
  156. /package/fireflysite/assets/{edit-time-error.png → image/edit-time-error.png} +0 -0
  157. /package/fireflysite/assets/{firefly-logo-notext.png → image/firefly-logo-notext.png} +0 -0
  158. /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()