firefly-compiler 0.4.79 → 0.4.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +153 -153
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +257 -257
  6. package/compiler/Compiler.ff +227 -227
  7. package/compiler/Dependencies.ff +187 -187
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/Inference.ff +2 -1
  10. package/compiler/JsEmitter.ff +940 -946
  11. package/compiler/LspHook.ff +202 -202
  12. package/compiler/Main.ff +3 -3
  13. package/compiler/ModuleCache.ff +178 -178
  14. package/compiler/Tokenizer.ff +1 -1
  15. package/compiler/Unification.ff +1 -1
  16. package/compiler/Workspace.ff +88 -88
  17. package/core/.firefly/include/package-lock.json +564 -564
  18. package/core/.firefly/include/package.json +5 -5
  19. package/core/.firefly/include/prepare.sh +1 -1
  20. package/core/.firefly/package.ff +2 -2
  21. package/core/Array.ff +265 -265
  22. package/core/Atomic.ff +64 -64
  23. package/core/Box.ff +7 -7
  24. package/core/BrowserSystem.ff +40 -40
  25. package/core/BuildSystem.ff +148 -148
  26. package/core/Crypto.ff +96 -96
  27. package/core/Equal.ff +36 -36
  28. package/core/Float.ff +25 -0
  29. package/core/HttpClient.ff +148 -148
  30. package/core/JsSystem.ff +69 -69
  31. package/core/Json.ff +434 -434
  32. package/core/List.ff +486 -486
  33. package/core/Lock.ff +144 -144
  34. package/core/NodeSystem.ff +216 -216
  35. package/core/Ordering.ff +161 -161
  36. package/core/Path.ff +401 -401
  37. package/core/Random.ff +134 -134
  38. package/core/RbMap.ff +216 -216
  39. package/core/Show.ff +43 -43
  40. package/core/SourceLocation.ff +68 -68
  41. package/core/Stream.ff +9 -9
  42. package/core/Task.ff +141 -141
  43. package/core/Try.ff +25 -4
  44. package/experimental/benchmarks/ListGrab.ff +23 -23
  45. package/experimental/benchmarks/ListGrab.java +55 -55
  46. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  47. package/experimental/benchmarks/Pyrotek45.java +64 -64
  48. package/experimental/bidirectional/Bidi.ff +88 -88
  49. package/experimental/random/Index.ff +53 -53
  50. package/experimental/random/Process.ff +120 -120
  51. package/experimental/random/Scrape.ff +51 -51
  52. package/experimental/random/Symbols.ff +73 -73
  53. package/experimental/random/Tensor.ff +52 -52
  54. package/experimental/random/Units.ff +36 -36
  55. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
  56. package/experimental/s3/S3TestPut.ff +16 -16
  57. package/experimental/tests/TestJson.ff +26 -26
  58. package/firefly.sh +0 -0
  59. package/fireflysite/.firefly/package.ff +4 -4
  60. package/fireflysite/CommunityOverview.ff +20 -20
  61. package/fireflysite/CountingButtonDemo.ff +58 -58
  62. package/fireflysite/DocumentParser.ff +331 -217
  63. package/fireflysite/ExamplesOverview.ff +40 -40
  64. package/fireflysite/FrontPage.ff +344 -360
  65. package/fireflysite/{GuideIntroduction.ff → GettingStarted.ff} +45 -52
  66. package/fireflysite/Guide.ff +443 -411
  67. package/fireflysite/Main.ff +141 -137
  68. package/fireflysite/MatchingPasswordsDemo.ff +82 -82
  69. package/fireflysite/PackagesOverview.ff +49 -49
  70. package/fireflysite/PostgresqlDemo.ff +34 -34
  71. package/fireflysite/ReferenceAll.ff +19 -0
  72. package/fireflysite/ReferenceIntroduction.ff +11 -0
  73. package/fireflysite/Styles.ff +567 -495
  74. package/fireflysite/Test.ff +38 -0
  75. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -0
  76. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +208 -0
  77. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +168 -0
  78. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -0
  79. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -0
  80. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -0
  81. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -0
  82. package/fireflysite/assets/markdown/{ControlFlow.md → scratch/ControlFlow.md} +136 -136
  83. package/fireflysite/assets/markdown/scratch/Toc.md +41 -0
  84. package/lsp/.firefly/package.ff +1 -1
  85. package/lsp/CompletionHandler.ff +828 -828
  86. package/lsp/Handler.ff +714 -714
  87. package/lsp/HoverHandler.ff +79 -79
  88. package/lsp/LanguageServer.ff +272 -272
  89. package/lsp/SignatureHelpHandler.ff +55 -55
  90. package/lsp/SymbolHandler.ff +181 -181
  91. package/lsp/TestReferences.ff +17 -17
  92. package/lsp/TestReferencesCase.ff +7 -7
  93. package/lsp/stderr.txt +1 -1
  94. package/lsp/stdout.txt +34 -34
  95. package/lux/.firefly/package.ff +1 -1
  96. package/lux/Css.ff +648 -648
  97. package/lux/CssTest.ff +48 -48
  98. package/lux/Lux.ff +487 -487
  99. package/lux/LuxEvent.ff +116 -116
  100. package/lux/Main.ff +123 -123
  101. package/lux/Main2.ff +143 -143
  102. package/output/js/ff/compiler/Builder.mjs +47 -47
  103. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  104. package/output/js/ff/compiler/Inference.mjs +2 -2
  105. package/output/js/ff/compiler/JsEmitter.mjs +18 -72
  106. package/output/js/ff/compiler/Main.mjs +4 -4
  107. package/output/js/ff/compiler/ModuleCache.mjs +4 -4
  108. package/output/js/ff/core/Array.mjs +59 -59
  109. package/output/js/ff/core/Atomic.mjs +36 -36
  110. package/output/js/ff/core/BrowserSystem.mjs +11 -11
  111. package/output/js/ff/core/BuildSystem.mjs +30 -30
  112. package/output/js/ff/core/Crypto.mjs +40 -40
  113. package/output/js/ff/core/Float.mjs +50 -0
  114. package/output/js/ff/core/HttpClient.mjs +56 -56
  115. package/output/js/ff/core/Json.mjs +147 -147
  116. package/output/js/ff/core/List.mjs +50 -50
  117. package/output/js/ff/core/Lock.mjs +97 -97
  118. package/output/js/ff/core/NodeSystem.mjs +87 -87
  119. package/output/js/ff/core/Ordering.mjs +8 -8
  120. package/output/js/ff/core/Path.mjs +231 -231
  121. package/output/js/ff/core/Random.mjs +56 -56
  122. package/output/js/ff/core/Task.mjs +39 -39
  123. package/output/js/ff/core/Try.mjs +98 -4
  124. package/package.json +1 -1
  125. package/postgresql/Pg.ff +1 -1
  126. package/rpc/.firefly/package.ff +1 -1
  127. package/rpc/Rpc.ff +70 -70
  128. package/s3/.firefly/package.ff +1 -1
  129. package/s3/S3.ff +94 -94
  130. package/unsafejs/UnsafeJs.ff +19 -19
  131. package/vscode/LICENSE.txt +21 -21
  132. package/vscode/Prepublish.ff +15 -15
  133. package/vscode/README.md +16 -16
  134. package/vscode/client/package.json +22 -22
  135. package/vscode/client/src/extension.ts +104 -104
  136. package/vscode/icons/firefly-icon.svg +10 -10
  137. package/vscode/language-configuration.json +61 -61
  138. package/vscode/package-lock.json +3623 -3623
  139. package/vscode/package.json +1 -1
  140. package/vscode/snippets.json +241 -241
  141. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  142. package/webserver/.firefly/include/package-lock.json +22 -22
  143. package/webserver/.firefly/include/package.json +5 -5
  144. package/webserver/.firefly/package.ff +2 -2
  145. package/webserver/WebServer.ff +685 -685
  146. package/websocket/.firefly/package.ff +1 -1
  147. package/websocket/WebSocket.ff +131 -131
  148. package/fireflysite/GuideAll.ff +0 -21
  149. package/fireflysite/GuideBaseTypes.ff +0 -168
  150. package/fireflysite/GuideControlFlow.ff +0 -212
  151. package/fireflysite/assets/markdown/Example.md +0 -78
  152. /package/fireflysite/assets/{NotoSansMono-Regular.ttf → font/NotoSansMono-Regular.ttf} +0 -0
  153. /package/fireflysite/assets/{NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf → font/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf} +0 -0
  154. /package/fireflysite/assets/{autocomplete-small.png → image/autocomplete-small.png} +0 -0
  155. /package/fireflysite/assets/{autocomplete.png → image/autocomplete.png} +0 -0
  156. /package/fireflysite/assets/{edit-time-error.png → image/edit-time-error.png} +0 -0
  157. /package/fireflysite/assets/{firefly-logo-notext.png → image/firefly-logo-notext.png} +0 -0
  158. /package/fireflysite/assets/{firefly-logo-yellow.png → image/firefly-logo-yellow.png} +0 -0
