firefly-compiler 0.4.77 → 0.4.78

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/core/Json.ff CHANGED
@@ -184,7 +184,7 @@ extend self: Json {
184
184
 
185
185
  index(key: Int): Json
186
186
  target js sync """
187
- return typeof self_ === 'array' ? self_[key] ?? null : null;
187
+ return Array.isArray(self_) ? self_[key] ?? null : null;
188
188
  """
189
189
 
190
190
  hasField(key: String): Bool
@@ -200,7 +200,7 @@ extend self: Json {
200
200
 
201
201
  getIndex(key: Int): Option[Json]
202
202
  target js sync """
203
- return typeof self_ === 'array' ? ff_core_Option.Some(self_[key_] ?? null) : ff_core_Option.None();
203
+ return Array.isArray(self_) ? ff_core_Option.Some(self_[key_] ?? null) : ff_core_Option.None();
204
204
  """
205
205
 
206
206
  getFields(): Option[List[String]]
package/core/String.ff CHANGED
@@ -206,6 +206,22 @@ extend self: String {
206
206
  return true;
207
207
  """
208
208
 
209
+ filter(body: Char => Bool): String
210
+ target js sync """
211
+ const result = [];
212
+ for(let i = 0; i < self_.length; i++) {
213
+ if(body_(self_.charCodeAt(i))) result.push(self_.charAt(i));
214
+ }
215
+ return result.join("");
216
+ """
217
+ target js async """
218
+ const result = [];
219
+ for(let i = 0; i < self_.length; i++) {
220
+ if(await body_(self_.charCodeAt(i))) result.push(self_.charAt(i));
221
+ }
222
+ return result.join("");
223
+ """
224
+
209
225
  toBuffer(): Buffer
210
226
  target js sync """
211
227
  const encoded = new TextEncoder().encode(self_)
@@ -0,0 +1,4 @@
1
+ package ff:fireflysite:0.0.0
2
+ dependency ff:webserver:0.0.0
3
+ dependency ff:lux:0.0.0
4
+ dependency ff:compiler:0.0.0
@@ -0,0 +1,20 @@
1
+ import Guide
2
+ import CountingButtonDemo
3
+ import MatchingPasswordsDemo
4
+
5
+ new(): Document {
6
+ Document([
7
+ Section("Community", [
8
+ Paragraph([
9
+ Text("The developers of Firefly hang out in")
10
+ Code("#firefly")
11
+ Text("on the")
12
+ Link("r/ProgrammingLanguages", "https://discord.gg/yQ9mSk7CJ5")
13
+ Text("Discord.")
14
+ ])
15
+ Paragraph([
16
+ Text("If you're interested in Firefly, that's the best way to be part of the community for now.")
17
+ ])
18
+ ])
19
+ ])
20
+ }
@@ -0,0 +1,58 @@
1
+ import Guide
2
+ import Lux from ff:lux
3
+ import LuxEvent from ff:lux
4
+
5
+ name = "Counting button"
6
+
7
+ new(): Demo {
8
+ Demo(
9
+ name
10
+ {render(_)}
11
+ )
12
+ }
13
+
14
+ render(lux: Lux): Unit {
15
+ lux.div {
16
+ lux.useState(0): count, setCount =>
17
+ lux.button {
18
+ lux.text("Clicked " + count + " times")
19
+ lux.onClick {event =>
20
+ event.preventDefault()
21
+ setCount(count + 1)
22
+ }
23
+ }
24
+ }
25
+ }
26
+
27
+ newDocument(): Document {
28
+ Document([
29
+ Section(name, [
30
+ Paragraph([Text("A button that counts how many times it's been clicked.")])
31
+ ])
32
+ Section("Demo", [
33
+ LuxDemo(name)
34
+ ])
35
+ Section("Code", [
36
+ Paragraph([
37
+ Text("This example uses the Lux frontend framework.")
38
+ Text("In Lux, state lives in the DOM, and the")
39
+ Code("useState")
40
+ Text("method lets you maintain state in the parent node.")
41
+ ])
42
+ CodeBlock("""
43
+ render(lux: Lux): Unit {
44
+ lux.div {
45
+ lux.useState(0): count, setCount =>
46
+ lux.button {
47
+ lux.text("Clicked " + count + " times")
48
+ lux.onClick {event =>
49
+ event.preventDefault()
50
+ setCount(count + 1)
51
+ }
52
+ }
53
+ }
54
+ }
55
+ """, firefly = True)
56
+ ])
57
+ ])
58
+ }
@@ -0,0 +1,31 @@
1
+ import Guide
2
+ import CountingButtonDemo
3
+ import MatchingPasswordsDemo
4
+
5
+ mock(): List[Document] {
6
+ [
7
+ new()
8
+ CountingButtonDemo.newDocument()
9
+ MatchingPasswordsDemo.newDocument()
10
+ ]
11
+ }
12
+
13
+ new(): Document {
14
+ Document([
15
+ Section("Overview", [
16
+ Paragraph([
17
+ Text("Here you'll find examples that demonstrate what you can do with Firefly.")
18
+ Text("In fact, you're looking at an example right now - this site is")
19
+ Link("written in Firefly", "https://github.com/Ahnfelt/firefly-boot/tree/master/fireflysite")
20
+ Text("and so is the")
21
+ Link("Firefly compiler", "https://github.com/Ahnfelt/firefly-boot/tree/master/compiler")
22
+ Text("and")
23
+ Link("language server", "https://github.com/Ahnfelt/firefly-boot/tree/master/lsp")
24
+ Text(".")
25
+ ])
26
+ Paragraph([
27
+ Text("Check the list here for examples and demos.")
28
+ ])
29
+ ])
30
+ ])
31
+ }
@@ -0,0 +1,308 @@
1
+ import Lux from ff:lux
2
+ import Css from ff:lux
3
+ import Tokenizer from ff:compiler
4
+ import Token from ff:compiler
5
+ import Styles
6
+
7
+ data Guide(
8
+ documents: List[Document]
9
+ )
10
+
11
+ data Document(
12
+ sections: List[Section]
13
+ )
14
+
15
+ data Section(
16
+ heading: String
17
+ blocks: List[Block]
18
+ )
19
+
20
+ data Block {
21
+ Paragraph(inlines: List[Inline])
22
+ Bullets(items: List[List[Inline]])
23
+ CodeBlock(code: String, firefly: Bool = False)
24
+ Image(url: String)
25
+ Video(url: String)
26
+ LuxDemo(demo: String)
27
+ }
28
+
29
+ data Inline {
30
+ Text(text: String)
31
+ Bold(text: String)
32
+ Italic(text: String)
33
+ Code(code: String, firefly: Bool = False)
34
+ Link(text: String, url: String)
35
+ Anchor(heading: String, title: Option[String] = None)
36
+ }
37
+
38
+ capability Demo(
39
+ name: String
40
+ render: Lux => Unit
41
+ )
42
+
43
+ render(lux: Lux, prefix: String, kebab: String, guide: Guide, demos: List[Demo]) {
44
+ let document = guide.documents.find {
45
+ _.sections.first().any {kebabCase(_.heading) == kebab}
46
+ }.else {guide.documents.grabFirst()}
47
+ lux.add("div") {
48
+ lux.cssClass(Styles.pageCss)
49
+ renderTopbar(lux, prefix)
50
+ lux.add("div") {
51
+ lux.cssClass(Styles.guideCss)
52
+ renderSidebar(lux, prefix, guide, document.sections.first().map {_.heading}.else {""})
53
+ lux.add("main") {
54
+ lux.cssClass(Styles.guideMainCss)
55
+ renderDocument(lux, document, demos)
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ renderSoleDocument(lux: Lux, prefix: String, document: Document, demos: List[Demo]) {
62
+ lux.add("div") {
63
+ lux.cssClass(Styles.pageCss)
64
+ renderTopbar(lux, prefix)
65
+ lux.add("main") {
66
+ lux.cssClass(Styles.guideSoleDocumentCss)
67
+ renderDocument(lux, document, demos)
68
+ }
69
+ }
70
+ }
71
+
72
+ renderTopbar(lux: Lux, prefix: String) {
73
+ lux.add("header") {
74
+ lux.add("nav") {
75
+ lux.cssClass(Styles.topbarCss)
76
+ lux.add("a") {
77
+ lux.cssClass(Styles.whiteLinkCss)
78
+ lux.cssClass(Styles.topbarFireflyCss)
79
+ if(prefix == "/") {lux.set("aria-current", "page")}
80
+ lux.set("href", "/")
81
+ lux.text("Firefly")
82
+ }
83
+ lux.add("a") {
84
+ lux.cssClass(Styles.whiteLinkCss)
85
+ if(prefix == "/guide/") {lux.set("aria-current", "page")}
86
+ lux.set("href", "/guide/")
87
+ lux.text("Guide")
88
+ }
89
+ lux.add("a") {
90
+ lux.cssClass(Styles.whiteLinkCss)
91
+ if(prefix == "/examples/") {lux.set("aria-current", "page")}
92
+ lux.set("href", "/examples/")
93
+ lux.text("Examples")
94
+ }
95
+ lux.add("a") {
96
+ lux.cssClass(Styles.whiteLinkCss)
97
+ if(prefix == "/packages/") {lux.set("aria-current", "page")}
98
+ lux.set("href", "/packages/")
99
+ lux.text("Packages")
100
+ }
101
+ lux.add("a") {
102
+ lux.cssClass(Styles.whiteLinkCss)
103
+ if(prefix == "/community/") {lux.set("aria-current", "page")}
104
+ lux.set("href", "/community/")
105
+ lux.text("Community")
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ renderSidebar(lux: Lux, prefix: String, guide: Guide, selectedHeading: String) {
112
+ lux.add("nav") {
113
+ lux.cssClass(Styles.guideSidebarCss)
114
+ lux.add("form") {
115
+ lux.set("role", "search")
116
+ lux.add("input") {
117
+ lux.set("aria-label", "Search")
118
+ lux.cssClass(Styles.searchInputCss)
119
+ lux.set("placeholder", "Search...")
120
+ }
121
+ }
122
+ lux.add("ul") {
123
+ lux.cssClass(Styles.guideSidebarUlCss)
124
+ mutable first = True
125
+ guide.documents.each {document =>
126
+ lux.add("li") {
127
+ lux.cssClass(Styles.guideSidebarLiCss)
128
+ lux.add("a") {
129
+ let heading = document.sections.grabFirst().heading
130
+ lux.cssClass(Styles.whiteLinkCss)
131
+ if(heading == selectedHeading) {lux.set("aria-current", "page")}
132
+ lux.set("href", if(first) {prefix} else {prefix + kebabCase(heading)})
133
+ lux.text(heading)
134
+ }
135
+ }
136
+ first = False
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ renderDocument(lux: Lux, document: Document, demos: List[Demo]) {
143
+ lux.add("article") {
144
+ lux.cssClass(Styles.guideDocumentCss)
145
+ document.sections.pairs().each {| Pair(index, section) =>
146
+ renderSection(lux, index == 0, section, demos)
147
+ }
148
+ }
149
+ }
150
+
151
+ renderSection(lux: Lux, first: Bool, section: Section, demos: List[Demo]) {
152
+ lux.add(if(first) {"h1"} else {"h2"}) {
153
+ lux.cssClass(if(first) {Styles.guideH1Css} else {Styles.guideH2Css})
154
+ lux.text(section.heading)
155
+ }
156
+ section.blocks.each {renderBlock(lux, _, demos)}
157
+ }
158
+
159
+ renderBlock(lux: Lux, block: Block, demos: List[Demo]) {
160
+ block.{
161
+ | Paragraph(inlines) => renderParagraph(lux, inlines)
162
+ | CodeBlock(code, firefly) => renderCodeBlock(lux, code, firefly)
163
+ | Bullets(items) => renderBullets(lux, items)
164
+ | Image(url) => renderImage(lux, url)
165
+ | Video(url) => renderVideo(lux, url)
166
+ | LuxDemo(name) => renderLuxDemo(lux, name, demos)
167
+ }
168
+ }
169
+
170
+ renderParagraph(lux: Lux, inlines: List[Inline]) {
171
+ lux.add("p") {
172
+ renderInlines(lux, inlines)
173
+ }
174
+ }
175
+
176
+ renderCodeBlock(lux: Lux, code: String, firefly: Bool) {
177
+ let lines = code.lines().dropWhile {_.all {_ == ' '}}.reverse().dropWhile {_.all {_ == ' '}}.reverse()
178
+ let indentation = lines.first().map {_.takeWhile {_ == ' '}.size()}.else {0}
179
+ lux.add("pre") {
180
+ lux.cssClass(Styles.guideCodeBlockCss)
181
+ lux.add("code") {
182
+ lux.cssClass(Styles.guideCodeCss)
183
+ let unindentedCode = lines.map {_.dropFirst(indentation)}.join("\n")
184
+ if(firefly) {
185
+ renderHighlightedCode(lux, unindentedCode)
186
+ } else {
187
+ lux.text(unindentedCode)
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ renderBullets(lux: Lux, items: List[List[Inline]]) {
194
+ lux.add("ul") {
195
+ items.each {inlines =>
196
+ lux.add("li") {
197
+ renderInlines(lux, inlines)
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ renderImage(lux: Lux, url: String) {
204
+ lux.add("img") {
205
+ lux.set("src", url)
206
+ lux.css(Css.maxWidth("100%"))
207
+ }
208
+ }
209
+
210
+ renderVideo(lux: Lux, url: String) {
211
+ lux.add("video") {
212
+ lux.set("src", url)
213
+ lux.css(Css.maxWidth("100%"))
214
+ }
215
+ }
216
+
217
+ renderLuxDemo(lux: Lux, name: String, demos: List[Demo]) {
218
+ demos.find {_.name == name}.map {_.render(lux)}.else {lux.text("Demo not found")}
219
+ }
220
+
221
+ renderInlines(lux: Lux, inlines: List[Inline]) {
222
+ inlines.separate([Text(" ")]).each {renderInline(lux, _)}
223
+ }
224
+
225
+ renderInline(lux: Lux, inline: Inline) {
226
+ inline.{
227
+ | Anchor(heading, title) =>
228
+ lux.add("a") {
229
+ let url = title.map {"/guide/" + kebabCase(_)}.else {""} + "#" + kebabCase(heading)
230
+ lux.cssClass(Styles.guideLinkCss)
231
+ lux.set("href", url)
232
+ lux.text(title.map {_ + ": "}.else {""} + heading)
233
+ }
234
+ | Bold(text) =>
235
+ lux.add("b") {
236
+ lux.text(text)
237
+ }
238
+ | Code(code, firefly) =>
239
+ lux.add("code") {
240
+ lux.cssClass(Styles.guideCodeCss)
241
+ if(firefly) {
242
+ renderHighlightedCode(lux, code)
243
+ } else {
244
+ lux.text(code)
245
+ }
246
+ }
247
+ | Italic(text) =>
248
+ lux.add("i") {
249
+ lux.text(text)
250
+ }
251
+ | Link(text, url) =>
252
+ lux.add("a") {
253
+ lux.cssClass(Styles.guideLinkCss)
254
+ lux.set("href", url)
255
+ lux.text(text)
256
+ }
257
+ | Text(text) =>
258
+ lux.text(text)
259
+ }
260
+ }
261
+
262
+ renderHighlightedCode(lux: Lux, code: String) {
263
+ let tokens = Tokenizer.tokenize("<example>", code, None, False)
264
+ mutable offset = 0
265
+ mutable index = 0
266
+ tokens.each {token =>
267
+ if(token.startOffset > offset) {
268
+ lux.span {
269
+ lux.cssClass(Styles.codeCommentCss)
270
+ lux.text(code.slice(offset, token.startOffset))
271
+ }
272
+ }
273
+ if(token.kind != LEnd) {
274
+ let css = token.kind.{
275
+ | LChar => Styles.codeStringCss
276
+ | LFloat => Styles.codeNumberCss
277
+ | LInt => Styles.codeNumberCss
278
+ | LKeyword => Styles.codeKeywordCss
279
+ | LNamespace => Styles.codeTypeCss
280
+ | LString => Styles.codeStringCss
281
+ | LUpper => Styles.codeTypeCss
282
+ | LWildcard => Styles.codeVariableCss
283
+ | LLower {tokens.get(index - 1).any {t =>
284
+ t.kind == LBracketRight || t.kind == LDot
285
+ }} => Styles.codeCallCss
286
+ | LLower {tokens.grab(index + 1).kind == LBracketLeft} => Styles.codeCallCss
287
+ | LLower => Styles.codeVariableCss
288
+ | _ => Styles.codeOtherCss
289
+ }
290
+ lux.span {
291
+ lux.cssClass(css)
292
+ lux.text(code.slice(token.startOffset, token.stopOffset))
293
+ }
294
+ offset = token.stopOffset
295
+ }
296
+ index += 1
297
+ }
298
+ }
299
+
300
+ kebabCase(text: String): String {
301
+ mutable result = text.trim().lower().filter {c => c.isAsciiLetterOrDigit() || c == ' '}
302
+ doWhile {
303
+ let before = result
304
+ result = result.replace(" ", "")
305
+ result != before
306
+ }
307
+ result.replace(" ", "-")
308
+ }
@@ -0,0 +1,69 @@
1
+ import Guide
2
+
3
+ mock(): List[Document] {
4
+ [
5
+ new()
6
+ Document([Section("Control flow", [])])
7
+ Document([Section("Base types", [])])
8
+ Document([Section("Collections", [])])
9
+ Document([Section("Custom types", [])])
10
+ Document([Section("Async I/O", [])])
11
+ Document([Section("Structured concurrency", [])])
12
+ Document([Section("Applications", [])])
13
+ Document([Section("Packages and modules", [])])
14
+ Document([Section("Functions and methods", [])])
15
+ Document([Section("Traits and instances", [])])
16
+ Document([Section("JavaScript interop", [])])
17
+ ]
18
+ }
19
+
20
+ new(): Document {
21
+ Document([
22
+ Section("Introduction", [
23
+ Paragraph([
24
+ Text("In this guide you will learn to use Firefly.")
25
+ Text("Firefly is a programming language that compiles to JavaScript.")
26
+ Text("It runs in the browser and on the server, and lets you develop webapps with a minimum of hassle.")
27
+ ])
28
+ ])
29
+ Section("Getting started", [
30
+ Paragraph([
31
+ Text("Firefly comes with a compiler, a build system and a package manager.")
32
+ ])
33
+ Paragraph([
34
+ Text("Install Firefly:")
35
+ ])
36
+ CodeBlock("npm install -g firefly-compiler")
37
+ Paragraph([
38
+ Text("Save this code as")
39
+ Code("Hello.ff")
40
+ Text(":")
41
+ ])
42
+ CodeBlock("""
43
+ nodeMain(system: NodeSystem) {
44
+ system.writeLine("Hello, World!")
45
+ }
46
+ """, firefly = True)
47
+ Paragraph([
48
+ Text("And run it:")
49
+ ])
50
+ CodeBlock("firefly Hello.ff")
51
+ Paragraph([
52
+ Text("You now know how to run Firefly code!")
53
+ ])
54
+ ])
55
+ Section("Editor support", [
56
+ Paragraph([
57
+ Text("Install the")
58
+ Link("Firefly Language VSCode extension", "https://marketplace.visualstudio.com/items?itemName=firefly-team.firefly-lang")
59
+ Text(" to get autocompletion, renaming, go to definition, highlighting, etc. You can find it under Extensions in VSCode.")
60
+ ])
61
+ Paragraph([
62
+ Text("The Firefly code highlighting on this site matches the Dark Modern theme in VSCode.")
63
+ ])
64
+ Paragraph([
65
+ Text("If you'd like to make an extension for a different editor, the language server is available, as well as the source code for the VSCode extension.")
66
+ ])
67
+ ])
68
+ ])
69
+ }
@@ -1,15 +1,92 @@
1
- dependency ff:webserver:0.0.0
2
1
  import WebServer from ff:webserver
2
+ import Lux from ff:lux
3
+ import Guide
4
+ import GuideIntroduction
5
+ import ExamplesOverview
6
+ import PackagesOverview
7
+ import CommunityOverview
8
+ import Styles
9
+ import CountingButtonDemo
10
+ import MatchingPasswordsDemo
3
11
 
4
12
  nodeMain(system: NodeSystem): Unit {
5
13
  let host = system.arguments().grab(0)
6
14
  let port = system.arguments().grab(1).grabInt()
7
15
  WebServer.new(system, host, port).listen {request =>
8
- let parameters = if(request.readRawQueryString().size() == 0) {""} else {
9
- "?" + request.readRawQueryString()
16
+ let path = request.readPath()
17
+ if(path.startsWith("/guide/")) {
18
+ serveGuideHtml("Firefly Guide", request)
19
+ } elseIf {path.startsWith("/examples/")} {
20
+ serveGuideHtml("Firefly Examples", request)
21
+ } elseIf {path.startsWith("/packages/")} {
22
+ serveGuideHtml("Firefly Packages", request)
23
+ } elseIf {path.startsWith("/community/")} {
24
+ serveGuideHtml("Firefly Community", request)
25
+ } elseIf {path.startsWith("/js/") && !path.contains("..")} {
26
+ request.writeHeader("Content-Type", "text/javascript; charset=UTF-8")
27
+ let asset = if(path == "/js/ff/fireflysite/Main.mjs" && system.assets().exists("/js/Main.bundle.js")) {
28
+ "/js/Main.bundle.js"
29
+ } else {
30
+ path
31
+ }
32
+ request.writeText(system.assets().readText(asset))
33
+ } elseIf {path.startsWith("/favicon.ico")} {
34
+ request.writeHeader("Content-Type", "image/png")
35
+ request.writeStream(system.assets().readStream("/images/firefly-logo-notext.png"))
36
+ } else {
37
+ let parameters = if(request.readRawQueryString().size() == 0) {""} else {
38
+ "?" + request.readRawQueryString()
39
+ }
40
+ request.writeHeader("Location", "https://www.firefly-lang.org" + path + parameters)
41
+ request.writeStatus("302 Found")
10
42
  }
11
- request.writeHeader("Location", "https://www.firefly-lang.org" + request.readPath() + parameters)
12
- request.writeStatus("302 Found")
13
43
  }
14
44
  }
15
45
 
46
+ browserMain(system: BrowserSystem): Unit {
47
+ if(system.urlPath().startsWith("/guide/")) {
48
+ let kebab = system.urlPath().dropFirst("/guide/".size())
49
+ Lux.renderById(system, "main") {lux =>
50
+ Guide.render(lux, "/guide/", kebab, Guide(GuideIntroduction.mock()), [])
51
+ }
52
+ } elseIf {system.urlPath().startsWith("/examples/")} {
53
+ let kebab = system.urlPath().dropFirst("/examples/".size())
54
+ let demos = [CountingButtonDemo.new(), MatchingPasswordsDemo.new()]
55
+ Lux.renderById(system, "main") {lux =>
56
+ Guide.render(lux, "/examples/", kebab, Guide(ExamplesOverview.mock()), demos)
57
+ }
58
+ } elseIf {system.urlPath().startsWith("/packages/")} {
59
+ let kebab = system.urlPath().dropFirst("/packages/".size())
60
+ Lux.renderById(system, "main") {lux =>
61
+ Guide.renderSoleDocument(lux, "/packages/", PackagesOverview.new(), [])
62
+ }
63
+ } else {
64
+ let kebab = system.urlPath().dropFirst("/community/".size())
65
+ Lux.renderById(system, "main") {lux =>
66
+ Guide.renderSoleDocument(lux, "/community/", CommunityOverview.new(), [])
67
+ }
68
+ }
69
+ }
70
+
71
+ buildMain(system: BuildSystem) {
72
+ let browser = system.compileForBrowser("Main.ff")
73
+ let assets = AssetSystem.create()
74
+ .addAssets("/js/", browser.assets())
75
+ .addAssets("/images/", system.packageAssets().asset("/firefly-logo-notext.png"))
76
+ system.setAssets(assets)
77
+ }
78
+
79
+ serveGuideHtml(title: String, request: WebRequest[WebResponse]): Unit {
80
+ request.writeHeader("Content-Type", "text/html; charset=UTF-8")
81
+ request.writeText("<!doctype html>")
82
+ request.writeText("<html lang='en' style='background-color: #303236; color: #dadada; width: 100%; height: 100%; color-scheme: dark;'>")
83
+ request.writeText("<head>")
84
+ request.writeText("<title>" + title + "</title>")
85
+ request.writeText("<meta name='viewport' content='width=device-width, initial-scale=1.0'>")
86
+ request.writeText("</head>")
87
+ request.writeText("<body style='margin: 0; padding: 0; width: 100%; height: 100%'>")
88
+ request.writeText("<div id='main'></div>")
89
+ request.writeText("<script type='module' src='/js/ff/fireflysite/Main.mjs'></script>")
90
+ request.writeText("</body>")
91
+ request.writeText("</html>")
92
+ }
@@ -0,0 +1,86 @@
1
+ import Guide
2
+ import Lux from ff:lux
3
+ import LuxEvent from ff:lux
4
+
5
+ name = "Matching passwords"
6
+
7
+ new(): Demo {
8
+ Demo(
9
+ name
10
+ {render(_)}
11
+ )
12
+ }
13
+
14
+ render(lux: Lux): Unit {
15
+ function passwordInput(password: String, setPassword: String => Unit) {
16
+ lux.div {
17
+ lux.input {
18
+ lux.set("type", "password")
19
+ lux.set("autocomplete", "new-password")
20
+ lux.setValue(password)
21
+ lux.onInput {event =>
22
+ setPassword(event.text())
23
+ }
24
+ }
25
+ }
26
+ }
27
+ lux.form {
28
+ lux.useState(""): password1, setPassword1 =>
29
+ lux.useState(""): password2, setPassword2 =>
30
+ passwordInput(password1, setPassword1)
31
+ passwordInput(password2, setPassword2)
32
+ if(password2.size() != 0) {
33
+ lux.text(
34
+ if(password1 == password2) {
35
+ "Passwords match!"
36
+ } else {
37
+ "Passwords don't match."
38
+ }
39
+ )
40
+ }
41
+ }
42
+ }
43
+
44
+ newDocument(): Document {
45
+ Document([
46
+ Section(name, [
47
+ Paragraph([Text("Two passwords fields and a check that they match.")])
48
+ ])
49
+ Section("Demo", [
50
+ LuxDemo(name)
51
+ ])
52
+ Section("Code", [
53
+ CodeBlock("""
54
+ render(lux: Lux): Unit {
55
+ function passwordInput(password: String, setPassword: String => Unit) {
56
+ lux.div {
57
+ lux.input {
58
+ lux.set("type", "password")
59
+ lux.set("autocomplete", "new-password")
60
+ lux.setValue(password)
61
+ lux.onInput {event =>
62
+ setPassword(event.text())
63
+ }
64
+ }
65
+ }
66
+ }
67
+ lux.form {
68
+ lux.useState(""): password1, setPassword1 =>
69
+ lux.useState(""): password2, setPassword2 =>
70
+ passwordInput(password1, setPassword1)
71
+ passwordInput(password2, setPassword2)
72
+ if(password2.size() != 0) {
73
+ lux.text(
74
+ if(password1 == password2) {
75
+ "Passwords match!"
76
+ } else {
77
+ "Passwords don't match."
78
+ }
79
+ )
80
+ }
81
+ }
82
+ }
83
+ """, firefly = True)
84
+ ])
85
+ ])
86
+ }
@@ -0,0 +1,49 @@
1
+ import Guide
2
+ import CountingButtonDemo
3
+ import MatchingPasswordsDemo
4
+
5
+ new(): Document {
6
+ Document([
7
+ Section("Packages", [
8
+ Paragraph([
9
+ Text("Here you'll eventually find the package index.")
10
+ Text("For now, here's a short list of essential packages.")
11
+ ])
12
+ ])
13
+ Section("ff:core", [
14
+ Paragraph([
15
+ Text("The standard library of Firefly.")
16
+ ])
17
+ ])
18
+ Section("ff:webserver", [
19
+ Paragraph([
20
+ Text("A webserver package. Has HTTPS and WebSocket support.")
21
+ ])
22
+ ])
23
+ Section("ff:websocket", [
24
+ Paragraph([
25
+ Text("A package for making websocket connections.")
26
+ ])
27
+ ])
28
+ Section("ff:postgresql", [
29
+ Paragraph([
30
+ Text("A package for connecting to PostgreSQL. Has connection pool support.")
31
+ ])
32
+ ])
33
+ Section("ff:lux", [
34
+ Paragraph([
35
+ Text("A package for building interactive web interfaces.")
36
+ ])
37
+ ])
38
+ Section("ff:rpc", [
39
+ Paragraph([
40
+ Text("A package for type safe remote procedure calls.")
41
+ ])
42
+ ])
43
+ Section("ff:unsafejs", [
44
+ Paragraph([
45
+ Text("A FFI package for JavaScript.")
46
+ ])
47
+ ])
48
+ ])
49
+ }
@@ -0,0 +1,306 @@
1
+ import Css from ff:lux
2
+
3
+ mobileMediaQuery = "@media only screen and (max-width: 600px)"
4
+ desktopMediaQuery = "@media only screen and (min-width: 1500px)"
5
+
6
+ pageCss: CssClass = CssClass(
7
+ [
8
+ Css.display("flex")
9
+ Css.flexDirection("column")
10
+ Css.minHeight("100vh")
11
+ Css.fontFamily("'Helvetica Neue', Helvetica, Arial, sans-serif")
12
+ Css.textRendering("optimizeLegibility")
13
+ ]
14
+ [
15
+ CssNest("*:focus-visible", [
16
+ Css.outline("2px solid #4fc1ff")
17
+ Css.outlineOffset("2px")
18
+ ], [])
19
+ ]
20
+ []
21
+ )
22
+
23
+ whiteLinkCss: CssClass = CssClass(
24
+ [
25
+ Css.color("#dadada")
26
+ Css.textDecoration("none")
27
+ ]
28
+ [
29
+ CssNest("&:hover", [Css.textDecoration("underline")], [])
30
+ CssNest("&[aria-current='page']", [Css.color("#4ec9b0")], [])
31
+ ]
32
+ []
33
+ )
34
+
35
+ greenLinkCss: CssClass = CssClass(
36
+ [
37
+ Css.color("#4ec9b0")
38
+ Css.textDecoration("none")
39
+ ]
40
+ [
41
+ CssNest("&:hover", [Css.textDecoration("underline")], [])
42
+ ]
43
+ []
44
+ )
45
+
46
+ topbarCss: CssClass = CssClass(
47
+ [
48
+ Css.display("flex")
49
+ Css.gap("30px")
50
+ Css.paddingLeft("20px")
51
+ Css.paddingRight("30px")
52
+ Css.lineHeight("49px")
53
+ Css.fontSize("17px")
54
+ Css.backgroundColor("#34373d")
55
+ Css.borderBottom("1px solid #2b2d30")
56
+ Css.boxSizing("border-box")
57
+ ]
58
+ [
59
+ CssNest(mobileMediaQuery, [
60
+ Css.paddingLeft("20px")
61
+ Css.paddingRight("20px")
62
+ Css.gap("10px")
63
+ Css.fontSize("15px")
64
+ Css.overflowX("auto")
65
+ Css.justifyContent("space-between")
66
+ ], [])
67
+ ]
68
+ []
69
+ )
70
+
71
+ topbarFireflyCss: CssClass = CssClass(
72
+ [
73
+ Css.marginRight("auto")
74
+ Css.fontSize("19px")
75
+ ]
76
+ [
77
+ CssNest(mobileMediaQuery, [
78
+ Css.fontSize("15px")
79
+ ], [])
80
+ ]
81
+ []
82
+ )
83
+
84
+ searchInputCss: CssClass = CssClass(
85
+ [
86
+ Css.appearance("none")
87
+ Css.boxSizing("border-box")
88
+ Css.marginLeft("15px")
89
+ Css.marginRight("25px")
90
+ Css.width("calc(100% - 15px - 25px)")
91
+ Css.display("flex")
92
+ Css.border("none")
93
+ Css.backgroundColor("#34373d")
94
+ Css.color("#dadada")
95
+ Css.height("34px")
96
+ Css.fontSize("15px")
97
+ Css.borderRadius("17px")
98
+ Css.paddingLeft("15px")
99
+ Css.paddingRight("15px")
100
+ Css.paddingTop("0")
101
+ Css.paddingBottom("0")
102
+ Css.marginBottom("20px")
103
+ ]
104
+ [
105
+ CssNest("&::placeholder", [
106
+ Css.color("#dadada")
107
+ Css.opacity("0.7")
108
+ ], [])
109
+ ]
110
+ []
111
+ )
112
+
113
+ guideCss: CssClass = CssClass(
114
+ [
115
+ Css.display("flex")
116
+ ]
117
+ [
118
+ CssNest(mobileMediaQuery, [
119
+ Css.flexDirection("column")
120
+ ], [])
121
+ ]
122
+ []
123
+ )
124
+
125
+ guideSidebarCss: CssClass = CssClass(
126
+ [
127
+ Css.width("300px")
128
+ Css.minHeight("calc(100vh - 50px)")
129
+ Css.paddingTop("20px")
130
+ Css.backgroundColor("#2b2d30")
131
+ Css.boxSizing("border-box")
132
+ ]
133
+ [
134
+ CssNest(mobileMediaQuery, [
135
+ Css.order("2")
136
+ Css.width("100%")
137
+ Css.height("auto")
138
+ Css.paddingTop("20px")
139
+ Css.paddingBottom("20px")
140
+ ], [])
141
+ ]
142
+ []
143
+ )
144
+
145
+ guideSidebarUlCss: CssClass = CssClass(
146
+ [
147
+ Css.display("flex")
148
+ Css.flexDirection("column")
149
+ Css.gap("25px")
150
+ Css.listStyle("none")
151
+ Css.margin("0px")
152
+ Css.padding("20px")
153
+ ]
154
+ []
155
+ []
156
+ )
157
+
158
+ guideSidebarLiCss: CssClass = CssClass(
159
+ [
160
+ Css.listStyle("none")
161
+ ]
162
+ []
163
+ []
164
+ )
165
+
166
+ guideMainCss: CssClass = CssClass(
167
+ [
168
+ Css.flex("1")
169
+ Css.display("flex")
170
+ Css.justifyContent("center")
171
+ Css.padding("20px")
172
+ Css.paddingTop("70px")
173
+ Css.position("relative")
174
+ Css.boxSizing("content-box")
175
+ ]
176
+ [
177
+ CssNest(mobileMediaQuery, [
178
+ Css.paddingTop("30px")
179
+ ], [])
180
+ CssNest(desktopMediaQuery, [
181
+ Css.paddingRight("200px")
182
+ ], [])
183
+ ]
184
+ []
185
+ )
186
+
187
+ guideSoleDocumentCss: CssClass = CssClass(
188
+ [
189
+ Css.flex("1")
190
+ Css.display("flex")
191
+ Css.justifyContent("center")
192
+ Css.padding("20px")
193
+ Css.paddingTop("70px")
194
+ Css.position("relative")
195
+ Css.boxSizing("content-box")
196
+ ]
197
+ [
198
+ CssNest(mobileMediaQuery, [
199
+ Css.paddingTop("30px")
200
+ ], [])
201
+ ]
202
+ []
203
+ )
204
+
205
+ guideDocumentCss: CssClass = CssClass(
206
+ [
207
+ Css.maxWidth("800px")
208
+ Css.width("100%")
209
+ Css.fontSize("17px")
210
+ Css.lineHeight("1.7")
211
+ ]
212
+ [
213
+ CssNest(mobileMediaQuery, [
214
+ Css.fontSize("16px")
215
+ Css.lineHeight("1.6")
216
+ ], [])
217
+ ]
218
+ []
219
+ )
220
+
221
+ guideH1Css: CssClass = CssClass(
222
+ [
223
+ Css.fontSize("32px")
224
+ Css.marginTop("0px")
225
+ ]
226
+ [
227
+ CssNest(mobileMediaQuery, [
228
+ Css.fontSize("30px")
229
+ ], [])
230
+ ]
231
+ []
232
+ )
233
+
234
+ guideH2Css: CssClass = CssClass(
235
+ [
236
+ Css.fontSize("28px")
237
+ Css.marginTop("40px")
238
+ ]
239
+ [
240
+ CssNest(mobileMediaQuery, [
241
+ Css.fontSize("25px")
242
+ ], [])
243
+ ]
244
+ []
245
+ )
246
+
247
+ guideCodeCss: CssClass = CssClass(
248
+ [
249
+ Css.fontFamily("Consolas, 'Liberation Mono', Menlo, Courier, monospace")
250
+ Css.fontSize("17px")
251
+ Css.lineHeight("1.4")
252
+ Css.color("#4ec9b0")
253
+ ]
254
+ [
255
+ CssNest(mobileMediaQuery, [
256
+ Css.fontSize("16px")
257
+ ], [])
258
+ ]
259
+ []
260
+ )
261
+
262
+ guideCodeBlockCss: CssClass = CssClass(
263
+ [
264
+ Css.lineHeight("1.4")
265
+ Css.backgroundColor("#2b2d30")
266
+ Css.padding("12px 18px")
267
+ Css.paddingBottom("14px")
268
+ Css.border("1px solid #232323")
269
+ Css.borderRadius("5px")
270
+ Css.boxSizing("border-box")
271
+ Css.overflowX("auto")
272
+ Css.maxWidth("100%")
273
+ ]
274
+ [
275
+ CssNest(mobileMediaQuery, [
276
+ Css.maxWidth("calc(100% + 40px)")
277
+ Css.marginLeft("-20px")
278
+ Css.marginRight("-20px")
279
+ Css.borderRadius("0")
280
+ Css.borderLeft("none")
281
+ Css.borderRight("none")
282
+ Css.padding("12px 20px")
283
+ ], [])
284
+ ]
285
+ []
286
+ )
287
+
288
+ guideLinkCss: CssClass = CssClass(
289
+ [
290
+ Css.color("#4fc1ff")
291
+ Css.textDecoration("none")
292
+ ]
293
+ [
294
+ CssNest("&:hover", [Css.textDecoration("underline")], [])
295
+ ]
296
+ []
297
+ )
298
+
299
+ codeCommentCss: CssClass = CssClass([Css.color("#6a9955")], [], [])
300
+ codeStringCss: CssClass = CssClass([Css.color("#ce9178")], [], [])
301
+ codeNumberCss: CssClass = CssClass([Css.color("#b5cea8")], [], [])
302
+ codeKeywordCss: CssClass = CssClass([Css.color("#569cd6")], [], [])
303
+ codeTypeCss: CssClass = CssClass([Css.color("#4ec9b0")], [], [])
304
+ codeVariableCss: CssClass = CssClass([Css.color("#9cdcfe")], [], [])
305
+ codeCallCss: CssClass = CssClass([Css.color("#dcdcaa")], [], [])
306
+ codeOtherCss: CssClass = CssClass([Css.color("#cccccc")], [], [])
package/lux/Main2.ff CHANGED
@@ -92,9 +92,8 @@ rhymeComponent(lux: Lux, system: BrowserSystem) {
92
92
 
93
93
 
94
94
  rhyme(system: BrowserSystem, text: String): String {
95
- let result = system.httpClient().get("https://api.datamuse.com/words?rel_rhy=" + text, [])
96
- let json = system.js().parseJson(result.readText())
97
- if(json.get(0).isNullOrUndefined()) {"?"} else {json.get(0).get("word").grabString()}
95
+ let json = system.httpClient().get("https://api.datamuse.com/words?rel_rhy=" + text, []) {_.readJson()}
96
+ json.index(0).field("word").getString().else {"?"}
98
97
  }
99
98
 
100
99
  browserMain(system: BrowserSystem): Unit {
@@ -452,7 +452,7 @@ export function Json_field(self_, key_) {
452
452
 
453
453
  export function Json_index(self_, key_) {
454
454
 
455
- return typeof self_ === 'array' ? self_[key] ?? null : null;
455
+ return Array.isArray(self_) ? self_[key] ?? null : null;
456
456
 
457
457
  }
458
458
 
@@ -471,7 +471,7 @@ export function Json_getField(self_, key_) {
471
471
 
472
472
  export function Json_getIndex(self_, key_) {
473
473
 
474
- return typeof self_ === 'array' ? ff_core_Option.Some(self_[key_] ?? null) : ff_core_Option.None();
474
+ return Array.isArray(self_) ? ff_core_Option.Some(self_[key_] ?? null) : ff_core_Option.None();
475
475
 
476
476
  }
477
477
 
@@ -329,6 +329,16 @@ export function String_all(self_, body_) {
329
329
 
330
330
  }
331
331
 
332
+ export function String_filter(self_, body_) {
333
+
334
+ const result = [];
335
+ for(let i = 0; i < self_.length; i++) {
336
+ if(body_(self_.charCodeAt(i))) result.push(self_.charAt(i));
337
+ }
338
+ return result.join("");
339
+
340
+ }
341
+
332
342
  export function String_toBuffer(self_) {
333
343
 
334
344
  const encoded = new TextEncoder().encode(self_)
@@ -510,6 +520,16 @@ export async function String_all$(self_, body_, $task) {
510
520
 
511
521
  }
512
522
 
523
+ export async function String_filter$(self_, body_, $task) {
524
+
525
+ const result = [];
526
+ for(let i = 0; i < self_.length; i++) {
527
+ if(await body_(self_.charCodeAt(i))) result.push(self_.charAt(i));
528
+ }
529
+ return result.join("");
530
+
531
+ }
532
+
513
533
  export async function String_toBuffer$(self_, $task) {
514
534
  throw new Error('Function String_toBuffer is missing on this target in async context.');
515
535
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "description": "Firefly compiler",
5
5
  "author": "Firefly team",
6
6
  "license": "MIT",
7
- "version": "0.4.77",
7
+ "version": "0.4.78",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"
@@ -4,7 +4,7 @@
4
4
  "description": "Firefly language support",
5
5
  "author": "Firefly team",
6
6
  "license": "MIT",
7
- "version": "0.4.77",
7
+ "version": "0.4.78",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"