firefly-compiler 0.4.4

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