package/lux/Lux.ff CHANGED
@@ -1,487 +1,487 @@
1
- import Lux
2
- import LuxEvent
3
- import Css
4
-
5
- capability Lux(
6
- document: LuxDocument
7
- jsSystem: JsSystem
8
- mutable cssClasses: StringMap[CssClass]
9
- mutable renderLock: Lock
10
- mutable task: Task
11
- mutable renderQueue: Array[RenderQueueItem]
12
- mutable element: LuxElement
13
- mutable depth: Int
14
- mutable keys: Option[StringMap[JsValue]]
15
- mutable key: String
16
- mutable attributes: Option[StringMap[String]]
17
- )
18
-
19
- capability RenderQueueItem(
20
- luxCopy: Lux
21
- render: () => Unit
22
- )
23
-
24
- class LuxElement(element: JsValue, mutable child: Int, mutable keepChildren: Bool)
25
-
26
- class LuxDocument(document: JsValue)
27
-
28
- extend self: LuxElement {
29
- childAt(index: Int): JsValue {
30
- self.element.get("childNodes").get(index)
31
- }
32
- insertBefore(newNode: JsValue, referenceNode: JsValue): JsValue {
33
- self.element.call2("insertBefore", newNode, referenceNode)
34
- }
35
- removeAt(index: Int): Bool {
36
- let node = self.childAt(index)
37
- let remove = !node.isNullOrUndefined()
38
- if(remove) {
39
- self.element.call1("removeChild", node)
40
- }
41
- remove
42
- }
43
- }
44
-
45
- extend self: LuxDocument {
46
- createTextNode(value: String): JsValue {
47
- self.document.call1("createTextNode", value)
48
- }
49
-
50
- createElement(tagName: String): JsValue {
51
- self.document.call1("createElement", tagName)
52
- }
53
-
54
- createFragment(): JsValue {
55
- self.document.call0("createDocumentFragment")
56
- }
57
- }
58
-
59
- extend self: Lux {
60
-
61
- copyFrom(lux: Lux) {
62
- self.renderLock = lux.renderLock
63
- self.renderQueue = lux.renderQueue
64
- self.element = lux.element.LuxElement()
65
- self.depth = lux.depth
66
- self.keys = lux.keys
67
- self.key = lux.key
68
- self.attributes = lux.attributes
69
- }
70
-
71
- text(value: String) {
72
- let oldNode = self.element.childAt(self.element.child)
73
- let oldValue = if(!oldNode.isNullOrUndefined()) {oldNode.get("data")} else {oldNode}
74
- if(oldValue.isNullOrUndefined() || oldValue.grabString() != value) {
75
- let node = self.document.createTextNode(value)
76
- self.element.insertBefore(node, oldNode)
77
- }
78
- self.element.child += 1
79
- }
80
-
81
- add(tagName: String, body: () => Unit = {}) {
82
- let node = patchElement(self, tagName)
83
- if(!node.get("luxHandlers").isNullOrUndefined()) {
84
- node.get("luxHandlers").grabArray().each {pair =>
85
- node.call2("removeEventListener", pair.get("event"), pair.get("handler"))
86
- }
87
- node.set("luxHandlers", self.jsSystem.array([]))
88
- }
89
- let savedAttributes = self.attributes
90
- let savedKeys = self.keys
91
- let savedElement = self.element
92
- self.attributes = None
93
- self.element = LuxElement(node, 0, keepChildren = False)
94
- self.element.element.get("classList").set("value", "")
95
- self.key = ""
96
- self.depth += 1
97
- try {
98
- body()
99
- } finally {
100
- if(!self.element.keepChildren) {
101
- doWhile {removeCurrentChild(self)}
102
- }
103
- patchAttributes(self)
104
- self.depth -= 1
105
- self.attributes = savedAttributes
106
- self.keys = savedKeys
107
- self.element = savedElement
108
- self.element.child += 1
109
- } grab()
110
- }
111
-
112
- div(body: () => Unit = {}) {self.add("div", body)}
113
- span(body: () => Unit = {}) {self.add("span", body)}
114
- label(body: () => Unit = {}) {self.add("label", body)}
115
- button(body: () => Unit = {}) {self.add("button", body)}
116
- form(body: () => Unit = {}) {self.add("form", body)}
117
- input(body: () => Unit = {}) {self.add("input", body)}
118
-
119
- keyed(key: String, body: () => Unit) {
120
- try {
121
- self.key = key
122
- body()
123
- } finally {
124
- self.key = ""
125
- } grab()
126
- }
127
-
128
- keyedList[T](values: List[T], getKey: T => String, body: T => Unit) {
129
- try {
130
- values.each {item =>
131
- self.key = getKey(item)
132
- body(item)
133
- }
134
- } finally {
135
- self.key = ""
136
- } grab()
137
- }
138
-
139
- set(attribute: String, value: String) {
140
- let attributes = self.attributes.else {
141
- let map = StringMap.new()
142
- self.attributes = Some(map)
143
- map
144
- }
145
- attributes.set(attribute, value)
146
- }
147
-
148
- setId(value: String) {self.set("id", value)}
149
-
150
- setValue(value: String) { // TODO: Not an attribute
151
- self.element.element.set("value", value)
152
- }
153
-
154
- css(style: Css) {
155
- let attributes = self.attributes.else {
156
- let map = StringMap.new()
157
- self.attributes = Some(map)
158
- map
159
- }
160
- if(attributes.has("style")) {
161
- attributes.set("style", attributes.grab("style") + ";" + style.property + ":" + style.value)
162
- } else {
163
- attributes.set("style", style.property + ":" + style.value)
164
- }
165
- }
166
-
167
- cssClass(class: CssClass) {
168
- if(!self.cssClasses.has(class.name())) {
169
- self.cssClasses.set(class.name(), class)
170
- let styleSheet = self.document.createElement("style")
171
- styleSheet.set("textContent", class.show())
172
- self.document.document.get("head").call1("appendChild", styleSheet)
173
- }
174
- self.element.element.get("classList").call1("add", class.name())
175
- self.set("class", self.element.element.get("className").grabString())
176
- }
177
-
178
- on(event: String, handler: LuxEvent => Unit) {
179
- let jsHandler = unsafeAsyncFunction1ToJs(self) {jsEvent =>
180
- self.renderLock.do(reentrant = False) {
181
- handler(unsafeJsToValue(jsEvent))
182
- processRenderQueue(self)
183
- }
184
- }
185
- self.element.element.call2("addEventListener", event, jsHandler)
186
- if(self.element.element.get("luxHandlers").isNullOrUndefined()) {
187
- self.element.element.set("luxHandlers", self.jsSystem.array([]))
188
- }
189
- self.element.element.get("luxHandlers").call1("push"
190
- self.jsSystem.object().with("event", event).with("handler", jsHandler)
191
- )
192
- }
193
-
194
- onClick(handler: LuxEvent => Unit) {self.on("click", handler)}
195
- onInput(handler: LuxEvent => Unit) {self.on("input", handler)}
196
-
197
- useState[T: HasAnyTag](initialValue: T, body: (T, T => Unit) => Unit) {
198
- self.depth += 1
199
- let value = getStateOnElement(self.element.element, self.depth, initialValue)
200
- mutable i = 0
201
- while {i < self.renderQueue.size()} {
202
- let item = self.renderQueue.grab(i)
203
- if(item.luxCopy.element.element.equals(self.element.element) && item.luxCopy.depth == self.depth) {
204
- self.renderQueue.delete(i, 1)
205
- } else {
206
- i += 1
207
- }
208
- }
209
- let luxCopy = self.Lux(element = self.element.LuxElement())
210
- function setState(newValue: T): Unit {
211
- setStateOnElement(luxCopy.element.element, luxCopy.depth, newValue)
212
- self.renderQueue.push(RenderQueueItem(
213
- luxCopy = luxCopy
214
- render = {
215
- let child = luxCopy.element.child
216
- let element = luxCopy.element.childAt(child)
217
- body(newValue, {setState(_)})
218
- if(!element.equals(luxCopy.element.childAt(child))) {
219
- removeCurrentChild(luxCopy.Lux(
220
- element = luxCopy.element.LuxElement(child = luxCopy.element.child + 1)
221
- ))
222
- }
223
- }
224
- ))
225
- }
226
- try {
227
- body(value, {setState(_)})
228
- } finally {
229
- self.depth -= 1
230
- } grab()
231
- }
232
-
233
- useCallback1[A1: Equal: HasAnyTag](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
234
- self.depth += 1
235
- setStateOnElement(self.element.element, self.depth, callback)
236
- let element = self.element.element
237
- let depth = self.depth
238
- try {
239
- body {a1 =>
240
- let latestCallback = getStateOnElement(element, depth, callback)
241
- latestCallback(a1)
242
- }
243
- } finally {
244
- self.depth -= 1
245
- } grab()
246
- }
247
-
248
- useLazy1[A1: Equal: HasAnyTag](a1: A1, body: A1 => Unit = {_ => }) {
249
- self.depth += 1
250
- try {
251
- let old = getStateOnElement(self.element.element, self.depth, None)
252
- old.{
253
- | Some(o1) {a1 == o1} =>
254
- self.element.keepChildren = True
255
- | _ =>
256
- setStateOnElement(self.element.element, self.depth, Some(a1))
257
- body(a1)
258
- }
259
- } finally {
260
- self.depth -= 1
261
- } grab()
262
- }
263
-
264
- useMemo1[A1: Equal: HasAnyTag, T: HasAnyTag](a1: A1, compute: A1 => T, body: T => Unit) {
265
- self.depth += 1
266
- try {
267
- let old = getStateOnElement(self.element.element, self.depth, Pair(a1, None))
268
- let computed = old.{
269
- | Pair(o1, _) {a1 != o1} =>
270
- let v = compute(a1)
271
- setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
272
- v
273
- | Pair(_, Some(v)) =>
274
- v
275
- | Pair(_, None) =>
276
- let v = compute(a1)
277
- setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
278
- v
279
- }
280
- body(computed)
281
- } finally {
282
- self.depth -= 1
283
- } grab()
284
- }
285
-
286
- useSuspense(suspense: () => Unit, body: Lux => Unit) {
287
- let oldSubtask = getTaskOnElement(self.element.element)
288
- oldSubtask.each {task =>
289
- forceAsyncAbort(self, task)
290
- }
291
- let fragment = self.document.createFragment()
292
- let luxCopy = self.Lux(element = self.element.LuxElement())
293
- let subtask = self.task.spawn {task =>
294
- let lux = luxCopy.Lux(
295
- task = task
296
- renderLock = task.lock()
297
- renderQueue = Array.new()
298
- element = luxCopy.element.LuxElement(element = fragment)
299
- )
300
- try {
301
- body(lux)
302
- task.throwIfAborted()
303
- lux.copyFrom(luxCopy)
304
- lux.renderLock.do(reentrant = False) {
305
- abortTasksOnElement(lux, lux.element.childAt(lux.element.child), onlyChildren = True)
306
- lux.element.removeAt(lux.element.child)
307
- lux.element.insertBefore(fragment, lux.jsSystem.null())
308
- }
309
- } catchAny {error =>
310
- if(error.name() != "AbortError") {
311
- error.rethrow()
312
- }
313
- } grab()
314
- }
315
- suspense()
316
- setTaskOnElement(self.element.childAt(self.element.child - 1), Some(subtask))
317
- }
318
-
319
- }
320
-
321
- processRenderQueue(self: Lux) {
322
- self.renderQueue.sortBy {-_.luxCopy.depth}
323
- let newLux = self.Lux(element = self.element.LuxElement())
324
- try {
325
- while {!self.renderQueue.isEmpty()} {
326
- let item = self.renderQueue.pop().grab()
327
- self.copyFrom(item.luxCopy)
328
- item.render()
329
- }
330
- } finally {
331
- self.copyFrom(newLux)
332
- } grab()
333
- }
334
-
335
- removeCurrentChild(self: Lux): Bool {
336
- let child = self.element.childAt(self.element.child)
337
- if(!child.isNullOrUndefined() && !child.get("children").isNullOrUndefined()) {
338
- abortTasksOnElement(self, child, False)
339
- }
340
- self.element.removeAt(self.element.child)
341
- }
342
-
343
- getStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, fallback: T): T {
344
- let value = element.get("lux" + depth)
345
- if(value.isNullOrUndefined()) {fallback} else {
346
- unsafeJsToValue(value)
347
- }
348
- }
349
-
350
- setStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, value: T): Unit {
351
- element.set("lux" + depth, unsafeJsFromValue(value))
352
- }
353
-
354
-
355
- getTaskOnElement(element: JsValue): Option[Task] {
356
- let value = element.get("luxTask")
357
- if(value.isNullOrUndefined()) {None} else {
358
- unsafeJsToValue(value)
359
- }
360
- }
361
-
362
- setTaskOnElement(element: JsValue, task: Option[Task]): Unit {
363
- element.set("luxTask", unsafeJsFromValue(task))
364
- }
365
-
366
- abortTasksOnElement(lux: Lux, element: JsValue, onlyChildren: Bool): Unit {
367
- if(!onlyChildren) {
368
- getTaskOnElement(element).each {task => forceAsyncAbort(lux, task)}
369
- }
370
- element.get("children").each {child =>
371
- abortTasksOnElement(lux, child, False)
372
- }
373
- }
374
-
375
- unsafeJsToValue[T](jsValue: JsValue): T
376
- target js sync """return jsValue_"""
377
-
378
- unsafeJsFromValue[T](value: T): JsValue
379
- target js sync """return value_"""
380
-
381
- unsafeAsyncFunction1ToJs[R](self: Lux, body: JsValue => R): JsValue
382
- target js async "return async a1 => await body_(a1, $task)"
383
-
384
- forceAsyncAbort(self: Lux, task: Task): Unit {
385
- task.abort()
386
- }
387
-
388
- patchElement(self: Lux, tagName: String): JsValue {
389
- let newKey = if(self.key != "") {tagName.upper() + ">" + self.key} else {""}
390
- let oldNode = self.element.childAt(self.element.child)
391
- let oldKey = if(!oldNode.isNullOrUndefined()) {oldNode.get("luxKey")} else {oldNode}
392
- let match = if(newKey != "") {
393
- !oldKey.isNullOrUndefined() &&
394
- oldKey.grabString() == newKey
395
- } else {
396
- oldKey.isNullOrUndefined() &&
397
- !oldNode.isNullOrUndefined() &&
398
- !oldNode.get("tagName").isNullOrUndefined() &&
399
- oldNode.get("tagName").grabString() == tagName.upper()
400
- }
401
- if(match) {
402
- oldNode
403
- } else {
404
- let newNode = if(newKey == "") {self.document.createElement(tagName)} else {
405
- let keys = self.keys.else {
406
- let map = StringMap.new()
407
- mutable i = self.element.child
408
- mutable c = self.element.childAt(i)
409
- while {!c.isNullOrUndefined()} {
410
- let k = c.get("luxKey")
411
- if(!k.isNullOrUndefined()) {map.set(k.grabString(), c)}
412
- c = self.element.childAt(i)
413
- i += 1
414
- }
415
- self.keys = Some(map)
416
- map
417
- }
418
- if(keys.has(newKey)) {
419
- let foundNode = keys.grab(newKey)
420
- keys.remove(newKey)
421
- foundNode
422
- } else {
423
- let createdNode = self.document.createElement(tagName)
424
- createdNode.set("luxKey", newKey)
425
- createdNode
426
- }
427
- }
428
- self.element.insertBefore(newNode, oldNode)
429
- newNode
430
- }
431
- }
432
-
433
- patchAttributes(self: Lux) {
434
- let attributes = self.element.element.get("attributes")
435
- self.attributes.{
436
- | None =>
437
- mutable i = attributes.get("length").grabInt() - 1
438
- while {i >= 0} {
439
- let attribute = attributes.get(i)
440
- self.element.element.call1("removeAttribute", attribute.get("name"))
441
- i -= 1
442
- }
443
- | Some(map) =>
444
- mutable i = attributes.get("length").grabInt() - 1
445
- while {i >= 0} {
446
- let attribute = attributes.get(i)
447
- if(!map.has(attribute.get("name").grabString())) {
448
- self.element.element.call1("removeAttribute", attribute.get("name"))
449
- }
450
- i -= 1
451
- }
452
- map.each {name, value =>
453
- let oldValue = self.element.element.call1("getAttribute", name)
454
- if(oldValue.isNullOrUndefined() || !oldValue.equals(value)) {
455
- self.element.element.call2("setAttribute", name, value)
456
- }
457
- }
458
- }
459
- }
460
-
461
- render(browserSystem: BrowserSystem, element: JsValue, body: Lux => Unit) {
462
- mutable document = element
463
- while {!document.get("parentNode").isNullOrUndefined()} {
464
- document = document.get("parentNode")
465
- }
466
- let lux = Lux(
467
- jsSystem = browserSystem.js()
468
- renderLock = browserSystem.mainTask().lock()
469
- cssClasses = StringMap.new()
470
- task = browserSystem.mainTask()
471
- depth = 0
472
- document = LuxDocument(document)
473
- element = LuxElement(element, 0, keepChildren = False)
474
- keys = None
475
- key = ""
476
- attributes = None
477
- renderQueue = Array.new()
478
- )
479
- lux.renderLock.do(reentrant = False) {
480
- body(lux)
481
- }
482
- }
483
-
484
- renderById(browserSystem: BrowserSystem, id: String, body: Lux => Unit) {
485
- let element = browserSystem.js().global().get("document").call1("getElementById", id)
486
- render(browserSystem, element, body)
487
- }
1
+ import Lux
2
+ import LuxEvent
3
+ import Css
4
+
5
+ capability Lux(
6
+ document: LuxDocument
7
+ jsSystem: JsSystem
8
+ mutable cssClasses: StringMap[CssClass]
9
+ mutable renderLock: Lock
10
+ mutable task: Task
11
+ mutable renderQueue: Array[RenderQueueItem]
12
+ mutable element: LuxElement
13
+ mutable depth: Int
14
+ mutable keys: Option[StringMap[JsValue]]
15
+ mutable key: String
16
+ mutable attributes: Option[StringMap[String]]
17
+ )
18
+
19
+ capability RenderQueueItem(
20
+ luxCopy: Lux
21
+ render: () => Unit
22
+ )
23
+
24
+ class LuxElement(element: JsValue, mutable child: Int, mutable keepChildren: Bool)
25
+
26
+ class LuxDocument(document: JsValue)
27
+
28
+ extend self: LuxElement {
29
+ childAt(index: Int): JsValue {
30
+ self.element.get("childNodes").get(index)
31
+ }
32
+ insertBefore(newNode: JsValue, referenceNode: JsValue): JsValue {
33
+ self.element.call2("insertBefore", newNode, referenceNode)
34
+ }
35
+ removeAt(index: Int): Bool {
36
+ let node = self.childAt(index)
37
+ let remove = !node.isNullOrUndefined()
38
+ if(remove) {
39
+ self.element.call1("removeChild", node)
40
+ }
41
+ remove
42
+ }
43
+ }
44
+
45
+ extend self: LuxDocument {
46
+ createTextNode(value: String): JsValue {
47
+ self.document.call1("createTextNode", value)
48
+ }
49
+
50
+ createElement(tagName: String): JsValue {
51
+ self.document.call1("createElement", tagName)
52
+ }
53
+
54
+ createFragment(): JsValue {
55
+ self.document.call0("createDocumentFragment")
56
+ }
57
+ }
58
+
59
+ extend self: Lux {
60
+
61
+ copyFrom(lux: Lux) {
62
+ self.renderLock = lux.renderLock
63
+ self.renderQueue = lux.renderQueue
64
+ self.element = lux.element.LuxElement()
65
+ self.depth = lux.depth
66
+ self.keys = lux.keys
67
+ self.key = lux.key
68
+ self.attributes = lux.attributes
69
+ }
70
+
71
+ text(value: String) {
72
+ let oldNode = self.element.childAt(self.element.child)
73
+ let oldValue = if(!oldNode.isNullOrUndefined()) {oldNode.get("data")} else {oldNode}
74
+ if(oldValue.isNullOrUndefined() || oldValue.grabString() != value) {
75
+ let node = self.document.createTextNode(value)
76
+ self.element.insertBefore(node, oldNode)
77
+ }
78
+ self.element.child += 1
79
+ }
80
+
81
+ add(tagName: String, body: () => Unit = {}) {
82
+ let node = patchElement(self, tagName)
83
+ if(!node.get("luxHandlers").isNullOrUndefined()) {
84
+ node.get("luxHandlers").grabArray().each {pair =>
85
+ node.call2("removeEventListener", pair.get("event"), pair.get("handler"))
86
+ }
87
+ node.set("luxHandlers", self.jsSystem.array([]))
88
+ }
89
+ let savedAttributes = self.attributes
90
+ let savedKeys = self.keys
91
+ let savedElement = self.element
92
+ self.attributes = None
93
+ self.element = LuxElement(node, 0, keepChildren = False)
94
+ self.element.element.get("classList").set("value", "")
95
+ self.key = ""
96
+ self.depth += 1
97
+ try {
98
+ body()
99
+ } finally {
100
+ if(!self.element.keepChildren) {
101
+ doWhile {removeCurrentChild(self)}
102
+ }
103
+ patchAttributes(self)
104
+ self.depth -= 1
105
+ self.attributes = savedAttributes
106
+ self.keys = savedKeys
107
+ self.element = savedElement
108
+ self.element.child += 1
109
+ }
110
+ }
111
+
112
+ div(body: () => Unit = {}) {self.add("div", body)}
113
+ span(body: () => Unit = {}) {self.add("span", body)}
114
+ label(body: () => Unit = {}) {self.add("label", body)}
115
+ button(body: () => Unit = {}) {self.add("button", body)}
116
+ form(body: () => Unit = {}) {self.add("form", body)}
117
+ input(body: () => Unit = {}) {self.add("input", body)}
118
+
119
+ keyed(key: String, body: () => Unit) {
120
+ try {
121
+ self.key = key
122
+ body()
123
+ } finally {
124
+ self.key = ""
125
+ }
126
+ }
127
+
128
+ keyedList[T](values: List[T], getKey: T => String, body: T => Unit) {
129
+ try {
130
+ values.each {item =>
131
+ self.key = getKey(item)
132
+ body(item)
133
+ }
134
+ } finally {
135
+ self.key = ""
136
+ }
137
+ }
138
+
139
+ set(attribute: String, value: String) {
140
+ let attributes = self.attributes.else {
141
+ let map = StringMap.new()
142
+ self.attributes = Some(map)
143
+ map
144
+ }
145
+ attributes.set(attribute, value)
146
+ }
147
+
148
+ setId(value: String) {self.set("id", value)}
149
+
150
+ setValue(value: String) { // TODO: Not an attribute
151
+ self.element.element.set("value", value)
152
+ }
153
+
154
+ css(style: Css) {
155
+ let attributes = self.attributes.else {
156
+ let map = StringMap.new()
157
+ self.attributes = Some(map)
158
+ map
159
+ }
160
+ if(attributes.has("style")) {
161
+ attributes.set("style", attributes.grab("style") + ";" + style.property + ":" + style.value)
162
+ } else {
163
+ attributes.set("style", style.property + ":" + style.value)
164
+ }
165
+ }
166
+
167
+ cssClass(class: CssClass) {
168
+ if(!self.cssClasses.has(class.name())) {
169
+ self.cssClasses.set(class.name(), class)
170
+ let styleSheet = self.document.createElement("style")
171
+ styleSheet.set("textContent", class.show())
172
+ self.document.document.get("head").call1("appendChild", styleSheet)
173
+ }
174
+ self.element.element.get("classList").call1("add", class.name())
175
+ self.set("class", self.element.element.get("className").grabString())
176
+ }
177
+
178
+ on(event: String, handler: LuxEvent => Unit) {
179
+ let jsHandler = unsafeAsyncFunction1ToJs(self) {jsEvent =>
180
+ self.renderLock.do(reentrant = False) {
181
+ handler(unsafeJsToValue(jsEvent))
182
+ processRenderQueue(self)
183
+ }
184
+ }
185
+ self.element.element.call2("addEventListener", event, jsHandler)
186
+ if(self.element.element.get("luxHandlers").isNullOrUndefined()) {
187
+ self.element.element.set("luxHandlers", self.jsSystem.array([]))
188
+ }
189
+ self.element.element.get("luxHandlers").call1("push"
190
+ self.jsSystem.object().with("event", event).with("handler", jsHandler)
191
+ )
192
+ }
193
+
194
+ onClick(handler: LuxEvent => Unit) {self.on("click", handler)}
195
+ onInput(handler: LuxEvent => Unit) {self.on("input", handler)}
196
+
197
+ useState[T: HasAnyTag](initialValue: T, body: (T, T => Unit) => Unit) {
198
+ self.depth += 1
199
+ let value = getStateOnElement(self.element.element, self.depth, initialValue)
200
+ mutable i = 0
201
+ while {i < self.renderQueue.size()} {
202
+ let item = self.renderQueue.grab(i)
203
+ if(item.luxCopy.element.element.equals(self.element.element) && item.luxCopy.depth == self.depth) {
204
+ self.renderQueue.delete(i, 1)
205
+ } else {
206
+ i += 1
207
+ }
208
+ }
209
+ let luxCopy = self.Lux(element = self.element.LuxElement())
210
+ function setState(newValue: T): Unit {
211
+ setStateOnElement(luxCopy.element.element, luxCopy.depth, newValue)
212
+ self.renderQueue.push(RenderQueueItem(
213
+ luxCopy = luxCopy
214
+ render = {
215
+ let child = luxCopy.element.child
216
+ let element = luxCopy.element.childAt(child)
217
+ body(newValue, {setState(_)})
218
+ if(!element.equals(luxCopy.element.childAt(child))) {
219
+ removeCurrentChild(luxCopy.Lux(
220
+ element = luxCopy.element.LuxElement(child = luxCopy.element.child + 1)
221
+ ))
222
+ }
223
+ }
224
+ ))
225
+ }
226
+ try {
227
+ body(value, {setState(_)})
228
+ } finally {
229
+ self.depth -= 1
230
+ }
231
+ }
232
+
233
+ useCallback1[A1: Equal: HasAnyTag](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
234
+ self.depth += 1
235
+ setStateOnElement(self.element.element, self.depth, callback)
236
+ let element = self.element.element
237
+ let depth = self.depth
238
+ try {
239
+ body {a1 =>
240
+ let latestCallback = getStateOnElement(element, depth, callback)
241
+ latestCallback(a1)
242
+ }
243
+ } finally {
244
+ self.depth -= 1
245
+ }
246
+ }
247
+
248
+ useLazy1[A1: Equal: HasAnyTag](a1: A1, body: A1 => Unit = {_ => }) {
249
+ self.depth += 1
250
+ try {
251
+ let old = getStateOnElement(self.element.element, self.depth, None)
252
+ old.{
253
+ | Some(o1) {a1 == o1} =>
254
+ self.element.keepChildren = True
255
+ | _ =>
256
+ setStateOnElement(self.element.element, self.depth, Some(a1))
257
+ body(a1)
258
+ }
259
+ } finally {
260
+ self.depth -= 1
261
+ }
262
+ }
263
+
264
+ useMemo1[A1: Equal: HasAnyTag, T: HasAnyTag](a1: A1, compute: A1 => T, body: T => Unit) {
265
+ self.depth += 1
266
+ try {
267
+ let old = getStateOnElement(self.element.element, self.depth, Pair(a1, None))
268
+ let computed = old.{
269
+ | Pair(o1, _) {a1 != o1} =>
270
+ let v = compute(a1)
271
+ setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
272
+ v
273
+ | Pair(_, Some(v)) =>
274
+ v
275
+ | Pair(_, None) =>
276
+ let v = compute(a1)
277
+ setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
278
+ v
279
+ }
280
+ body(computed)
281
+ } finally {
282
+ self.depth -= 1
283
+ }
284
+ }
285
+
286
+ useSuspense(suspense: () => Unit, body: Lux => Unit) {
287
+ let oldSubtask = getTaskOnElement(self.element.element)
288
+ oldSubtask.each {task =>
289
+ forceAsyncAbort(self, task)
290
+ }
291
+ let fragment = self.document.createFragment()
292
+ let luxCopy = self.Lux(element = self.element.LuxElement())
293
+ let subtask = self.task.spawn {task =>
294
+ let lux = luxCopy.Lux(
295
+ task = task
296
+ renderLock = task.lock()
297
+ renderQueue = Array.new()
298
+ element = luxCopy.element.LuxElement(element = fragment)
299
+ )
300
+ try {
301
+ body(lux)
302
+ task.throwIfAborted()
303
+ lux.copyFrom(luxCopy)
304
+ lux.renderLock.do(reentrant = False) {
305
+ abortTasksOnElement(lux, lux.element.childAt(lux.element.child), onlyChildren = True)
306
+ lux.element.removeAt(lux.element.child)
307
+ lux.element.insertBefore(fragment, lux.jsSystem.null())
308
+ }
309
+ } catchAny {error =>
310
+ if(error.name() != "AbortError") {
311
+ error.rethrow()
312
+ }
313
+ }
314
+ }
315
+ suspense()
316
+ setTaskOnElement(self.element.childAt(self.element.child - 1), Some(subtask))
317
+ }
318
+
319
+ }
320
+
321
+ processRenderQueue(self: Lux) {
322
+ self.renderQueue.sortBy {-_.luxCopy.depth}
323
+ let newLux = self.Lux(element = self.element.LuxElement())
324
+ try {
325
+ while {!self.renderQueue.isEmpty()} {
326
+ let item = self.renderQueue.pop().grab()
327
+ self.copyFrom(item.luxCopy)
328
+ item.render()
329
+ }
330
+ } finally {
331
+ self.copyFrom(newLux)
332
+ }
333
+ }
334
+
335
+ removeCurrentChild(self: Lux): Bool {
336
+ let child = self.element.childAt(self.element.child)
337
+ if(!child.isNullOrUndefined() && !child.get("children").isNullOrUndefined()) {
338
+ abortTasksOnElement(self, child, False)
339
+ }
340
+ self.element.removeAt(self.element.child)
341
+ }
342
+
343
+ getStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, fallback: T): T {
344
+ let value = element.get("lux" + depth)
345
+ if(value.isNullOrUndefined()) {fallback} else {
346
+ unsafeJsToValue(value)
347
+ }
348
+ }
349
+
350
+ setStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, value: T): Unit {
351
+ element.set("lux" + depth, unsafeJsFromValue(value))
352
+ }
353
+
354
+
355
+ getTaskOnElement(element: JsValue): Option[Task] {
356
+ let value = element.get("luxTask")
357
+ if(value.isNullOrUndefined()) {None} else {
358
+ unsafeJsToValue(value)
359
+ }
360
+ }
361
+
362
+ setTaskOnElement(element: JsValue, task: Option[Task]): Unit {
363
+ element.set("luxTask", unsafeJsFromValue(task))
364
+ }
365
+
366
+ abortTasksOnElement(lux: Lux, element: JsValue, onlyChildren: Bool): Unit {
367
+ if(!onlyChildren) {
368
+ getTaskOnElement(element).each {task => forceAsyncAbort(lux, task)}
369
+ }
370
+ element.get("children").each {child =>
371
+ abortTasksOnElement(lux, child, False)
372
+ }
373
+ }
374
+
375
+ unsafeJsToValue[T](jsValue: JsValue): T
376
+ target js sync """return jsValue_"""
377
+
378
+ unsafeJsFromValue[T](value: T): JsValue
379
+ target js sync """return value_"""
380
+
381
+ unsafeAsyncFunction1ToJs[R](self: Lux, body: JsValue => R): JsValue
382
+ target js async "return async a1 => await body_(a1, $task)"
383
+
384
+ forceAsyncAbort(self: Lux, task: Task): Unit {
385
+ task.abort()
386
+ }
387
+
388
+ patchElement(self: Lux, tagName: String): JsValue {
389
+ let newKey = if(self.key != "") {tagName.upper() + ">" + self.key} else {""}
390
+ let oldNode = self.element.childAt(self.element.child)
391
+ let oldKey = if(!oldNode.isNullOrUndefined()) {oldNode.get("luxKey")} else {oldNode}
392
+ let match = if(newKey != "") {
393
+ !oldKey.isNullOrUndefined() &&
394
+ oldKey.grabString() == newKey
395
+ } else {
396
+ oldKey.isNullOrUndefined() &&
397
+ !oldNode.isNullOrUndefined() &&
398
+ !oldNode.get("tagName").isNullOrUndefined() &&
399
+ oldNode.get("tagName").grabString() == tagName.upper()
400
+ }
401
+ if(match) {
402
+ oldNode
403
+ } else {
404
+ let newNode = if(newKey == "") {self.document.createElement(tagName)} else {
405
+ let keys = self.keys.else {
406
+ let map = StringMap.new()
407
+ mutable i = self.element.child
408
+ mutable c = self.element.childAt(i)
409
+ while {!c.isNullOrUndefined()} {
410
+ let k = c.get("luxKey")
411
+ if(!k.isNullOrUndefined()) {map.set(k.grabString(), c)}
412
+ c = self.element.childAt(i)
413
+ i += 1
414
+ }
415
+ self.keys = Some(map)
416
+ map
417
+ }
418
+ if(keys.has(newKey)) {
419
+ let foundNode = keys.grab(newKey)
420
+ keys.remove(newKey)
421
+ foundNode
422
+ } else {
423
+ let createdNode = self.document.createElement(tagName)
424
+ createdNode.set("luxKey", newKey)
425
+ createdNode
426
+ }
427
+ }
428
+ self.element.insertBefore(newNode, oldNode)
429
+ newNode
430
+ }
431
+ }
432
+
433
+ patchAttributes(self: Lux) {
434
+ let attributes = self.element.element.get("attributes")
435
+ self.attributes.{
436
+ | None =>
437
+ mutable i = attributes.get("length").grabInt() - 1
438
+ while {i >= 0} {
439
+ let attribute = attributes.get(i)
440
+ self.element.element.call1("removeAttribute", attribute.get("name"))
441
+ i -= 1
442
+ }
443
+ | Some(map) =>
444
+ mutable i = attributes.get("length").grabInt() - 1
445
+ while {i >= 0} {
446
+ let attribute = attributes.get(i)
447
+ if(!map.has(attribute.get("name").grabString())) {
448
+ self.element.element.call1("removeAttribute", attribute.get("name"))
449
+ }
450
+ i -= 1
451
+ }
452
+ map.each {name, value =>
453
+ let oldValue = self.element.element.call1("getAttribute", name)
454
+ if(oldValue.isNullOrUndefined() || !oldValue.equals(value)) {
455
+ self.element.element.call2("setAttribute", name, value)
456
+ }
457
+ }
458
+ }
459
+ }
460
+
461
+ render(browserSystem: BrowserSystem, element: JsValue, body: Lux => Unit) {
462
+ mutable document = element
463
+ while {!document.get("parentNode").isNullOrUndefined()} {
464
+ document = document.get("parentNode")
465
+ }
466
+ let lux = Lux(
467
+ jsSystem = browserSystem.js()
468
+ renderLock = browserSystem.mainTask().lock()
469
+ cssClasses = StringMap.new()
470
+ task = browserSystem.mainTask()
471
+ depth = 0
472
+ document = LuxDocument(document)
473
+ element = LuxElement(element, 0, keepChildren = False)
474
+ keys = None
475
+ key = ""
476
+ attributes = None
477
+ renderQueue = Array.new()
478
+ )
479
+ lux.renderLock.do(reentrant = False) {
480
+ body(lux)
481
+ }
482
+ }
483
+
484
+ renderById(browserSystem: BrowserSystem, id: String, body: Lux => Unit) {
485
+ let element = browserSystem.js().global().get("document").call1("getElementById", id)
486
+ render(browserSystem, element, body)
487
+ }