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