firefly-compiler 0.5.46 → 0.5.47

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/Buffer.ff CHANGED
@@ -193,7 +193,7 @@ fromHex(hex: String): Buffer {
193
193
 
194
194
  fromBase64(base64: String): Buffer {
195
195
  let binaryString = Js->atob(base64)
196
- let bytes = Js->Uint8Array->from(binaryString, Js->{char => char->CharCodeAt(0)})
196
+ let bytes = Js->Uint8Array->from(binaryString, Js->{char => char->charCodeAt(0)})
197
197
  Js->DataView->(bytes->buffer)?
198
198
  }
199
199
 
@@ -0,0 +1,3 @@
1
+ package ff:site:0.0.0
2
+ dependency ff:webserver:0.0.0
3
+ dependency ff:lux:0.0.0
@@ -0,0 +1,26 @@
1
+ import Lux from ff:lux
2
+ import WebServer from ff:webserver
3
+ import RouteTools
4
+
5
+ data Chat(room: String)
6
+
7
+ pageRoute = PageRoute("ChatRoute", "/chat/{roomId}")
8
+
9
+ handle(system: NodeSystem, request: WebRequest[WebResponse], context: Context) {
10
+ let pageData = Chat("42")
11
+ let title = "Chat"
12
+ RouteTools.renderAndServe(system, pageRoute, pageData, title, request, {render(_, pageData)})
13
+ }
14
+
15
+ browserMain(system: BrowserSystem) {
16
+ RouteTools.renderToMain(system, {render(_, _)})
17
+ }
18
+
19
+ render(lux: Lux, chat: Chat) {
20
+ lux.add("div") {
21
+ lux.add("h1") {
22
+ lux.text("Hello")
23
+ }
24
+ }
25
+ }
26
+
@@ -0,0 +1,40 @@
1
+ import WebServer from ff:webserver
2
+ import RouteTools
3
+ import Routes
4
+
5
+ nodeMain(system: NodeSystem): Unit {
6
+ let host = system.arguments().grab(0)
7
+ let port = system.arguments().grab(1).grabInt()
8
+ let handlers = RouteTools.checkRoutesAndHandlers(Routes.routes, Routes.handlers())
9
+ system.writeLine("Listening on " + host + ":" + port)
10
+ WebServer.new(system, host, port).listen {request =>
11
+ let path = request.readPath()
12
+ let segments = path.split('/').filter {s => s != "" && s != "." && s != ".."}
13
+ segments.{
14
+ | ["js", ...] =>
15
+ let asset = segments.map {"/" + _}.join()
16
+ request.writeHeader("Content-Type", "text/javascript; charset=UTF-8")
17
+ request.writeStream(system.assets().readStream(asset))
18
+ | _ =>
19
+ // Very sketchy routing
20
+ let routeOption = Routes.routes.find {route =>
21
+ let routeSegments = route.pattern.split('/').filter {s => s != "" && s != "." && s != ".."}
22
+ if(segments.size() != routeSegments.size()) {False} else:
23
+ segments.zip(routeSegments).all {| Pair(s, r) => s == r || r.startsWith("{")}
24
+ }
25
+ routeOption.or {request.writeStatus("404 Not Found")}: route =>
26
+ let context = Context()
27
+ let handle = handlers.grab(route.pattern)
28
+ handle(system, request, context)
29
+ }
30
+ }
31
+ }
32
+
33
+ buildMain(system: BuildSystem) {
34
+ mutable assets = AssetSystem.create()
35
+ Routes.routes.each {route =>
36
+ let browser = system.compileForBrowser(route.page + ".ff") // .bundle()
37
+ assets = assets.addAssets("/js/", browser.assets())
38
+ }
39
+ system.setAssets(assets)
40
+ }
@@ -0,0 +1,70 @@
1
+ import WebServer from ff:webserver
2
+ import Lux from ff:lux
3
+
4
+ data PageRoute(page: String, pattern: String)
5
+ capability PageHandler(pattern: String, handle: (NodeSystem, WebRequest[WebResponse], Context) => Unit)
6
+
7
+ data Context()
8
+
9
+ serveHtml(
10
+ pageRoute: PageRoute
11
+ title: String
12
+ pageData: Buffer
13
+ contentHtml: String
14
+ styleTags: String
15
+ request: WebRequest[WebResponse]
16
+ ): Unit {
17
+ request.writeHeader("Content-Type", "text/html; charset=UTF-8")
18
+ request.writeText("<!doctype html>")
19
+ request.writeText("<html lang='en' style='background-color: #ffffff; color: #333333; width: 100%; height: 100%; color-scheme: light;'>")
20
+ request.writeText("<head>")
21
+ request.writeText("<title>" + title + "</title>")
22
+ request.writeText("<meta name='viewport' content='width=device-width, initial-scale=1.0'>")
23
+ request.writeText("<meta name='theme-color' content='#ecc45e'>")
24
+ request.writeText("<script type='module' src='" + "/js/ff/site/" + pageRoute.page + ".mjs" + "'></script>")
25
+ request.writeText("<script type='text/plain' id='data'>" + pageData.toBase64() + "</script>")
26
+ request.writeText(styleTags)
27
+ request.writeText("</head>")
28
+ request.writeText("<body style='margin: 0; padding: 0; width: 100%; height: 100%; touch-action: manipulation;'>")
29
+ request.writeText("<div id='main'>" + contentHtml + "</div>")
30
+ request.writeText("</body>")
31
+ request.writeText("</html>")
32
+ }
33
+
34
+ renderAndServe[T: Serializable](
35
+ system: NodeSystem
36
+ pageRoute: PageRoute
37
+ pageData: T
38
+ title: String
39
+ request: WebRequest[WebResponse]
40
+ render: Lux => Unit
41
+ ) {
42
+ let serialized = Serializable.serialize(pageData)
43
+ let htmlAndCss = Lux.renderToString(system, render)
44
+ serveHtml(pageRoute, title, serialized, htmlAndCss.first, htmlAndCss.second, request)
45
+ }
46
+
47
+ renderToMain[T: Serializable](system: BrowserSystem, render: (Lux, T) => Unit) {
48
+ let base64 = system.js()->document->getElementById("data")->textContent.grabString()
49
+ let chat = Serializable.deserialize(Buffer.fromBase64(base64))
50
+ Lux.renderById(system, "main") {lux =>
51
+ render(lux, chat)
52
+ }
53
+ }
54
+
55
+ checkRoutesAndHandlers(
56
+ routes: List[PageRoute]
57
+ handlers: List[PageHandler]
58
+ ): Map[String, (NodeSystem, WebRequest[WebResponse], Context) => Unit] {
59
+ let handlerMap = handlers.map {h => Pair(h.pattern, h.handle)}.toMap()
60
+ routes.each {r =>
61
+ if(!handlerMap.contains(r.pattern)) {
62
+ panic("Missing handler for " + r.pattern)
63
+ }
64
+ }
65
+ let missingPatterns = handlerMap.keys().removeAll(routes.map {_.pattern}.toSet())
66
+ missingPatterns.each {p =>
67
+ panic("Missing route for " + p)
68
+ }
69
+ handlerMap
70
+ }
@@ -0,0 +1,13 @@
1
+ import WebServer from ff:webserver
2
+ import RouteTools
3
+ import ChatRoute
4
+
5
+ routes: List[PageRoute] = [
6
+ ChatRoute.pageRoute
7
+ ]
8
+
9
+ handlers(): List[PageHandler] {
10
+ [
11
+ PageHandler(ChatRoute.pageRoute.pattern, {ChatRoute.handle(_, _, _)})
12
+ ]
13
+ }
@@ -0,0 +1,3 @@
1
+ package ff:site2:0.0.0
2
+ dependency ff:webserver:0.0.0
3
+ dependency ff:lux:0.0.0
@@ -0,0 +1,27 @@
1
+ import Lux from ff:lux
2
+ import WebServer from ff:webserver
3
+ import Router
4
+ import Html
5
+
6
+ routeModule = SourceLocation.here().module()
7
+
8
+ data Chat(room: String)
9
+
10
+ handle(request: WebRequest[WebResponse], context: RouteContext, roomId: Int) {
11
+ let pageData = Chat("42")
12
+ let title = "Chat"
13
+ Html.renderAndServe(context.system, routeModule, pageData, title, request, {render(_, pageData)})
14
+ }
15
+
16
+ browserMain(system: BrowserSystem) {
17
+ Html.renderToMain(system, {render(_, _)})
18
+ }
19
+
20
+ render(lux: Lux, chat: Chat) {
21
+ lux.add("div") {
22
+ lux.add("h1") {
23
+ lux.text("Hello")
24
+ }
25
+ }
26
+ }
27
+
@@ -0,0 +1,49 @@
1
+ import WebServer from ff:webserver
2
+ import WebRoute from ff:webserver
3
+ import Lux from ff:lux
4
+
5
+ serveHtml(
6
+ moduleName: String
7
+ title: String
8
+ pageData: Buffer
9
+ contentHtml: String
10
+ styleTags: String
11
+ request: WebRequest[WebResponse]
12
+ ): Unit {
13
+ request.writeHeader("Content-Type", "text/html; charset=UTF-8")
14
+ request.writeText("<!doctype html>")
15
+ request.writeText("<html lang='en' style='background-color: #ffffff; color: #333333; width: 100%; height: 100%; color-scheme: light;'>")
16
+ request.writeText("<head>")
17
+ request.writeText("<title>" + title + "</title>")
18
+ request.writeText("<meta name='viewport' content='width=device-width, initial-scale=1.0'>")
19
+ request.writeText("<meta name='theme-color' content='#ecc45e'>")
20
+ request.writeText("<script type='module' src='" + "/js/ff/site2/" + moduleName + ".mjs" + "'></script>")
21
+ request.writeText("<script type='text/plain' id='data'>" + pageData.toBase64() + "</script>")
22
+ request.writeText(styleTags)
23
+ request.writeText("</head>")
24
+ request.writeText("<body style='margin: 0; padding: 0; width: 100%; height: 100%; touch-action: manipulation;'>")
25
+ request.writeText("<div id='main'>" + contentHtml + "</div>")
26
+ request.writeText("</body>")
27
+ request.writeText("</html>")
28
+ }
29
+
30
+ renderAndServe[T: Serializable](
31
+ system: NodeSystem
32
+ moduleName: String
33
+ pageData: T
34
+ title: String
35
+ request: WebRequest[WebResponse]
36
+ render: Lux => Unit
37
+ ) {
38
+ let serialized = Serializable.serialize(pageData)
39
+ let htmlAndCss = Lux.renderToString(system, render)
40
+ serveHtml(moduleName, title, serialized, htmlAndCss.first, htmlAndCss.second, request)
41
+ }
42
+
43
+ renderToMain[T: Serializable](system: BrowserSystem, render: (Lux, T) => Unit) {
44
+ let base64 = system.js()->document->getElementById("data")->textContent.grabString()
45
+ let chat = Serializable.deserialize(Buffer.fromBase64(base64))
46
+ Lux.renderById(system, "main") {lux =>
47
+ render(lux, chat)
48
+ }
49
+ }
@@ -0,0 +1,37 @@
1
+ import WebServer from ff:webserver
2
+ import WebRoute from ff:webserver
3
+ import Html
4
+ import Router
5
+
6
+ nodeMain(system: NodeSystem): Unit {
7
+ let host = system.arguments().grab(0)
8
+ let port = system.arguments().grab(1).grabInt()
9
+ let context = RouteContext(system)
10
+ let handlers = WebRouteHandler(Array.new())
11
+ Router.handlers(handlers)
12
+ system.writeLine("Listening on " + host + ":" + port)
13
+ WebServer.new(system, host, port).listen {request =>
14
+ let path = request.readPath()
15
+ let segments = path.split('/').filter {s => s != "" && s != "." && s != ".."}
16
+ segments.{
17
+ | ["js", ...] =>
18
+ let asset = segments.map {"/" + _}.join()
19
+ request.writeHeader("Content-Type", "text/javascript; charset=UTF-8")
20
+ request.writeStream(system.assets().readStream(asset))
21
+ | _ =>
22
+ if(!handlers.handle(request, context)) {
23
+ request.writeStatus("404 Not Found")
24
+ }
25
+ Unit
26
+ }
27
+ }
28
+ }
29
+
30
+ buildMain(system: BuildSystem) {
31
+ mutable assets = AssetSystem.create()
32
+ Router.modulesWithBrowserMain.each {moduleName =>
33
+ let browser = system.compileForBrowser(moduleName + ".ff") // .bundle()
34
+ assets = assets.addAssets("/js/", browser.assets())
35
+ }
36
+ system.setAssets(assets)
37
+ }
@@ -0,0 +1,23 @@
1
+ import WebServer from ff:webserver
2
+ import WebRoute from ff:webserver
3
+ import ChatRoomRoute
4
+
5
+ chatRoomRoute: WebRoute1[Int] = WebRoute.new1(
6
+ "/chat/{roomId}"
7
+ )
8
+ chatRoomUsersRoute: WebRoute1[Int] = WebRoute.new1(
9
+ "/chat/{roomId}/users"
10
+ )
11
+ chatRoomUserRoute: WebRoute2[Int, Int] = WebRoute.new2(
12
+ "/chat/{roomId}/users/{userId}"
13
+ )
14
+
15
+ modulesWithBrowserMain: List[String] = [
16
+ ChatRoomRoute.routeModule
17
+ ]
18
+
19
+ handlers(handler: WebRouteHandler[RouteContext]) {
20
+ handler.add1(chatRoomRoute, ChatRoomRoute.handle)
21
+ }
22
+
23
+ capability RouteContext(system: NodeSystem)
@@ -140,7 +140,7 @@ return (new DataView(arrayBuffer_))
140
140
  export function fromBase64_(base64_) {
141
141
  const binaryString_ = atob(base64_);
142
142
  const bytes_ = Uint8Array.from(binaryString_, ((char_) => {
143
- return char_.CharCodeAt(0)
143
+ return char_.charCodeAt(0)
144
144
  }));
145
145
  return (new DataView(bytes_.buffer))
146
146
  }
@@ -186,7 +186,7 @@ return (new DataView(arrayBuffer_))
186
186
  export async function fromBase64_$(base64_, $task) {
187
187
  const binaryString_ = atob(base64_);
188
188
  const bytes_ = Uint8Array.from(binaryString_, ((char_) => {
189
- return char_.CharCodeAt(0)
189
+ return char_.charCodeAt(0)
190
190
  }));
191
191
  return (new DataView(bytes_.buffer))
192
192
  }
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.5.46",
7
+ "version": "0.5.47",
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.5.46",
7
+ "version": "0.5.47",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"
@@ -0,0 +1,150 @@
1
+ import WebServer
2
+
3
+ trait P: WebParameter {
4
+ toRouteParameter(text: String): Option[P]
5
+ fromRouteParameter(value: P): String
6
+ }
7
+
8
+ instance String: WebParameter {
9
+ toRouteParameter(text: String): Option[String] {Some(text)} // TODO: Unescape
10
+ fromRouteParameter(value: String): String {value} // TODO: Escape
11
+ }
12
+
13
+ instance Int: WebParameter {
14
+ toRouteParameter(text: String): Option[Int] {text.getInt()}
15
+ fromRouteParameter(value: Int): String {"" + value}
16
+ }
17
+
18
+ data WebRoute0(urlPattern: String, segments: List[String])
19
+ data WebRoute1[P1](urlPattern: String, segments: List[String])
20
+ data WebRoute2[P1, P2](urlPattern: String, segments: List[String])
21
+
22
+ new0(urlPattern: String): WebRoute0 {
23
+ WebRoute0(urlPattern, urlPattern.split('/').filter {s => s != ""})
24
+ }
25
+
26
+ new1[P1](urlPattern: String): WebRoute1[P1] {
27
+ WebRoute1(urlPattern, urlPattern.split('/').filter {s => s != ""})
28
+ }
29
+
30
+ new2[P1, P2](urlPattern: String): WebRoute2[P1, P2] {
31
+ WebRoute2(urlPattern, urlPattern.split('/').filter {s => s != ""})
32
+ }
33
+
34
+ extend self: WebRoute0 {
35
+ toUrl(): String {
36
+ self.urlPattern
37
+ }
38
+ }
39
+
40
+ extend self[P1: WebParameter]: WebRoute1[P1] {
41
+ toUrl(p1: P1): String {
42
+ mutable result = ""
43
+ mutable parameter = 1
44
+ self.segments.each {r =>
45
+ result += "/"
46
+ if(r.startsWith("{")) {
47
+ if(parameter == 1) {result += fromRouteParameter(p1)}
48
+ parameter += 1
49
+ } else {
50
+ result += r
51
+ }
52
+ }
53
+ if(self.urlPattern.endsWith("/")) {result += "/"}
54
+ result
55
+ }
56
+ }
57
+
58
+ extend self[P1: WebParameter, P2: WebParameter]: WebRoute2[P1, P2] {
59
+ toUrl(p1: P1, p2: P2): String {
60
+ mutable result = ""
61
+ mutable parameter = 0
62
+ self.segments.each {r =>
63
+ result += "/"
64
+ if(r.startsWith("{")) {
65
+ if(parameter == 1) {result += fromRouteParameter(p1)}
66
+ if(parameter == 2) {result += fromRouteParameter(p2)}
67
+ parameter += 1
68
+ } else {
69
+ result += r
70
+ }
71
+ }
72
+ if(self.urlPattern.endsWith("/")) {result += "/"}
73
+ result
74
+ }
75
+ }
76
+
77
+ capability WebRouteHandler[C](handlers: Array[(WebRequest[WebResponse], List[String], C) => Bool])
78
+
79
+ extend self[C]: WebRouteHandler[C] {
80
+
81
+ add0(route: WebRoute0, handle: (WebRequest[WebResponse], C) => Unit) {
82
+ self.handlers.push {request, segments, context =>
83
+ if(segments != route.segments) {False} else:
84
+ handle(request, context)
85
+ True
86
+ }
87
+ }
88
+
89
+ add1[P1: WebParameter](route: WebRoute1[P1], handle: (WebRequest[WebResponse], C, P1) => Unit) {
90
+ self.handlers.push {request, segments, context =>
91
+ if(segments.size() != route.segments.size()) {False} else:
92
+ let matching = True
93
+ mutable p1 = None
94
+ segments.zip(route.segments).eachWhile {| Pair(s, r) =>
95
+ if(r.startsWith("{")) {
96
+ if(p1.isEmpty()) {
97
+ p1 = toRouteParameter(s)
98
+ !p1.isEmpty()
99
+ } else {
100
+ False
101
+ }
102
+ } else {
103
+ r == s
104
+ }
105
+ }
106
+ if(!matching) {False} else:
107
+ handle(request, context, p1.grab())
108
+ True
109
+ }
110
+ }
111
+
112
+ add2[P1: WebParameter, P2: WebParameter](route: WebRoute2[P1, P2], handle: (WebRequest[WebResponse], C, P1, P2) => Unit) {
113
+ self.handlers.push {request, segments, context =>
114
+ if(segments.size() != route.segments.size()) {False} else:
115
+ mutable p1 = None
116
+ mutable p2 = None
117
+ mutable matching = True
118
+ segments.zip(route.segments).eachWhile {| Pair(s, r) =>
119
+ matching = if(r.startsWith("{")) {
120
+ if(p1.isEmpty()) {
121
+ p1 = toRouteParameter(s)
122
+ !p1.isEmpty()
123
+ } elseIf {p2.isEmpty()} {
124
+ p2 = toRouteParameter(s)
125
+ !p2.isEmpty()
126
+ } else {
127
+ False
128
+ }
129
+ } else {
130
+ r == s
131
+ }
132
+ matching
133
+ }
134
+ if(!matching) {False} else:
135
+ handle(request, context, p1.grab(), p2.grab())
136
+ True
137
+ }
138
+ }
139
+
140
+ handle(request: WebRequest[WebResponse], context: C): Bool {
141
+ let segments = request.readPath().split('/').filter {s => s != ""}
142
+ mutable handled = False
143
+ self.handlers.eachWhile {| handler =>
144
+ handled = handler(request, segments, context)
145
+ !handled
146
+ }
147
+ handled
148
+ }
149
+
150
+ }