firefly-compiler 0.4.79 → 0.4.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 (164) 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 +149 -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 +442 -411
  67. package/fireflysite/Main.ff +151 -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 +18 -0
  72. package/fireflysite/ReferenceIntroduction.ff +11 -0
  73. package/fireflysite/Styles.ff +567 -495
  74. package/fireflysite/Test.ff +46 -0
  75. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -0
  76. package/fireflysite/assets/markdown/reference/EmittedJavascript.md +66 -0
  77. package/fireflysite/assets/markdown/reference/Exceptions.md +101 -0
  78. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +338 -0
  79. package/fireflysite/assets/markdown/reference/JavascriptInterop.md +134 -0
  80. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -0
  81. package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -0
  82. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -0
  83. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -0
  84. package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -0
  85. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -0
  86. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -0
  87. package/fireflysite/assets/markdown/{ControlFlow.md → scratch/ControlFlow.md} +136 -136
  88. package/fireflysite/assets/markdown/scratch/Toc.md +41 -0
  89. package/lsp/.firefly/package.ff +1 -1
  90. package/lsp/CompletionHandler.ff +828 -828
  91. package/lsp/Handler.ff +714 -714
  92. package/lsp/HoverHandler.ff +79 -79
  93. package/lsp/LanguageServer.ff +272 -272
  94. package/lsp/SignatureHelpHandler.ff +55 -55
  95. package/lsp/SymbolHandler.ff +181 -181
  96. package/lsp/TestReferences.ff +17 -17
  97. package/lsp/TestReferencesCase.ff +7 -7
  98. package/lsp/stderr.txt +1 -1
  99. package/lsp/stdout.txt +34 -34
  100. package/lux/.firefly/package.ff +1 -1
  101. package/lux/Css.ff +648 -648
  102. package/lux/CssTest.ff +48 -48
  103. package/lux/Lux.ff +593 -487
  104. package/lux/LuxEvent.ff +116 -116
  105. package/lux/Main.ff +123 -123
  106. package/lux/Main2.ff +143 -143
  107. package/lux/TestDry.ff +27 -0
  108. package/output/js/ff/compiler/Builder.mjs +47 -47
  109. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  110. package/output/js/ff/compiler/Inference.mjs +2 -2
  111. package/output/js/ff/compiler/JsEmitter.mjs +18 -72
  112. package/output/js/ff/compiler/Main.mjs +4 -4
  113. package/output/js/ff/compiler/ModuleCache.mjs +4 -4
  114. package/output/js/ff/core/Array.mjs +59 -59
  115. package/output/js/ff/core/Atomic.mjs +36 -36
  116. package/output/js/ff/core/BrowserSystem.mjs +11 -11
  117. package/output/js/ff/core/BuildSystem.mjs +30 -30
  118. package/output/js/ff/core/Crypto.mjs +40 -40
  119. package/output/js/ff/core/Float.mjs +50 -0
  120. package/output/js/ff/core/HttpClient.mjs +56 -56
  121. package/output/js/ff/core/Json.mjs +147 -147
  122. package/output/js/ff/core/List.mjs +50 -50
  123. package/output/js/ff/core/Lock.mjs +97 -97
  124. package/output/js/ff/core/NodeSystem.mjs +87 -87
  125. package/output/js/ff/core/Ordering.mjs +8 -8
  126. package/output/js/ff/core/Path.mjs +231 -231
  127. package/output/js/ff/core/Random.mjs +56 -56
  128. package/output/js/ff/core/Task.mjs +71 -39
  129. package/output/js/ff/core/Try.mjs +98 -4
  130. package/package.json +1 -1
  131. package/postgresql/Pg.ff +1 -1
  132. package/rpc/.firefly/package.ff +1 -1
  133. package/rpc/Rpc.ff +70 -70
  134. package/s3/.firefly/package.ff +1 -1
  135. package/s3/S3.ff +94 -94
  136. package/unsafejs/UnsafeJs.ff +19 -19
  137. package/vscode/LICENSE.txt +21 -21
  138. package/vscode/Prepublish.ff +15 -15
  139. package/vscode/README.md +16 -16
  140. package/vscode/client/package.json +22 -22
  141. package/vscode/client/src/extension.ts +104 -104
  142. package/vscode/icons/firefly-icon.svg +10 -10
  143. package/vscode/language-configuration.json +61 -61
  144. package/vscode/package-lock.json +3623 -3623
  145. package/vscode/package.json +1 -1
  146. package/vscode/snippets.json +241 -241
  147. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  148. package/webserver/.firefly/include/package-lock.json +22 -22
  149. package/webserver/.firefly/include/package.json +5 -5
  150. package/webserver/.firefly/package.ff +2 -2
  151. package/webserver/WebServer.ff +685 -685
  152. package/websocket/.firefly/package.ff +1 -1
  153. package/websocket/WebSocket.ff +131 -131
  154. package/fireflysite/GuideAll.ff +0 -21
  155. package/fireflysite/GuideBaseTypes.ff +0 -168
  156. package/fireflysite/GuideControlFlow.ff +0 -212
  157. package/fireflysite/assets/markdown/Example.md +0 -78
  158. /package/fireflysite/assets/{NotoSansMono-Regular.ttf → font/NotoSansMono-Regular.ttf} +0 -0
  159. /package/fireflysite/assets/{NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf → font/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf} +0 -0
  160. /package/fireflysite/assets/{autocomplete-small.png → image/autocomplete-small.png} +0 -0
  161. /package/fireflysite/assets/{autocomplete.png → image/autocomplete.png} +0 -0
  162. /package/fireflysite/assets/{edit-time-error.png → image/edit-time-error.png} +0 -0
  163. /package/fireflysite/assets/{firefly-logo-notext.png → image/firefly-logo-notext.png} +0 -0
  164. /package/fireflysite/assets/{firefly-logo-yellow.png → image/firefly-logo-yellow.png} +0 -0
