firefly-compiler 0.5.79 → 0.5.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/compiler/Builder.ff +31 -39
  2. package/compiler/Compiler.ff +14 -4
  3. package/compiler/DevelopMode.ff +413 -0
  4. package/compiler/JsEmitter.ff +6 -0
  5. package/compiler/Main.ff +83 -59
  6. package/compiler/ModuleCache.ff +5 -5
  7. package/core/.firefly/include/package.json +1 -1
  8. package/core/BuildSystem.ff +82 -11
  9. package/core/Error.ff +2 -2
  10. package/core/NodeSystem.ff +47 -30
  11. package/core/Path.ff +7 -2
  12. package/experimental/proxy/Main.ff +60 -0
  13. package/experimental/proxy/Runner.ff +11 -0
  14. package/experimental/proxy/Tcp.ff +162 -0
  15. package/experimental/random/Superdigit.ff +18 -0
  16. package/experimental/terrain/Main.ff +40 -0
  17. package/experimental/terrain/Terrain.ff +97 -0
  18. package/experimental/terrain/Terrain2.ff +109 -0
  19. package/fireflysite/Main.ff +0 -1
  20. package/fireflysite/assets/markdown/reference/statements-and-expressions.md +1 -1
  21. package/lsp/CompletionHandler.ff +2 -2
  22. package/output/js/ff/compiler/Builder.mjs +24 -48
  23. package/output/js/ff/compiler/Builder.mjs.map +7 -11
  24. package/output/js/ff/compiler/Compiler.mjs +66 -12
  25. package/output/js/ff/compiler/Compiler.mjs.map +18 -14
  26. package/output/js/ff/compiler/Dependencies.mjs.map +2 -2
  27. package/output/js/ff/compiler/DependencyLock.mjs.map +1 -1
  28. package/output/js/ff/compiler/Deriver.mjs.map +1 -1
  29. package/output/js/ff/compiler/DevelopMode.mjs +1045 -0
  30. package/output/js/ff/compiler/DevelopMode.mjs.map +182 -0
  31. package/output/js/ff/compiler/Dictionaries.mjs.map +1 -1
  32. package/output/js/ff/compiler/Environment.mjs.map +1 -1
  33. package/output/js/ff/compiler/Inference.mjs.map +1 -1
  34. package/output/js/ff/compiler/JsEmitter.mjs +8 -4
  35. package/output/js/ff/compiler/JsEmitter.mjs.map +5 -3
  36. package/output/js/ff/compiler/JsImporter.mjs.map +1 -1
  37. package/output/js/ff/compiler/LspHook.mjs.map +1 -1
  38. package/output/js/ff/compiler/Main.mjs +324 -117
  39. package/output/js/ff/compiler/Main.mjs.map +50 -44
  40. package/output/js/ff/compiler/Main.run.mjs +4 -0
  41. package/output/js/ff/compiler/ModuleCache.mjs +12 -8
  42. package/output/js/ff/compiler/ModuleCache.mjs.map +4 -3
  43. package/output/js/ff/compiler/Parser.mjs.map +1 -1
  44. package/output/js/ff/compiler/Patterns.mjs.map +1 -1
  45. package/output/js/ff/compiler/Resolver.mjs.map +1 -1
  46. package/output/js/ff/compiler/SourceMap.mjs.map +1 -1
  47. package/output/js/ff/compiler/Substitution.mjs.map +1 -1
  48. package/output/js/ff/compiler/Syntax.mjs.map +1 -1
  49. package/output/js/ff/compiler/Token.mjs.map +1 -1
  50. package/output/js/ff/compiler/Tokenizer.mjs.map +1 -1
  51. package/output/js/ff/compiler/Unification.mjs.map +1 -1
  52. package/output/js/ff/compiler/Wildcards.mjs.map +1 -1
  53. package/output/js/ff/compiler/Workspace.mjs.map +1 -1
  54. package/output/js/ff/core/Any.mjs.map +1 -1
  55. package/output/js/ff/core/Array.mjs.map +1 -1
  56. package/output/js/ff/core/AssetSystem.mjs.map +1 -1
  57. package/output/js/ff/core/Atomic.mjs.map +1 -1
  58. package/output/js/ff/core/Bool.mjs.map +1 -1
  59. package/output/js/ff/core/BrowserSystem.mjs.map +1 -1
  60. package/output/js/ff/core/Buffer.mjs.map +1 -1
  61. package/output/js/ff/core/BuildSystem.mjs +116 -12
  62. package/output/js/ff/core/BuildSystem.mjs.map +35 -6
  63. package/output/js/ff/core/Channel.mjs.map +1 -1
  64. package/output/js/ff/core/Char.mjs.map +1 -1
  65. package/output/js/ff/core/Core.mjs.map +1 -1
  66. package/output/js/ff/core/Crypto.mjs.map +1 -1
  67. package/output/js/ff/core/Date.mjs.map +1 -1
  68. package/output/js/ff/core/Duration.mjs.map +1 -1
  69. package/output/js/ff/core/Equal.mjs.map +1 -1
  70. package/output/js/ff/core/Error.mjs +12 -8
  71. package/output/js/ff/core/Error.mjs.map +6 -4
  72. package/output/js/ff/core/FileHandle.mjs.map +1 -1
  73. package/output/js/ff/core/Float.mjs.map +1 -1
  74. package/output/js/ff/core/HttpClient.mjs.map +1 -1
  75. package/output/js/ff/core/Int.mjs.map +1 -1
  76. package/output/js/ff/core/IntMap.mjs.map +1 -1
  77. package/output/js/ff/core/Js.mjs.map +1 -1
  78. package/output/js/ff/core/JsSystem.mjs.map +1 -1
  79. package/output/js/ff/core/JsValue.mjs.map +1 -1
  80. package/output/js/ff/core/Json.mjs.map +1 -1
  81. package/output/js/ff/core/List.mjs.map +1 -1
  82. package/output/js/ff/core/Lock.mjs.map +1 -1
  83. package/output/js/ff/core/Log.mjs.map +1 -1
  84. package/output/js/ff/core/Map.mjs.map +1 -1
  85. package/output/js/ff/core/NodeSystem.mjs +54 -20
  86. package/output/js/ff/core/NodeSystem.mjs.map +14 -6
  87. package/output/js/ff/core/Nothing.mjs.map +1 -1
  88. package/output/js/ff/core/Option.mjs.map +1 -1
  89. package/output/js/ff/core/Ordering.mjs.map +1 -1
  90. package/output/js/ff/core/Pair.mjs.map +1 -1
  91. package/output/js/ff/core/Path.mjs +30 -4
  92. package/output/js/ff/core/Path.mjs.map +8 -6
  93. package/output/js/ff/core/Queue.mjs.map +1 -1
  94. package/output/js/ff/core/Random.mjs.map +1 -1
  95. package/output/js/ff/core/RbMap.mjs.map +1 -1
  96. package/output/js/ff/core/Serializable.mjs.map +1 -1
  97. package/output/js/ff/core/Set.mjs.map +1 -1
  98. package/output/js/ff/core/Show.mjs.map +1 -1
  99. package/output/js/ff/core/SourceLocation.mjs.map +1 -1
  100. package/output/js/ff/core/Stream.mjs.map +1 -1
  101. package/output/js/ff/core/String.mjs.map +1 -1
  102. package/output/js/ff/core/StringMap.mjs.map +1 -1
  103. package/output/js/ff/core/Task.mjs.map +1 -1
  104. package/output/js/ff/core/Try.mjs.map +1 -1
  105. package/output/js/ff/core/Unit.mjs.map +1 -1
  106. package/output/js/ff/core/node_modules +1 -0
  107. package/package.json +1 -1
  108. package/vscode/package.json +1 -1
  109. package/webserver/.firefly/include/package.json +1 -1
  110. package/webserver/WebServer.ff +6 -3
