firefly-compiler 0.4.77 → 0.4.79
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.
- package/.hintrc +4 -4
- package/.vscode/settings.json +4 -4
- package/bin/Release.ff +153 -153
- package/bin/firefly.mjs +1 -1
- package/compiler/Builder.ff +257 -257
- package/compiler/Compiler.ff +227 -227
- package/compiler/Dependencies.ff +187 -187
- package/compiler/DependencyLock.ff +17 -17
- package/compiler/JsEmitter.ff +946 -946
- package/compiler/LspHook.ff +202 -202
- package/compiler/ModuleCache.ff +178 -178
- package/compiler/Workspace.ff +88 -88
- package/core/.firefly/include/package-lock.json +564 -394
- package/core/.firefly/include/package.json +5 -5
- package/core/.firefly/include/prepare.sh +1 -1
- package/core/.firefly/package.ff +2 -2
- package/core/Array.ff +265 -265
- package/core/Atomic.ff +64 -64
- package/core/Box.ff +7 -7
- package/core/BrowserSystem.ff +40 -40
- package/core/BuildSystem.ff +148 -148
- package/core/Crypto.ff +96 -96
- package/core/Equal.ff +36 -36
- package/core/HttpClient.ff +148 -148
- package/core/JsSystem.ff +69 -69
- package/core/Json.ff +434 -434
- package/core/List.ff +486 -486
- package/core/Lock.ff +144 -144
- package/core/NodeSystem.ff +216 -216
- package/core/Ordering.ff +161 -161
- package/core/Path.ff +401 -401
- package/core/Random.ff +134 -134
- package/core/RbMap.ff +216 -216
- package/core/Show.ff +43 -43
- package/core/SourceLocation.ff +68 -68
- package/core/String.ff +16 -0
- package/core/Task.ff +141 -141
- package/experimental/benchmarks/ListGrab.ff +23 -23
- package/experimental/benchmarks/ListGrab.java +55 -55
- package/experimental/benchmarks/Pyrotek45.ff +30 -30
- package/experimental/benchmarks/Pyrotek45.java +64 -64
- package/experimental/bidirectional/Bidi.ff +88 -88
- package/experimental/random/Index.ff +53 -53
- package/experimental/random/Process.ff +120 -120
- package/experimental/random/Scrape.ff +51 -51
- package/experimental/random/Symbols.ff +73 -73
- package/experimental/random/Tensor.ff +52 -52
- package/experimental/random/Units.ff +36 -36
- package/experimental/s3/S3TestAuthorizationHeader.ff +39 -38
- package/experimental/s3/S3TestPut.ff +16 -15
- package/experimental/tests/TestJson.ff +26 -26
- package/firefly.sh +0 -0
- package/fireflysite/.firefly/package.ff +4 -0
- package/fireflysite/CommunityOverview.ff +20 -0
- package/fireflysite/CountingButtonDemo.ff +58 -0
- package/fireflysite/DocumentParser.ff +218 -0
- package/fireflysite/ExamplesOverview.ff +40 -0
- package/fireflysite/FrontPage.ff +360 -0
- package/fireflysite/Guide.ff +411 -0
- package/fireflysite/GuideAll.ff +21 -0
- package/fireflysite/GuideBaseTypes.ff +168 -0
- package/fireflysite/GuideControlFlow.ff +212 -0
- package/fireflysite/GuideIntroduction.ff +52 -0
- package/fireflysite/Main.ff +137 -15
- package/fireflysite/MatchingPasswordsDemo.ff +82 -0
- package/fireflysite/PackagesOverview.ff +49 -0
- package/fireflysite/PostgresqlDemo.ff +34 -0
- package/fireflysite/Styles.ff +495 -0
- package/fireflysite/assets/NotoSansMono-Regular.ttf +0 -0
- package/fireflysite/assets/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf +0 -0
- package/fireflysite/assets/autocomplete-small.png +0 -0
- package/fireflysite/assets/autocomplete.png +0 -0
- package/fireflysite/assets/edit-time-error.png +0 -0
- package/fireflysite/assets/firefly-logo-notext.png +0 -0
- package/fireflysite/assets/firefly-logo-yellow.png +0 -0
- package/fireflysite/assets/markdown/ControlFlow.md +136 -0
- package/fireflysite/assets/markdown/Example.md +78 -0
- package/lsp/.firefly/package.ff +1 -1
- package/lsp/CompletionHandler.ff +828 -828
- package/lsp/Handler.ff +714 -714
- package/lsp/HoverHandler.ff +79 -79
- package/lsp/LanguageServer.ff +272 -272
- package/lsp/SignatureHelpHandler.ff +55 -55
- package/lsp/SymbolHandler.ff +181 -181
- package/lsp/TestReferences.ff +17 -17
- package/lsp/TestReferencesCase.ff +7 -7
- package/lsp/stderr.txt +1 -1
- package/lsp/stdout.txt +34 -34
- package/lux/.firefly/package.ff +1 -1
- package/lux/Css.ff +648 -648
- package/lux/CssTest.ff +48 -48
- package/lux/Lux.ff +487 -487
- package/lux/LuxEvent.ff +116 -116
- package/lux/Main.ff +123 -123
- package/lux/Main2.ff +143 -144
- package/output/js/ff/compiler/Builder.mjs +43 -43
- package/output/js/ff/compiler/Dependencies.mjs +3 -3
- package/output/js/ff/core/Array.mjs +59 -59
- package/output/js/ff/core/Atomic.mjs +36 -36
- package/output/js/ff/core/BrowserSystem.mjs +11 -11
- package/output/js/ff/core/BuildSystem.mjs +30 -30
- package/output/js/ff/core/Crypto.mjs +40 -40
- package/output/js/ff/core/HttpClient.mjs +56 -56
- package/output/js/ff/core/Json.mjs +147 -147
- package/output/js/ff/core/List.mjs +50 -50
- package/output/js/ff/core/Lock.mjs +97 -97
- package/output/js/ff/core/NodeSystem.mjs +83 -83
- package/output/js/ff/core/Ordering.mjs +8 -8
- package/output/js/ff/core/Path.mjs +231 -231
- package/output/js/ff/core/Random.mjs +56 -56
- package/output/js/ff/core/String.mjs +20 -0
- package/output/js/ff/core/Task.mjs +31 -31
- package/package.json +1 -1
- package/rpc/.firefly/package.ff +1 -1
- package/rpc/Rpc.ff +70 -70
- package/s3/.firefly/package.ff +1 -1
- package/s3/S3.ff +94 -94
- package/unsafejs/UnsafeJs.ff +19 -19
- package/vscode/LICENSE.txt +21 -21
- package/vscode/Prepublish.ff +15 -15
- package/vscode/README.md +16 -16
- package/vscode/client/package.json +22 -22
- package/vscode/client/src/extension.ts +104 -104
- package/vscode/icons/firefly-icon.svg +10 -10
- package/vscode/language-configuration.json +61 -61
- package/vscode/package-lock.json +3623 -3623
- package/vscode/package.json +15 -1
- package/vscode/snippets.json +241 -241
- package/vscode/syntaxes/firefly-markdown-injection.json +45 -0
- package/webserver/.firefly/include/package-lock.json +22 -16
- package/webserver/.firefly/include/package.json +5 -5
- package/webserver/.firefly/package.ff +2 -2
- package/webserver/WebServer.ff +685 -685
- package/websocket/.firefly/package.ff +1 -1
- package/websocket/WebSocket.ff +131 -131
|
@@ -0,0 +1,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, 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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Guide
|
|
2
|
+
import GuideIntroduction
|
|
3
|
+
import GuideControlFlow
|
|
4
|
+
import GuideBaseTypes
|
|
5
|
+
|
|
6
|
+
mock(): List[Document] {
|
|
7
|
+
[
|
|
8
|
+
GuideIntroduction.new()
|
|
9
|
+
GuideBaseTypes.new()
|
|
10
|
+
//GuideControlFlow.new()
|
|
11
|
+
UnfetchedDocument("Control Flow")
|
|
12
|
+
ReadyDocument([Section("Custom types", [])])
|
|
13
|
+
ReadyDocument([Section("Async I/O", [])])
|
|
14
|
+
ReadyDocument([Section("Structured concurrency", [])])
|
|
15
|
+
ReadyDocument([Section("Applications", [])])
|
|
16
|
+
ReadyDocument([Section("Packages and modules", [])])
|
|
17
|
+
ReadyDocument([Section("Functions and methods", [])])
|
|
18
|
+
ReadyDocument([Section("Traits and instances", [])])
|
|
19
|
+
ReadyDocument([Section("JavaScript interop", [])])
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import Guide
|
|
2
|
+
|
|
3
|
+
new(): Document {
|
|
4
|
+
ReadyDocument([
|
|
5
|
+
|
|
6
|
+
Section("Base types", [
|
|
7
|
+
Paragraph([
|
|
8
|
+
Text("Firefly has four primitive types: ")
|
|
9
|
+
Code("String, Char, Int, Float.", firefly = True)
|
|
10
|
+
Text("They have the following literal syntax:")
|
|
11
|
+
])
|
|
12
|
+
CodeBlock("""
|
|
13
|
+
"Hello, World!" // String
|
|
14
|
+
'H' // Char
|
|
15
|
+
42 // Int
|
|
16
|
+
3.14 // Float
|
|
17
|
+
""".replace("'''", "\"\"\""), firefly = True)
|
|
18
|
+
Paragraph([
|
|
19
|
+
Text("Strings may be concatenated using plus, e.g. ")
|
|
20
|
+
Code("\"ban\" + \"ana\".", firefly = True)
|
|
21
|
+
Text("String literals may contain escape sequences, e.g. ")
|
|
22
|
+
Code("\\n, \\t, \\r, \\\", \\\'.")
|
|
23
|
+
Text("You may also write multiline strings using triple quotes:")
|
|
24
|
+
Code("\"\"\"...\"\"\".", firefly = True)
|
|
25
|
+
])
|
|
26
|
+
Paragraph([
|
|
27
|
+
Text("Ints support the usual arithmetic operators:")
|
|
28
|
+
Code("+ - * / ^", firefly = True)
|
|
29
|
+
Text("(division returns a Float, use the")
|
|
30
|
+
Code(".div()", firefly = True)
|
|
31
|
+
Text("method for integer division).")
|
|
32
|
+
])
|
|
33
|
+
Paragraph([
|
|
34
|
+
Text("Float literals may use scientific notation, e.g. ")
|
|
35
|
+
Code("1.0e3, 1.0e-3", firefly = True)
|
|
36
|
+
Text("The usual arithmetic operators are available:")
|
|
37
|
+
Code("+ - * / ^", firefly = True)
|
|
38
|
+
])
|
|
39
|
+
])
|
|
40
|
+
Section("Booleans", [
|
|
41
|
+
Paragraph([
|
|
42
|
+
Text("Bool is defined as a data type:")
|
|
43
|
+
])
|
|
44
|
+
CodeBlock("""
|
|
45
|
+
data Bool {
|
|
46
|
+
False
|
|
47
|
+
True
|
|
48
|
+
}
|
|
49
|
+
""", firefly = True)
|
|
50
|
+
Paragraph([
|
|
51
|
+
Text("Meaning it has two values:")
|
|
52
|
+
Code("False, True.", firefly = True)
|
|
53
|
+
Text("They support the usual logical operators:")
|
|
54
|
+
Code("&& || !", firefly = True)
|
|
55
|
+
])
|
|
56
|
+
])
|
|
57
|
+
Section("Durations and instants", [
|
|
58
|
+
Paragraph([
|
|
59
|
+
Text("Durations represent elapsed time, and instants represent points in time.")
|
|
60
|
+
])
|
|
61
|
+
CodeBlock("""
|
|
62
|
+
newtype Duration(seconds: Float)
|
|
63
|
+
newtype Instant(since1970: Duration)
|
|
64
|
+
""", firefly = True)
|
|
65
|
+
Paragraph([
|
|
66
|
+
Text("You can get the current instant:")
|
|
67
|
+
Code("system.mainTask.now()", firefly = True)
|
|
68
|
+
Text("or ask how much time elapsed since the program started:")
|
|
69
|
+
Code("system.mainTask.elapsed().", firefly = True)
|
|
70
|
+
Text("Note that neither has a time zone nor support for local calendars.")
|
|
71
|
+
])
|
|
72
|
+
])
|
|
73
|
+
Section("Lists and arrays", [
|
|
74
|
+
Paragraph([
|
|
75
|
+
Text("List is the most common data structure in Firefly.")
|
|
76
|
+
Text("It's an immutable contiguous blocks of values that can be indexed by integers, mapped over, filtered and so on.")
|
|
77
|
+
])
|
|
78
|
+
CodeBlock("""
|
|
79
|
+
let fruits: List[String] =
|
|
80
|
+
["apple", "banana", "orange"]
|
|
81
|
+
""", firefly = True)
|
|
82
|
+
Paragraph([
|
|
83
|
+
Text("You can flatten one list into another using the spread syntax:")
|
|
84
|
+
Code("[...friuts, \"cherry\"] == [\"apple\", \"banana\", \"orange\", \"cherry\"].", firefly = True)
|
|
85
|
+
])
|
|
86
|
+
Paragraph([
|
|
87
|
+
Text("Arrays is the mutable (and resizable) version of lists. You can convert a list to an array like this:")
|
|
88
|
+
Code("fruits.toArray(),", firefly = True)
|
|
89
|
+
Text("and you can construct an empty array like this:")
|
|
90
|
+
Code("Array.new().", firefly = True)
|
|
91
|
+
])
|
|
92
|
+
])
|
|
93
|
+
Section("Sets and maps", [
|
|
94
|
+
Paragraph([
|
|
95
|
+
Text("Set and Map are immutable and sorted collections. Sets have no duplicate entries, and maps have no duplicate keys.")
|
|
96
|
+
])
|
|
97
|
+
CodeBlock("""
|
|
98
|
+
let fruitSet: Set[String] =
|
|
99
|
+
["apple", "banana", "orange"].toSet()
|
|
100
|
+
|
|
101
|
+
let numberMap: Map[String, Int] =
|
|
102
|
+
[Pair("one", 1), Pair("two", 2)].toMap()
|
|
103
|
+
""", firefly = True)
|
|
104
|
+
Paragraph([
|
|
105
|
+
Text("They have methods for lookup, union, intersection, etc.")
|
|
106
|
+
Text("In addition, there's a mutable IntMap and StringMap that's optimized for integer and string keys, respectively.")
|
|
107
|
+
])
|
|
108
|
+
])
|
|
109
|
+
Section("Streams and buffers", [
|
|
110
|
+
Paragraph([
|
|
111
|
+
Text("Streams are lazy sequences that are consumed when read. They're often used for streaming I/O.")
|
|
112
|
+
])
|
|
113
|
+
CodeBlock("""
|
|
114
|
+
// Streaming file copy
|
|
115
|
+
let stream = system.path("file1.txt").readStream()
|
|
116
|
+
system.path("file2.txt").writeStream(stream)
|
|
117
|
+
""", firefly = True)
|
|
118
|
+
Paragraph([
|
|
119
|
+
Text("Note that there is no risk of leaking a file handle above, as the file isn't opened until the first element of the stream is read,")
|
|
120
|
+
Text("and the writeStream method will take care of closing the stream, even in the face of I/O errors.")
|
|
121
|
+
])
|
|
122
|
+
Paragraph([
|
|
123
|
+
Text("In this case you get a")
|
|
124
|
+
Code("Stream[Buffer]", firefly = True)
|
|
125
|
+
Text("back, which is common for streaming I/O in Firefly.")
|
|
126
|
+
Text("A buffer is a mutable fixed-size array of bytes that you can manipulate with hardware friendly binary operations (8-bit, 16-bit, 32-bit etc.).")
|
|
127
|
+
])
|
|
128
|
+
])
|
|
129
|
+
Section("Option, Pair, Unit and Nothing", [
|
|
130
|
+
Paragraph([
|
|
131
|
+
Text("Option represent a value that may be missing. There's no null in Firefly, so Option is often the return type of lookups. It's defined like this:")
|
|
132
|
+
])
|
|
133
|
+
CodeBlock("""
|
|
134
|
+
data Option[T] {
|
|
135
|
+
None
|
|
136
|
+
Some(value: T)
|
|
137
|
+
}
|
|
138
|
+
""", firefly = True)
|
|
139
|
+
Paragraph([
|
|
140
|
+
Text("Pair is a generic record with two fields, e.g. for key/value pairs in a map:")
|
|
141
|
+
])
|
|
142
|
+
CodeBlock("""
|
|
143
|
+
data Pair[A, B](first: A, second: B)
|
|
144
|
+
""", firefly = True)
|
|
145
|
+
Paragraph([
|
|
146
|
+
Text("Unit is the return type for functions that have no interesting return value:")
|
|
147
|
+
])
|
|
148
|
+
CodeBlock("""
|
|
149
|
+
data Unit {
|
|
150
|
+
Unit
|
|
151
|
+
}
|
|
152
|
+
""", firefly = True)
|
|
153
|
+
Paragraph([
|
|
154
|
+
Text("Meaning it only has one possible value:")
|
|
155
|
+
Code("Unit.", firefly = True)
|
|
156
|
+
])
|
|
157
|
+
Paragraph([
|
|
158
|
+
Text("Nothing is a type that has no values:")
|
|
159
|
+
])
|
|
160
|
+
CodeBlock("""
|
|
161
|
+
data Nothing {}
|
|
162
|
+
""", firefly = True)
|
|
163
|
+
Paragraph([
|
|
164
|
+
Text("It's occasionally useful to statically rule out the possibility of constructing something.")
|
|
165
|
+
])
|
|
166
|
+
])
|
|
167
|
+
])
|
|
168
|
+
}
|