@@ -1,411 +1,442 @@
1
- import Lux from ff:lux
2
- import LuxEvent from ff:lux
3
- import Css from ff:lux
4
- import Tokenizer from ff:compiler
5
- import Token from ff:compiler
6
- import Styles
7
- import DocumentParser
8
-
9
- data Guide(
10
- prefix: String
11
- documents: List[Document]
12
- )
13
-
14
- data Document {
15
- ReadyDocument(
16
- sections: List[Section]
17
- )
18
- UnfetchedDocument(
19
- header: String
20
- )
21
- }
22
-
23
-
24
- data Section(heading: String) {
25
- Section(
26
- blocks: List[Block]
27
- )
28
- SplitSection(
29
- first: Block
30
- second: Block
31
- )
32
- }
33
-
34
- data Block {
35
- Paragraph(inlines: List[Inline])
36
- Bullets(items: List[List[Inline]])
37
- CodeBlock(code: String, firefly: Bool = False)
38
- Image(url: String)
39
- Video(url: String)
40
- LuxDemo(demo: String)
41
- }
42
-
43
- data Inline {
44
- Text(text: String)
45
- Bold(text: String)
46
- Italic(text: String)
47
- Code(code: String, firefly: Bool = False)
48
- Link(text: String, url: String)
49
- Anchor(heading: String, title: Option[String] = None)
50
- }
51
-
52
- extend self: Guide {
53
- heading(): String {
54
- self.documents.first().map {_.heading()}.else {""}
55
- }
56
- title(): String {
57
- if(self.heading() == "Firefly") {self.heading()} else {"Firefly " + self.heading()}
58
- }
59
- }
60
-
61
- extend self: Document {
62
- heading(): String {
63
- self.{
64
- | ReadyDocument(sections) =>
65
- sections.first().map {_.heading}.else {""}
66
- | UnfetchedDocument(header) => header
67
- }
68
- }
69
- title(guide: Guide): String {
70
- if(guide.documents.first().any {_.heading() == self.heading()}) {
71
- guide.title()
72
- } else {
73
- self.heading() + " · " + guide.title()
74
- }
75
- }
76
- path(guide: Guide): String {
77
- if(guide.documents.first().any {_.heading() == self.heading()}) {
78
- guide.prefix
79
- } else {
80
- guide.prefix + kebabCase(self.heading())
81
- }
82
- }
83
- }
84
-
85
- capability Demo(
86
- name: String
87
- render: Lux => Unit
88
- )
89
-
90
- data GuideDocument(
91
- guide: Guide
92
- document: Document
93
- )
94
-
95
- render(lux: Lux, browser: BrowserSystem, prefix: String, kebab: String, guides: List[Guide], demos: List[Demo]) {
96
- let guide = guides.find {_.prefix == prefix}.else {guides.grabFirst()}
97
- let document = guide.documents.find {d =>
98
- kebabCase(d.heading()) == kebab
99
- }.else {guide.documents.grabFirst()}
100
- browser.js().global().get("document").set("title", document.title(guide))
101
- let guideDocuments = guides.flatMap {guide => guide.documents.map {document =>
102
- GuideDocument(guide, document)
103
- }}
104
- let nextDocument = guideDocuments.dropWhile {n =>
105
- n.guide.prefix != guide.prefix || n.document.heading() != document.heading()
106
- }.dropFirst().first()
107
- lux.add("div") {
108
- lux.useState(False): menu, setMenu =>
109
- lux.cssClass(Styles.pageCss)
110
- lux.add("div") {
111
- lux.cssClass(Styles.guideCss)
112
- lux.add("main") {
113
- lux.cssClass(Styles.guideMainCss)
114
- renderDocument(lux, browser.httpClient(), document, demos, nextDocument)
115
- }
116
- lux.add("button") {
117
- lux.set("aria-label", "Toggle the menu")
118
- lux.cssClass(Styles.guideSidebarButtonCss)
119
- if(menu) {lux.cssClass(Styles.guideSidebarButtonOpenCss)}
120
- lux.onClick {event =>
121
- event.preventDefault()
122
- setMenu(True)
123
- }
124
- }
125
- lux.add("div") {
126
- lux.cssClass(Styles.guideSidebarBackdropCss)
127
- if(menu) {lux.cssClass(Styles.guideSidebarBackdropOpenCss)}
128
- lux.onClick {event =>
129
- event.preventDefault()
130
- setMenu(False)
131
- }
132
- }
133
- renderSidebar(lux, guides, guide, document.heading(), menu)
134
- }
135
- }
136
- }
137
-
138
- renderSidebar(lux: Lux, guides: List[Guide], selectedGuide: Guide, selectedHeading: String, menu: Bool) {
139
- lux.add("nav") {
140
- lux.cssClass(Styles.guideSidebarCss)
141
- if(menu) {lux.cssClass(Styles.guideSidebarOpenCss)}
142
- lux.add("a") {
143
- lux.set("href", "/front/")
144
- lux.cssClass(Styles.guideSidebarLogoCss)
145
- lux.add("img") {
146
- lux.set("src", "/assets/firefly-logo-yellow.png")
147
- }
148
- lux.div {
149
- lux.text("Firefly")
150
- }
151
- }
152
- lux.add("form") {
153
- lux.set("role", "search")
154
- lux.add("input") {
155
- lux.set("aria-label", "Search")
156
- lux.cssClass(Styles.searchInputCss)
157
- lux.set("placeholder", "Search...")
158
- }
159
- }
160
- lux.add("ul") {
161
- lux.cssClass(Styles.guideSidebarUl1Css)
162
- guides.dropFirst().each {guide =>
163
- lux.add("li") {
164
- lux.cssClass(Styles.guideSidebarLi1Css)
165
- lux.add("a") {
166
- lux.cssClass(Styles.whiteLinkCss)
167
- let heading = guide.documents.grabFirst().heading()
168
- if(selectedGuide.prefix == guide.prefix && heading == selectedHeading) {
169
- lux.set("aria-current", "page")
170
- }
171
- lux.set("href", guide.prefix)
172
- lux.text(heading)
173
- }
174
- if(guide.documents.size() > 1):
175
- lux.add("ul") {
176
- lux.cssClass(Styles.guideSidebarUl2Css)
177
- guide.documents.dropFirst().each {document =>
178
- lux.add("li") {
179
- lux.cssClass(Styles.guideSidebarLi2Css)
180
- lux.add("a") {
181
- lux.cssClass(Styles.whiteLinkCss)
182
- let heading = document.heading()
183
- if(selectedGuide.prefix == guide.prefix && heading == selectedHeading) {
184
- lux.set("aria-current", "page")
185
- }
186
- lux.set("href", guide.prefix + kebabCase(heading))
187
- lux.text(heading)
188
- }
189
- }
190
- }
191
- }
192
- }
193
- }
194
- }
195
- }
196
- }
197
-
198
- renderDocument(lux: Lux, http: HttpClient, document: Document, demos: List[Demo], nextDocument: Option[GuideDocument]) {
199
- lux.add("article") {
200
- lux.cssClass(Styles.guideDocumentCss)
201
- document.{
202
- | ReadyDocument(sections) =>
203
- renderSections(lux, sections, demos)
204
- | UnfetchedDocument(header) =>
205
- lux.div {
206
- lux.useSuspense {lux.div {lux.text("Loading document...")}}: lux =>
207
- let asset = "/assets/markdown/" + document.heading().replace(" ", "") + ".md"
208
- let markdown = http.get(asset, []) {_.readText()}
209
- let parser = DocumentParser(asset, markdown.split('\n'))
210
- let sections = parser.parseDocument()
211
- renderSections(lux, sections, demos)
212
- }
213
- }
214
- nextDocument.each {next =>
215
- lux.div {
216
- lux.cssClass(Styles.guideNextButtonCss)
217
- lux.add("a") {
218
- lux.cssClass(Styles.guideButtonCss)
219
- lux.set("href", next.document.path(next.guide))
220
- lux.text("Next: " + next.document.heading())
221
- }
222
- }
223
- }
224
- }
225
- }
226
-
227
- renderSections(lux: Lux, sections: List[Section], demos: List[Demo]) {
228
- sections.pairs().each {| Pair(index, section) =>
229
- renderSection(lux, index == 0, section, demos)
230
- }
231
- }
232
-
233
- renderSection(lux: Lux, firstSection: Bool, section: Section, demos: List[Demo]) {
234
- section.{
235
- | Section(heading, blocks) =>
236
- lux.add(if(firstSection) {"h1"} else {"h2"}) {
237
- lux.cssClass(if(firstSection) {Styles.guideH1Css} else {Styles.guideH2Css})
238
- lux.text(heading)
239
- }
240
- blocks.each {renderBlock(lux, _, demos)}
241
- | SplitSection(heading, first, second) =>
242
- lux.add("div") {
243
- lux.cssClass(Styles.guideSplitCss)
244
- lux.add("div") {
245
- lux.add("h2") {
246
- lux.cssClass(Styles.guideSplitHeadingCss)
247
- lux.text(section.heading)
248
- }
249
- renderBlock(lux, first, demos)
250
- }
251
- lux.add("div") {
252
- renderBlock(lux, second, demos)
253
- }
254
- }
255
- }
256
- }
257
-
258
- renderBlock(lux: Lux, block: Block, demos: List[Demo]) {
259
- block.{
260
- | Paragraph(inlines) => renderParagraph(lux, inlines)
261
- | CodeBlock(code, firefly) => renderCodeBlock(lux, code, firefly)
262
- | Bullets(items) => renderBullets(lux, items)
263
- | Image(url) => renderImage(lux, url)
264
- | Video(url) => renderVideo(lux, url)
265
- | LuxDemo(name) => renderLuxDemo(lux, name, demos)
266
- }
267
- }
268
-
269
- renderParagraph(lux: Lux, inlines: List[Inline]) {
270
- lux.add("p") {
271
- renderInlines(lux, inlines)
272
- }
273
- }
274
-
275
- renderCodeBlock(lux: Lux, code: String, firefly: Bool) {
276
- let lines = code.lines().dropWhile {_.all {_ == ' '}}.reverse().dropWhile {_.all {_ == ' '}}.reverse()
277
- let indentation = lines.first().map {_.takeWhile {_ == ' '}.size()}.else {0}
278
- lux.add("pre") {
279
- lux.cssClass(Styles.guideCodeBlockCss)
280
- lux.add("code") {
281
- lux.cssClass(Styles.guideCodeCss)
282
- let unindentedCode = lines.map {_.dropFirst(indentation)}.join("\n")
283
- if(firefly) {
284
- renderHighlightedCode(lux, unindentedCode)
285
- } else {
286
- lux.text(unindentedCode)
287
- }
288
- }
289
- }
290
- }
291
-
292
- renderBullets(lux: Lux, items: List[List[Inline]]) {
293
- lux.add("ul") {
294
- items.each {inlines =>
295
- lux.add("li") {
296
- renderInlines(lux, inlines)
297
- }
298
- }
299
- }
300
- }
301
-
302
- renderImage(lux: Lux, url: String) {
303
- lux.add("img") {
304
- lux.set("src", url)
305
- lux.css(Css.maxWidth("100%"))
306
- lux.css(Css.borderRadius("8px"))
307
- }
308
- }
309
-
310
- renderVideo(lux: Lux, url: String) {
311
- lux.add("video") {
312
- lux.set("src", url)
313
- lux.css(Css.maxWidth("100%"))
314
- }
315
- }
316
-
317
- renderLuxDemo(lux: Lux, name: String, demos: List[Demo]) {
318
- demos.find {_.name == name}.map {_.render(lux)}.else {lux.text("Demo not found")}
319
- }
320
-
321
- renderInlines(lux: Lux, inlines: List[Inline]) {
322
- inlines.separate([Text(" ")]).each {renderInline(lux, _)}
323
- }
324
-
325
- renderInline(lux: Lux, inline: Inline) {
326
- inline.{
327
- | Anchor(heading, title) =>
328
- lux.add("a") {
329
- let url = title.map {"/guide/" + kebabCase(_)}.else {""} + "#" + kebabCase(heading)
330
- lux.cssClass(Styles.guideLinkCss)
331
- lux.set("href", url)
332
- lux.text(title.map {_ + ": "}.else {""} + heading)
333
- }
334
- | Bold(text) =>
335
- lux.add("b") {
336
- lux.text(text)
337
- }
338
- | Code(code, firefly) =>
339
- lux.add("code") {
340
- lux.cssClass(Styles.guideCodeCss)
341
- if(firefly) {
342
- renderHighlightedCode(lux, code)
343
- } else {
344
- lux.text(code)
345
- }
346
- }
347
- | Italic(text) =>
348
- lux.add("i") {
349
- lux.text(text)
350
- }
351
- | Link(text, url) =>
352
- lux.add("a") {
353
- lux.cssClass(Styles.guideLinkCss)
354
- lux.set("href", url)
355
- lux.text(text)
356
- }
357
- | Text(text) =>
358
- lux.text(text)
359
- }
360
- }
361
-
362
- renderHighlightedCode(lux: Lux, code: String) {
363
- try {
364
- Tokenizer.tokenize("<example>", code + "\n", None, False)
365
- }.toOption().map {tokens =>
366
- mutable offset = 0
367
- mutable index = 0
368
- tokens.each {token =>
369
- if(token.startOffset > offset) {
370
- lux.span {
371
- lux.cssClass(Styles.codeCommentCss)
372
- lux.text(code.slice(offset, token.startOffset))
373
- }
374
- }
375
- if(token.kind != LEnd) {
376
- let css = token.kind.{
377
- | LChar => Styles.codeStringCss
378
- | LFloat => Styles.codeNumberCss
379
- | LInt => Styles.codeNumberCss
380
- | LKeyword => Styles.codeKeywordCss
381
- | LNamespace => Styles.codeTypeCss
382
- | LString => Styles.codeStringCss
383
- | LUpper => Styles.codeTypeCss
384
- | LWildcard => Styles.codeVariableCss
385
- | LLower {tokens.get(index - 1).any {t =>
386
- t.kind == LBracketRight || t.kind == LDot
387
- }} => Styles.codeCallCss
388
- | LLower {tokens.grab(index + 1).kind == LBracketLeft} => Styles.codeCallCss
389
- | LLower => Styles.codeVariableCss
390
- | _ => Styles.codeOtherCss
391
- }
392
- lux.span {
393
- lux.cssClass(css)
394
- lux.text(code.slice(token.startOffset, token.stopOffset))
395
- }
396
- offset = token.stopOffset
397
- }
398
- index += 1
399
- }
400
- }.else {lux.text(code)}
401
- }
402
-
403
- kebabCase(text: String): String {
404
- mutable result = text.trim().lower().filter {c => c.isAsciiLetterOrDigit() || c == ' '}
405
- doWhile {
406
- let before = result
407
- result = result.replace(" ", "")
408
- result != before
409
- }
410
- result.replace(" ", "-")
411
- }
1
+ import Lux from ff:lux
2
+ import LuxEvent from ff:lux
3
+ import Css from ff:lux
4
+ import Tokenizer from ff:compiler
5
+ import Token from ff:compiler
6
+ import Styles
7
+ import DocumentParser
8
+
9
+ data Guide(
10
+ prefix: String
11
+ documents: List[Document]
12
+ )
13
+
14
+ data Document {
15
+ ReadyDocument(
16
+ sections: List[Section]
17
+ )
18
+ UnfetchedDocument(
19
+ header: String
20
+ )
21
+ }
22
+
23
+
24
+ data Section(heading: String) {
25
+ Section(
26
+ blocks: List[Block]
27
+ )
28
+ SplitSection(
29
+ first: Block
30
+ second: Block
31
+ )
32
+ }
33
+
34
+ data Block {
35
+ Paragraph(inlines: List[Inline])
36
+ Bullets(items: List[List[Inline]])
37
+ CodeBlock(code: String, firefly: Bool = False)
38
+ Image(url: String)
39
+ Video(url: String)
40
+ LuxDemo(demo: String)
41
+ }
42
+
43
+ data Inline {
44
+ Text(text: String)
45
+ Bold(text: String)
46
+ Italic(text: String)
47
+ Code(code: String, firefly: Bool = False)
48
+ Link(text: String, url: String)
49
+ Anchor(heading: String, title: Option[String] = None)
50
+ }
51
+
52
+ extend self: Guide {
53
+ heading(): String {
54
+ self.documents.first().map {_.heading()}.else {""}
55
+ }
56
+ title(): String {
57
+ if(self.heading() == "Firefly") {self.heading()} else {"Firefly " + self.heading()}
58
+ }
59
+ }
60
+
61
+ extend self: Document {
62
+ heading(): String {
63
+ self.{
64
+ | ReadyDocument(sections) =>
65
+ sections.first().map {_.heading}.else {""}
66
+ | UnfetchedDocument(header) => header
67
+ }
68
+ }
69
+ title(guide: Guide): String {
70
+ if(guide.documents.first().any {_.heading() == self.heading()}) {
71
+ guide.title()
72
+ } else {
73
+ self.heading() + " · " + guide.title()
74
+ }
75
+ }
76
+ path(guide: Guide): String {
77
+ if(guide.documents.first().any {_.heading() == self.heading()}) {
78
+ guide.prefix
79
+ } else {
80
+ guide.prefix + kebabCase(self.heading())
81
+ }
82
+ }
83
+ }
84
+
85
+ capability Demo(
86
+ name: String
87
+ render: Lux => Unit
88
+ )
89
+
90
+ data GuideDocument(
91
+ guide: Guide
92
+ document: Document
93
+ )
94
+
95
+ render(lux: Lux, httpClient: HttpClient, prefix: String, kebab: String, guides: List[Guide], demos: List[Demo]) {
96
+ let guide = guides.find {_.prefix == prefix}.else {guides.grabFirst()}
97
+ let document = guide.documents.find {d =>
98
+ kebabCase(d.heading()) == kebab
99
+ }.else {guide.documents.grabFirst()}
100
+ let guideDocuments = guides.flatMap {guide => guide.documents.map {document =>
101
+ GuideDocument(guide, document)
102
+ }}
103
+ let nextDocument = guideDocuments.dropWhile {n =>
104
+ n.guide.prefix != guide.prefix || n.document.heading() != document.heading()
105
+ }.dropFirst().first()
106
+ lux.add("div") {
107
+ lux.useState(False): menu, setMenu =>
108
+ lux.cssClass(Styles.pageCss)
109
+ lux.add("div") {
110
+ lux.cssClass(Styles.guideCss)
111
+ lux.add("main") {
112
+ lux.cssClass(Styles.guideMainCss)
113
+ renderDocument(lux, httpClient, prefix, document, demos, nextDocument)
114
+ }
115
+ renderTopbar(lux, menu, setMenu)
116
+ lux.add("div") {
117
+ lux.cssClass(Styles.guideSidebarBackdropCss)
118
+ if(menu) {lux.cssClass(Styles.guideSidebarBackdropOpenCss)}
119
+ lux.onClick {event =>
120
+ event.preventDefault()
121
+ setMenu(False)
122
+ }
123
+ }
124
+ renderSidebar(lux, guides, guide, document.heading(), menu)
125
+ }
126
+ }
127
+ }
128
+
129
+ renderTopbar(lux: Lux, menu: Bool, setMenu: Bool => Unit) {
130
+ lux.add("div") {
131
+ lux.cssClass(Styles.guideTopbarCss)
132
+ lux.add("a") {
133
+ lux.set("href", "/front/")
134
+ lux.cssClass(Styles.guideTopbarLogoCss)
135
+ lux.add("img") {
136
+ lux.set("src", "/assets/image/firefly-logo-yellow.png")
137
+ }
138
+ lux.div {
139
+ lux.text("Firefly")
140
+ }
141
+ }
142
+ lux.add("button") {
143
+ lux.set("aria-label", "Toggle the menu")
144
+ lux.cssClass(Styles.guideTopbarButtonCss)
145
+ lux.onClick {event =>
146
+ event.preventDefault()
147
+ setMenu(True)
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ renderSidebar(lux: Lux, guides: List[Guide], selectedGuide: Guide, selectedHeading: String, menu: Bool) {
154
+ lux.add("nav") {
155
+ lux.cssClass(Styles.guideSidebarCss)
156
+ if(menu) {lux.cssClass(Styles.guideSidebarOpenCss)}
157
+ lux.add("a") {
158
+ lux.set("href", "/front/")
159
+ lux.cssClass(Styles.guideSidebarLogoCss)
160
+ lux.add("img") {
161
+ lux.set("src", "/assets/image/firefly-logo-yellow.png")
162
+ }
163
+ lux.div {
164
+ lux.text("Firefly")
165
+ }
166
+ }
167
+ lux.add("form") {
168
+ lux.set("role", "search")
169
+ lux.add("input") {
170
+ lux.set("aria-label", "Search")
171
+ lux.cssClass(Styles.searchInputCss)
172
+ lux.set("placeholder", "Search...")
173
+ }
174
+ }
175
+ lux.add("ul") {
176
+ lux.cssClass(Styles.guideSidebarUl1Css)
177
+ guides.dropFirst().each {guide =>
178
+ lux.add("li") {
179
+ lux.cssClass(Styles.guideSidebarLi1Css)
180
+ lux.add("a") {
181
+ lux.cssClass(Styles.whiteLinkCss)
182
+ let heading = guide.documents.grabFirst().heading()
183
+ if(selectedGuide.prefix == guide.prefix && heading == selectedHeading) {
184
+ lux.set("aria-current", "page")
185
+ }
186
+ lux.set("href", guide.prefix)
187
+ lux.text(heading)
188
+ }
189
+ if(guide.documents.size() > 1):
190
+ lux.add("ul") {
191
+ lux.cssClass(Styles.guideSidebarUl2Css)
192
+ guide.documents.dropFirst().each {document =>
193
+ lux.add("li") {
194
+ lux.cssClass(Styles.guideSidebarLi2Css)
195
+ lux.add("a") {
196
+ lux.cssClass(Styles.whiteLinkCss)
197
+ let heading = document.heading()
198
+ if(selectedGuide.prefix == guide.prefix && heading == selectedHeading) {
199
+ lux.set("aria-current", "page")
200
+ }
201
+ lux.set("href", guide.prefix + kebabCase(heading))
202
+ lux.text(heading)
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ renderDocument(
214
+ lux: Lux
215
+ http: HttpClient
216
+ prefix: String
217
+ document: Document
218
+ demos: List[Demo]
219
+ nextDocument: Option[GuideDocument]
220
+ ) {
221
+ lux.add("article") {
222
+ lux.cssClass(Styles.guideDocumentCss)
223
+ document.{
224
+ | ReadyDocument(sections) =>
225
+ renderSections(lux, sections, demos)
226
+ renderNext(lux, nextDocument)
227
+ | UnfetchedDocument(header) =>
228
+ lux.div {
229
+ lux.useSuspense {lux.div {lux.text("Loading document...")}}: lux =>
230
+ let firstName = document.heading().split(' ').map {w =>
231
+ let word = w.trim().lower()
232
+ word.slice(0, 1).upper() + word.dropFirst()
233
+ }.join()
234
+ let asset = "/assets/markdown" + prefix + firstName + ".md"
235
+ let markdown = http.get(asset, []) {_.readText()}
236
+ let parser = DocumentParser(asset, markdown.split('\n'), 0)
237
+ let sections = parser.parseDocument()
238
+ renderSections(lux, sections, demos)
239
+ renderNext(lux, nextDocument)
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ renderSections(lux: Lux, sections: List[Section], demos: List[Demo]) {
246
+ sections.pairs().each {| Pair(index, section) =>
247
+ renderSection(lux, index == 0, section, demos)
248
+ }
249
+ }
250
+
251
+ renderNext(lux: Lux, nextDocument: Option[GuideDocument]) {
252
+ nextDocument.each {next =>
253
+ lux.div {
254
+ lux.cssClass(Styles.guideNextButtonCss)
255
+ lux.add("a") {
256
+ lux.cssClass(Styles.guideButtonCss)
257
+ lux.set("href", next.document.path(next.guide))
258
+ lux.text("Next: " + next.document.heading())
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ renderSection(lux: Lux, firstSection: Bool, section: Section, demos: List[Demo]) {
265
+ section.{
266
+ | Section(heading, blocks) =>
267
+ lux.add(if(firstSection) {"h1"} else {"h2"}) {
268
+ lux.cssClass(if(firstSection) {Styles.guideH1Css} else {Styles.guideH2Css})
269
+ lux.text(heading)
270
+ }
271
+ blocks.each {renderBlock(lux, _, demos)}
272
+ | SplitSection(heading, first, second) =>
273
+ lux.add("div") {
274
+ lux.cssClass(Styles.guideSplitCss)
275
+ lux.add("div") {
276
+ lux.add("h2") {
277
+ lux.cssClass(Styles.guideSplitHeadingCss)
278
+ lux.text(section.heading)
279
+ }
280
+ renderBlock(lux, first, demos)
281
+ }
282
+ lux.add("div") {
283
+ renderBlock(lux, second, demos)
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ renderBlock(lux: Lux, block: Block, demos: List[Demo]) {
290
+ block.{
291
+ | Paragraph(inlines) => renderParagraph(lux, inlines)
292
+ | CodeBlock(code, firefly) => renderCodeBlock(lux, code, firefly)
293
+ | Bullets(items) => renderBullets(lux, items)
294
+ | Image(url) => renderImage(lux, url)
295
+ | Video(url) => renderVideo(lux, url)
296
+ | LuxDemo(name) => renderLuxDemo(lux, name, demos)
297
+ }
298
+ }
299
+
300
+ renderParagraph(lux: Lux, inlines: List[Inline]) {
301
+ lux.add("p") {
302
+ renderInlines(lux, inlines)
303
+ }
304
+ }
305
+
306
+ renderCodeBlock(lux: Lux, code: String, firefly: Bool) {
307
+ let lines = code.lines().dropWhile {_.all {_ == ' '}}.reverse().dropWhile {_.all {_ == ' '}}.reverse()
308
+ let indentation = lines.first().map {_.takeWhile {_ == ' '}.size()}.else {0}
309
+ lux.add("pre") {
310
+ lux.cssClass(Styles.guideCodeBlockCss)
311
+ lux.add("code") {
312
+ lux.cssClass(Styles.guideCodeCss)
313
+ let unindentedCode = lines.map {_.dropFirst(indentation)}.join("\n")
314
+ if(firefly) {
315
+ renderHighlightedCode(lux, unindentedCode)
316
+ } else {
317
+ lux.text(unindentedCode)
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ renderBullets(lux: Lux, items: List[List[Inline]]) {
324
+ lux.add("ul") {
325
+ items.each {inlines =>
326
+ lux.add("li") {
327
+ renderInlines(lux, inlines)
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ renderImage(lux: Lux, url: String) {
334
+ lux.add("img") {
335
+ lux.set("src", url)
336
+ lux.css(Css.maxWidth("100%"))
337
+ lux.css(Css.borderRadius("8px"))
338
+ }
339
+ }
340
+
341
+ renderVideo(lux: Lux, url: String) {
342
+ lux.add("video") {
343
+ lux.set("src", url)
344
+ lux.css(Css.maxWidth("100%"))
345
+ }
346
+ }
347
+
348
+ renderLuxDemo(lux: Lux, name: String, demos: List[Demo]) {
349
+ demos.find {_.name == name}.map {_.render(lux)}.else {lux.text("Demo not found")}
350
+ }
351
+
352
+ renderInlines(lux: Lux, inlines: List[Inline]) {
353
+ inlines.separate([Text(" ")]).each {renderInline(lux, _)}
354
+ }
355
+
356
+ renderInline(lux: Lux, inline: Inline) {
357
+ inline.{
358
+ | Anchor(heading, title) =>
359
+ lux.add("a") {
360
+ let url = title.map {"/guide/" + kebabCase(_)}.else {""} + "#" + kebabCase(heading)
361
+ lux.cssClass(Styles.guideLinkCss)
362
+ lux.set("href", url)
363
+ lux.text(title.map {_ + ": "}.else {""} + heading)
364
+ }
365
+ | Bold(text) =>
366
+ lux.add("b") {
367
+ lux.text(text)
368
+ }
369
+ | Code(code, firefly) =>
370
+ lux.add("code") {
371
+ lux.cssClass(Styles.guideCodeCss)
372
+ if(firefly) {
373
+ renderHighlightedCode(lux, code)
374
+ } else {
375
+ lux.text(code)
376
+ }
377
+ }
378
+ | Italic(text) =>
379
+ lux.add("i") {
380
+ lux.text(text)
381
+ }
382
+ | Link(text, url) =>
383
+ lux.add("a") {
384
+ lux.cssClass(Styles.guideLinkCss)
385
+ lux.set("href", url)
386
+ lux.text(text)
387
+ }
388
+ | Text(text) =>
389
+ lux.text(text)
390
+ }
391
+ }
392
+
393
+ renderHighlightedCode(lux: Lux, code: String) {
394
+ try {
395
+ Tokenizer.tokenize("<example>", code + "\n", None, False)
396
+ }.toOption().map {tokens =>
397
+ mutable offset = 0
398
+ mutable index = 0
399
+ tokens.each {token =>
400
+ if(token.startOffset > offset && index <= tokens.size() - 5) {
401
+ lux.span {
402
+ lux.cssClass(Styles.codeCommentCss)
403
+ lux.text(code.slice(offset, token.startOffset))
404
+ }
405
+ }
406
+ if(token.kind != LEnd) {
407
+ let css = token.kind.{
408
+ | LChar => Styles.codeStringCss
409
+ | LFloat => Styles.codeNumberCss
410
+ | LInt => Styles.codeNumberCss
411
+ | LKeyword => Styles.codeKeywordCss
412
+ | LNamespace => Styles.codeTypeCss
413
+ | LString => Styles.codeStringCss
414
+ | LUpper => Styles.codeTypeCss
415
+ | LWildcard => Styles.codeVariableCss
416
+ | LLower {tokens.get(index - 1).any {t =>
417
+ t.kind == LBracketRight || t.kind == LDot
418
+ }} => Styles.codeCallCss
419
+ | LLower {tokens.grab(index + 1).kind == LBracketLeft} => Styles.codeCallCss
420
+ | LLower => Styles.codeVariableCss
421
+ | _ => Styles.codeOtherCss
422
+ }
423
+ lux.span {
424
+ lux.cssClass(css)
425
+ lux.text(code.slice(token.startOffset, token.stopOffset))
426
+ }
427
+ offset = token.stopOffset
428
+ }
429
+ index += 1
430
+ }
431
+ }.else {lux.text(code)}
432
+ }
433
+
434
+ kebabCase(text: String): String {
435
+ mutable result = text.trim().lower().filter {c => c.isAsciiLetterOrDigit() || c == ' '}
436
+ doWhile {
437
+ let before = result
438
+ result = result.replace(" ", "")
439
+ result != before
440
+ }
441
+ result.replace(" ", "-")
442
+ }