@@ -16,54 +16,40 @@ build(
16
16
  mainModules: List[ModuleKey]
17
17
  resolvedDependencies: ResolvedDependencies
18
18
  compilerModulePath: Option[Path]
19
- tempPath: Path
20
19
  jsOutputPath: Path
21
20
  printMeasurements: Bool
22
21
  moduleCache: ModuleCache
23
22
  ): Unit {
24
-
25
- if(tempPath.exists()) {tempPath.delete()}
26
- tempPath.createDirectory()
27
-
28
- let jsPathFile = tempPath.slash("js")
29
- jsPathFile.createDirectory(createParentDirectories = True)
30
-
31
- let success = do {
32
- let compiler = Compiler.new(
33
- emitTarget
34
- system.mainTask()
35
- compilerModulePath
36
- jsPathFile
37
- resolvedDependencies
38
- Map.new()
39
- moduleCache
40
- lspHook = LspHook.disabled()
41
- )
42
- mainModules.each {moduleKey => compiler.emit(moduleKey, isMainModule = True)}
43
- if(printMeasurements) {compiler.printMeasurements()}
44
- resolvedDependencies.packagePaths.each {packagePair, packagePath =>
45
- resolvedDependencies.packages.get(packagePair).each {packageInfo =>
46
- processNodeModules(system, jsPathFile, packagePath, packageInfo)
47
- if(emitTarget != EmitBrowser) {
48
- processIncludes(jsPathFile, packagePath, packageInfo)
49
- }
23
+ jsOutputPath.createDirectory(createParentDirectories = True)
24
+ let compiler = Compiler.new(
25
+ emitTarget
26
+ system.mainTask()
27
+ compilerModulePath
28
+ jsOutputPath
29
+ resolvedDependencies
30
+ Map.new()
31
+ moduleCache
32
+ lspHook = LspHook.disabled()
33
+ )
34
+ mainModules.each {moduleKey => compiler.emit(moduleKey, isMainModule = True)}
35
+ if(printMeasurements) {compiler.printMeasurementsPerPhase()}
36
+ resolvedDependencies.packagePaths.each {packagePair, packagePath =>
37
+ resolvedDependencies.packages.get(packagePair).each {packageInfo =>
38
+ processNodeModules(system, jsOutputPath, packagePath, packageInfo)
39
+ if(emitTarget != EmitBrowser) {
40
+ processIncludes(jsOutputPath, packagePath, packageInfo)
50
41
  }
51
42
  }
52
- True
53
43
  }
54
-
55
- if(success) {
56
- if(jsOutputPath.exists()) {jsOutputPath.delete()}
57
- jsPathFile.renameTo(jsOutputPath)
58
- }
59
-
60
44
  }
61
45
 
62
46
  processIncludes(jsPathFile: Path, packagePath: Path, info: PackageInfo) {
63
47
  info.includes.each {include =>
64
48
  let fromPath = packagePath.slash(".firefly").slash("include").slash(include.path)
65
49
  let toPath = jsPathFile.slash(info.package.packagePair.groupName("/")).slash(include.path)
66
- toPath.createSymlinkTo(fromPath, junction = True)
50
+ if(!toPath.exists()) {
51
+ toPath.createSymlinkTo(fromPath, junction = True)
52
+ }
67
53
  }
68
54
  }
69
55
 
@@ -86,7 +72,14 @@ processNodeModules(system: NodeSystem, jsPathFile: Path, packagePath: Path, info
86
72
  }
87
73
  }
88
74
 
89
- buildViaBuildSystem(system: NodeSystem, fireflyPath: Path, mainFiles: List[Path], target: String) {
75
+ buildViaBuildSystem(
76
+ system: NodeSystem
77
+ fireflyPath: Path
78
+ mainFiles: List[Path]
79
+ target: String
80
+ moduleCache: ModuleCache = ModuleCache.new(0)
81
+ printMeasurements: Bool = False
82
+ ) {
90
83
  let resolvedDependencies = Dependencies.process(
91
84
  system.httpClient()
92
85
  DependencyLock.new(system.mainTask())
@@ -115,10 +108,9 @@ buildViaBuildSystem(system: NodeSystem, fireflyPath: Path, mainFiles: List[Path]
115
108
  mainModules = mainModuleKeys
116
109
  resolvedDependencies = resolvedDependencies.ResolvedDependencies(packagePaths = fixedPackagePaths)
117
110
  compilerModulePath = None
118
- tempPath = system.path(".firefly/temporary")
119
111
  jsOutputPath = system.path(".firefly/output").slash(target)
120
- printMeasurements = False
121
- moduleCache = ModuleCache.new(0)
112
+ printMeasurements = printMeasurements
113
+ moduleCache = moduleCache
122
114
  )
123
115
  }
124
116
 
@@ -132,6 +132,16 @@ extend self: Compiler {
132
132
  }
133
133
  }
134
134
 
135
+ printMeasurementsPerPhase(): Unit {
136
+ self.phaseDurations.toList().map {
137
+ | Pair(name, d) {name.split(' ') | [phase, module]} => Pair(phase, d)
138
+ | Pair(name, d) => Pair("Unknown", d)
139
+ }.group().each {phase, durations =>
140
+ let d = Duration(durations.map {_.seconds}.foldLeft(0.0, {_ + _}))
141
+ Log.debug(phase + " (" + durations.size() + "):\t" + d.show())
142
+ }
143
+ }
144
+
135
145
  parse(moduleKey: ModuleKey, importedAt: Option[Location]): Module {
136
146
  self.cache.cacheParsedModule(self.packagePaths, moduleKey): path =>
137
147
  self.measure("Parse", moduleKey):
@@ -214,21 +224,21 @@ extend self: Compiler {
214
224
  self.infer(i.moduleKey)
215
225
  }
216
226
 
227
+ let packagePath = self.jsOutputPath.slash(moduleKey.packagePair.group).slash(moduleKey.packagePair.name)
228
+ let jsPath = moduleKey.folders.foldLeft(packagePath) {p, f => p.slash(f)}
217
229
  let allModules = [module, ...otherModules]
218
230
  let emitter = JsEmitter.new(
219
231
  otherModules = allModules
220
232
  emitTarget = self.emitTarget
221
233
  isMainModule = isMainModule
222
- compilerModuleFileUrl = self.compilerModulePath.map {_.url()}
234
+ compilerModuleFileUrl = self.compilerModulePath.map {_.relativeUrlTo(jsPath)}
223
235
  moduleKey = moduleKey
224
236
  )
225
237
  emitter.emitModule(module)
226
- let packagePath = self.jsOutputPath.slash(moduleKey.packagePair.group).slash(moduleKey.packagePair.name)
227
- let jsPath = moduleKey.folders.foldLeft(packagePath) {p, f => p.slash(f)}
228
238
  let jsFile = jsPath.slash(moduleKey.name + ".mjs")
229
239
  let sourceMapFile = jsPath.slash(moduleKey.name + ".mjs.map")
230
240
  let source = Some(path.readText())
231
- let jsAndSourceMap = emitter.makeOutputAndSourceMap(path.relativeListTo(jsPath).join("/"), source)
241
+ let jsAndSourceMap = emitter.makeOutputAndSourceMap(path.relativeUrlTo(jsPath), source)
232
242
  jsPath.createDirectory(createParentDirectories = True)
233
243
  jsFile.writeText(jsAndSourceMap.first + "\n\n//# sourceMappingURL=" + sourceMapFile.base())
234
244
  sourceMapFile.writeText(jsAndSourceMap.second.write(Some(" ")))
@@ -0,0 +1,413 @@
1
+ import Main
2
+ import ModuleCache
3
+ import Dependencies
4
+ import DependencyLock
5
+ import Syntax
6
+ import JsEmitter
7
+ import Builder from ff:compiler
8
+
9
+ capability Runner(
10
+ lock: Lock
11
+ lockCondition: LockCondition
12
+ mutable iteration: Int
13
+ mutable state: RunnerState
14
+ mutable changedSinceCompilationStarted: Set[String]
15
+ mutable recompile: Bool
16
+ mutable appRunning: Bool
17
+ )
18
+
19
+ data RunnerState {
20
+ CompilingState
21
+ CompileErrorState(at: Option[Location], output: String)
22
+ ApplicationRunningState
23
+ ApplicationCrashedState(output: String)
24
+ }
25
+
26
+ class EsbuildContext(mutable jsValue: Option[JsValue])
27
+
28
+ run(
29
+ system: NodeSystem
30
+ fireflyPath: Path
31
+ proxyPort: Int
32
+ targetPort: Int
33
+ mainFile: String
34
+ arguments: List[String]
35
+ ) {
36
+ let esbuildContext = EsbuildContext(None)
37
+ let lock = system.mainTask().lock()
38
+ let lockCondition = lock.condition()
39
+ let runner = Runner(lock, lockCondition, 1, CompilingState, Set.new(), False, False)
40
+ startProxy(system, runner, proxyPort, targetPort)
41
+ startChangeListener(system, runner, system.path("."))
42
+ let moduleCache = ModuleCache.new(0)
43
+ while {True} {
44
+ let moduleKey = build(system, runner, mainFile, moduleCache)
45
+ let task = moduleKey.{
46
+ | None =>
47
+ system.mainTask().spawn {_ => }
48
+ | Some(key) =>
49
+ runner.lock.do {
50
+ runner.state = ApplicationRunningState
51
+ }
52
+ startApp(system, runner, fireflyPath, esbuildContext, moduleCache, key, mainFile, arguments)
53
+ }
54
+ runner.lock.do {
55
+ if(runner.appRunning) {
56
+ while {runner.appRunning} {
57
+ runner.lockCondition.sleep()
58
+ }
59
+ }
60
+ while {!runner.recompile} {
61
+ runner.lockCondition.sleep()
62
+ }
63
+ task.abort()
64
+ runner.changedSinceCompilationStarted.each {key =>
65
+ moduleCache.invalidate(key)
66
+ }
67
+ runner.state = CompilingState
68
+ runner.recompile = False
69
+ runner.changedSinceCompilationStarted = Set.new()
70
+ runner.iteration += 1
71
+ }
72
+ }
73
+ }
74
+
75
+ build(system: NodeSystem, runner: Runner, mainFile: String, moduleCache: ModuleCache): Option[ModuleKey] {
76
+ try {
77
+ Main.prepareFireflyDirectory(system.path("."))
78
+ let resolvedDependencies = Dependencies.process(
79
+ system.httpClient()
80
+ DependencyLock.new(system.mainTask())
81
+ system.path(mainFile)
82
+ )
83
+ let mainPath = system.path(mainFile)
84
+ Some(Main.buildScript(
85
+ system
86
+ mainPath
87
+ resolvedDependencies.mainPackagePair
88
+ EmitNode
89
+ resolvedDependencies
90
+ moduleCache
91
+ printMeasurements = False
92
+ ))
93
+ } tryCatch {| CompileError(at, message), error =>
94
+ Log.debug(message)
95
+ Log.debug(" at " + at.file.replace("./", "") + ":" + at.line + ":" + at.column)
96
+ runner.lock.do {
97
+ runner.state = CompileErrorState(Some(at), message)
98
+ }
99
+ None
100
+ } tryCatch {| CompileErrors(compileErrors), error =>
101
+ compileErrors.each {| CompileError(at, message) =>
102
+ Log.debug(message)
103
+ Log.debug(" at " + at.file.replace("./", "") + ":" + at.line + ":" + at.column)
104
+ runner.lock.do {
105
+ runner.state = CompileErrorState(Some(at), message)
106
+ }
107
+ }
108
+ None
109
+ } catchAny {error =>
110
+ Log.debug(error.message())
111
+ runner.lock.do {
112
+ runner.state = CompileErrorState(None, error.message())
113
+ }
114
+ None
115
+ }
116
+ }
117
+
118
+ startApp(
119
+ system: NodeSystem
120
+ runner: Runner
121
+ fireflyPath: Path
122
+ esbuildContext: EsbuildContext
123
+ moduleCache: ModuleCache
124
+ moduleKey: ModuleKey
125
+ mainFile: String
126
+ arguments: List[String]
127
+ ): Task {
128
+ let taskIteration = runner.iteration
129
+ system.mainTask().spawn {task =>
130
+ try {
131
+ let runFile = Main.locateRunFile(system, "node", moduleKey)
132
+ let runFilePath = if(runFile.contains("://")) {system.pathFromUrl(runFile)} else {system.path(runFile)}
133
+ let startPath = runFilePath.parent().grab().slash(runFilePath.base() + ".start.mjs")
134
+ startPath.writeText(
135
+ "import * as run from " + Json.string("./" + runFilePath.base()).write() + "\n" +
136
+ "globalThis.ffDevelopMode = true\n" +
137
+ // The following should be awaited, but esbuild doesn't support top level await - still seems to work though
138
+ "run.$run$(" + Json.string(fireflyPath.absolute()).write() + ", " + Json.toJson(arguments).write() + ")"
139
+ )
140
+ let esBuildPath = runFilePath.parent().grab().slash(runFilePath.base() + ".minified.js")
141
+ let context = esbuildContext.jsValue.else {
142
+ let jsValue = BuildSystem.internalNodeCallEsBuildContext(system, startPath.absolute(), esBuildPath.absolute(), minify = True)
143
+ esbuildContext.jsValue = Some(jsValue)
144
+ jsValue
145
+ }
146
+ Js.await(context->rebuild())
147
+ let relativeStartFile = esBuildPath.relativeTo(system.path("."))
148
+ let result = system.execute(relativeStartFile, arguments, node = Some({message, forkedProcess =>
149
+ if(message->ffDevelopMode === "internalCompile") {
150
+ let mainFiles: List[String] = message->mainFiles?
151
+ let mainPaths = mainFiles.map {system.path(_)}
152
+ let target: String = message->target?
153
+ runner.lock.do {
154
+ if(taskIteration == runner.iteration):
155
+ try {
156
+ Builder.buildViaBuildSystem(system, fireflyPath, mainPaths, target, moduleCache, printMeasurements = False)
157
+ forkedProcess->send(Js->(ffDevelopMode = "internalCompile"))
158
+ Unit
159
+ } tryCatch {| CompileError(at, message), error =>
160
+ runner.state = CompileErrorState(Some(at), message)
161
+ } tryCatch {| CompileErrors(compileErrors), error =>
162
+ compileErrors.each {| CompileError(at, message) =>
163
+ runner.state = CompileErrorState(Some(at), message)
164
+ }
165
+ } catchAny {error =>
166
+ runner.state = CompileErrorState(None, error.message())
167
+ }
168
+ }
169
+ }
170
+ }))
171
+ let standardOut = result.standardOut.toString()
172
+ let standardError = result.standardError.toString()
173
+ runner.lock.do {
174
+ runner.appRunning = False
175
+ runner.state.{
176
+ | ApplicationRunningState {taskIteration == runner.iteration} =>
177
+ runner.state = ApplicationCrashedState(
178
+ "Exited with code: " + result.exitCode + "\n\n" + standardOut + "\n\n" + standardError
179
+ )
180
+ | _ =>
181
+ }
182
+ runner.lockCondition.wakeAll()
183
+ }
184
+ } catchAny {error =>
185
+ runner.lock.do {
186
+ runner.appRunning = False
187
+ runner.lockCondition.wakeAll()
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ startChangeListener(
194
+ system: NodeSystem
195
+ runner: Runner
196
+ path: Path
197
+ ) {
198
+ let fs = Js.import("node:fs")
199
+ fs->watch(path.absolute(), Js->(recursive = True), Js->{eventType, fileName =>
200
+ if(!fileName.isNullOrUndefined()):
201
+ let file: String = fileName?
202
+ if(file.endsWith(".ff") || file.endsWith(".firefly-workspace")) {
203
+ let key = system.path(file).absolute()
204
+ runner.lock.do {
205
+ // Probably we should also listen for other files, e.g. resources
206
+ runner.changedSinceCompilationStarted = runner.changedSinceCompilationStarted.add(key)
207
+ }
208
+ }
209
+ })
210
+ }
211
+
212
+ startProxy(
213
+ system: NodeSystem
214
+ runner: Runner
215
+ proxyPort: Int
216
+ targetPort: Int
217
+ ) {
218
+ let net = Js.import("node:net")
219
+ let targetServer = "localhost" // 127.0.0.1
220
+ let proxyServer = net->createServer(Js->(pauseOnConnect = True), Js->{clientSocket =>
221
+ mutable targetSocket = Js.undefined()
222
+ mutable connected = False
223
+ clientSocket->on("error", Js->{err =>
224
+ if(!targetSocket.isUndefined()) {targetSocket->end()}
225
+ // Mute "Error: write EPIPE" and other errors that happen while writing a custom response to the client
226
+ //Log.debugDynamic(err)
227
+ })
228
+ function serveWaiterHtml() {
229
+ if(runner.changedSinceCompilationStarted.size() != 0) {
230
+ runner.recompile = True
231
+ system.mainTask().spawn {task =>
232
+ runner.lock.do {
233
+ runner.lockCondition.wakeAll()
234
+ }
235
+ }
236
+ }
237
+ function escapeHtml(html: String): String {
238
+ html.replace("&", "&")
239
+ .replace("'", "'")
240
+ .replace("\"", """)
241
+ .replace("<", "&lt;")
242
+ .replace(">", "&gt;")
243
+ }
244
+ let status = runner.state.{
245
+ | _ {runner.recompile} => "Restarting..."
246
+ | ApplicationCrashedState(output) => "Application crashed!<br>" + escapeHtml(output)
247
+ | ApplicationRunningState => "Starting application..."
248
+ | CompileErrorState(Some(at), output) =>
249
+ let location = escapeHtml(at.file + ":" + at.line + ":" + at.column)
250
+ let relativeFile = system.path(at.file).relativeTo(system.path("."))
251
+ let relativeLocation = escapeHtml(relativeFile + ":" + at.line + ":" + at.column)
252
+ let link = "<a href='vscode://file/" + escapeHtml(location) + "'>" + relativeLocation + "</a>"
253
+ escapeHtml(output) + "<br><br>at " + link
254
+ | CompileErrorState(None, output) => "Compiler crashed!<br>" + escapeHtml(output)
255
+ | CompilingState => "Compiling..."
256
+ }
257
+
258
+ let waiterBuffer = waiterHtml.replace("[STATUS]", status).toBuffer()
259
+ clientSocket->write("HTTP/1.1 200 OK\r\n")
260
+ clientSocket->write("Content-Type: text/html\r\n")
261
+ clientSocket->write("Content-Length: " + waiterBuffer.size() + "\r\n")
262
+ clientSocket->write("x-firefly-develop-mode: true\r\n")
263
+ clientSocket->write("Connection: close\r\n")
264
+ clientSocket->write("\r\n")
265
+ clientSocket->write(
266
+ Js->Buffer->from(waiterBuffer!->buffer, waiterBuffer!->byteOffset, waiterBuffer!->byteLength)
267
+ )
268
+ clientSocket->end()
269
+ }
270
+ targetSocket = net->createConnection(targetPort, targetServer, Js->{
271
+ connected = True
272
+ let direct = runner.state.{
273
+ | ApplicationRunningState => !runner.recompile && runner.changedSinceCompilationStarted.size() == 0
274
+ | _ => False
275
+ }
276
+ if(direct) {
277
+ clientSocket->pipe(targetSocket)->pipe(clientSocket)
278
+ clientSocket->resume()?
279
+ } else {
280
+ serveWaiterHtml()
281
+ }
282
+ })
283
+ targetSocket->on("error", Js->{err =>
284
+ if(connected) {
285
+ clientSocket->end()?
286
+ } else {
287
+ serveWaiterHtml()
288
+ }
289
+ })?
290
+ })
291
+
292
+ proxyServer->listen(proxyPort, Js->{
293
+ //print(system, "Proxy server running on port " + proxyPort)
294
+ })
295
+ }
296
+
297
+ waiterHtml = """<!DOCTYPE html>
298
+ <html lang="en">
299
+ <head>
300
+ <meta charset="UTF-8">
301
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
302
+ <title>Firefly develop mode</title>
303
+ <style>
304
+ body {
305
+ background-color: #121212;
306
+ color: #e0e0e0;
307
+ font-family: 'Courier New', monospace;
308
+ display: flex;
309
+ flex-direction: column;
310
+ justify-content: center;
311
+ align-items: center;
312
+ height: 100vh;
313
+ margin: 0;
314
+ background-image: radial-gradient(circle at center, #1a1a2e, #121212);
315
+ }
316
+
317
+ h1 {
318
+ color: #00f2ff;
319
+ text-shadow: 0 0 10px #00f2ff;
320
+ font-size: 3rem;
321
+ margin-bottom: 20px;
322
+ animation: glow 2s ease-in-out infinite alternate;
323
+ }
324
+
325
+ p {
326
+ background-color: #1a1a2e;
327
+ color: #00f2ff;
328
+ padding: 15px 30px;
329
+ border-radius: 25px;
330
+ font-size: 1.2rem;
331
+ border: 2px solid #00f2ff;
332
+ box-shadow: 0 0 15px #00f2ff;
333
+ animation: pulse 1.5s infinite;
334
+ }
335
+
336
+ a {
337
+ color: #00f2ff;
338
+ }
339
+
340
+ @media only screen and (max-width: 600px) {
341
+ h1 {
342
+ font-size: 1.7rem;
343
+ }
344
+ p {
345
+ padding: 10px 20px;
346
+ border-radius: 50px;
347
+ font-size: 1.1rem;
348
+ max-width: 100%;
349
+ box-sizing: border-box;
350
+ }
351
+ }
352
+
353
+ @keyframes glow {
354
+ from {
355
+ text-shadow: 0 0 10px #00f2ff;
356
+ }
357
+ to {
358
+ text-shadow: 0 0 20px #00f2ff, 0 0 30px #00f2ff;
359
+ }
360
+ }
361
+
362
+ @keyframes pulse {
363
+ 0% {
364
+ box-shadow: 0 0 0 0 rgba(0, 242, 255, 0.7);
365
+ }
366
+ 70% {
367
+ box-shadow: 0 0 0 10px rgba(0, 242, 255, 0);
368
+ }
369
+ 100% {
370
+ box-shadow: 0 0 0 0 rgba(0, 242, 255, 0);
371
+ }
372
+ }
373
+ </style>
374
+ <script>
375
+ let start = Date.now()
376
+ let appStarted = null
377
+ let delay = 10
378
+ let poll = async () => {
379
+ //delay *= 1.1
380
+ try {
381
+ let response = await fetch(".", {cache: 'no-store'})
382
+ if(!response.headers.has('x-firefly-develop-mode')) {
383
+ let now = Date.now()
384
+ let compiling = appStarted - start
385
+ let appStarting = now - appStarted
386
+ //window.alert("Reloading after: " + (now - start) + " ms. Compiling: " + compiling + " ms. Starting application: " + appStarting + " ms.")
387
+ window.location.reload(true)
388
+ return
389
+ } else {
390
+ let html = await response.text()
391
+ if(appStarted == null && html.includes("Starting application...")) appStarted = Date.now()
392
+ let parser = new DOMParser()
393
+ let d = parser.parseFromString(html, 'text/html')
394
+ let bodyHtml = d.body.innerHTML
395
+ if(document.body.innerHTML !== bodyHtml) {
396
+ document.body.innerHTML = bodyHtml
397
+ }
398
+ setTimeout(poll, delay)
399
+ }
400
+ } catch (error) {
401
+ console.error("Polling error:", error)
402
+ setTimeout(poll, delay)
403
+ }
404
+ }
405
+ setTimeout(poll, delay)
406
+ </script>
407
+ </head>
408
+ <body>
409
+ <h1>Firefly develop mode</h1>
410
+ <p>[STATUS]</p>
411
+ </body>
412
+ </html>
413
+ """
@@ -206,6 +206,7 @@ extend self: JsEmitter {
206
206
  "import {" + escapeKeyword(buildMain.signature.name) + "$} from './" + moduleName + ".mjs'"
207
207
  }.toList()
208
208
  "import {" + escapeKeyword(mainName) + "$} from './" + moduleName + ".mjs'"
209
+ self.emitImport(ModuleKey(PackagePair("ff", "core"), [], "Error"))
209
210
  "export async function $run$(fireflyPath_, arguments_) {"
210
211
  "Error.stackTraceLimit = 50"
211
212
  "const $task = {controller_: new AbortController(), subtasks_: new Set(), promise_: new Promise(() => {}), started_: performance.now() * 0.001}"
@@ -230,6 +231,11 @@ extend self: JsEmitter {
230
231
  ...if(self.emitTarget == EmitBuild) {[
231
232
  "await $firefly_compiler.internalCreateExecutable_$(system, '.firefly/output/executable/Main.bundle.js', '.firefly/output', ['host'], system.assets_, $task)"
232
233
  ]} else {[]}
234
+ ...if(!willRunOnNode) {[]} else {[
235
+ "} catch(error) {"
236
+ "console.error(ff_core_Error.Error_stack(error))"
237
+ "process.exit(1)"
238
+ ]}
233
239
  "} finally {"
234
240
  ...if(self.emitTarget != EmitBrowser) {[
235
241
  "$task.controller_.abort()"