firefly-compiler 0.5.46 → 0.5.48

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "editor.suggest.showWords": false,
3
- "firefly.trace.server": "messages",
3
+ "firefly.trace.server": "verbose",
4
4
  "editor.trimAutoWhitespace": false
5
5
  }
@@ -136,7 +136,15 @@ check(
136
136
 
137
137
  packages.filter {!_.files.isEmpty()}.each {package =>
138
138
  let firstFile = package.files.grabFirst()
139
- let resolvedDependencies = Dependencies.process(system.httpClient(), dependencyLock, firstFile)
139
+ try {
140
+ Some(Dependencies.process(system.httpClient(), dependencyLock, firstFile))
141
+ } tryCatch {| CompileError(_, _) @ c, error =>
142
+ errors.push(c)
143
+ None
144
+ } catch {| CompileErrors(compileErrors), error =>
145
+ errors.pushList(compileErrors)
146
+ None
147
+ }.each: resolvedDependencies =>
140
148
  let fixedPackagePaths = if(resolvedDependencies.packagePaths.contains(PackagePair("ff", "core"))) {
141
149
  resolvedDependencies.packagePaths
142
150
  } else {
@@ -24,7 +24,7 @@ extend self: Dependencies {
24
24
  loadPackageInfo(
25
25
  packagePair: PackagePair
26
26
  path: Path
27
- ): PackageInfo {
27
+ ): Option[PackageInfo] {
28
28
  let packageDirectory = if(path.extension() == ".ff") {path.parent().grab()} else {path}
29
29
  let sharedPackageFile = packageDirectory.slash(".firefly").slash("package.ff")
30
30
  let packageFile = if(sharedPackageFile.exists()) {
@@ -33,8 +33,8 @@ extend self: Dependencies {
33
33
  self.singleFilePackages = self.singleFilePackages.add(packagePair)
34
34
  path
35
35
  }
36
- let code = packageFile.readText()
37
- self.parsePackageFile(packagePair, packageFile.relativeTo(path), code)
36
+ try {packageFile.readText()}.toOption().map: code =>
37
+ self.parsePackageFile(packagePair, packageFile.absolute(), code)
38
38
  }
39
39
 
40
40
  parsePackageFile(
@@ -93,7 +93,7 @@ extend self: Dependencies {
93
93
  Log.trace("Fetching " + location)
94
94
  let buffer = httpClient.get(location, []) {response =>
95
95
  if(!response.ok()) {
96
- panic("Could not download dependency: " + location)
96
+ throw(CompileError(dependency.at, "Could not download dependency: " + location))
97
97
  }
98
98
  response.readBuffer()
99
99
  }
@@ -109,7 +109,7 @@ extend self: Dependencies {
109
109
  }
110
110
  dependencyPath
111
111
  } else {
112
- panic("Loading packages by this protocol is not supported: " + location)
112
+ throw(CompileError(dependency.at, "Loading packages by this protocol is not supported: " + location))
113
113
  }
114
114
  } else {
115
115
  path.path(location)
@@ -126,7 +126,9 @@ extend self: Dependencies {
126
126
  let packageInfos = dependencies.map {dependency =>
127
127
  let dependencyPath = self.fetchDependency(path, httpClient, dependencyLock, dependency)
128
128
  self.packagePaths = self.packagePaths.add(dependency.packagePair, dependencyPath)
129
- let packageInfo = self.loadPackageInfo(dependency.packagePair, dependencyPath)
129
+ let packageInfo = self.loadPackageInfo(dependency.packagePair, dependencyPath).else {
130
+ throw(CompileError(dependency.at, "Dependency not found: " + dependencyPath.absolute()))
131
+ }
130
132
  checkPackagePairs(dependency.packagePair, packageInfo.package.packagePair)
131
133
  packageInfo
132
134
  }
@@ -141,7 +143,9 @@ extend self: Dependencies {
141
143
  process(fetch: HttpClient, dependencyLock: DependencyLock, path: Path): ResolvedDependencies {
142
144
  let workspace = Workspace.loadWorkspace(path)
143
145
  let self = Dependencies(workspace, [].toMap(), [].toMap(), [].toSet())
144
- let packageInfo = self.loadPackageInfo(PackagePair("script", "script"), path)
146
+ let packageInfo = self.loadPackageInfo(PackagePair("script", "script"), path).else {
147
+ panic("Not a main file: " + path.absolute())
148
+ }
145
149
  let newDependencies = self.processPackageInfo(packageInfo)
146
150
  self.processDependencies(path, fetch, dependencyLock, newDependencies)
147
151
  let packagePaths = self.packagePaths.add(packageInfo.package.packagePair, findScriptPackageLocation(path))
@@ -279,7 +279,7 @@ extend self: JsEmitter {
279
279
  }
280
280
  }
281
281
 
282
- emitTerm(term: Term, async: Bool): String {term.{
282
+ emitTerm(term: Term, async: Bool, ignored: Bool = False): String {term.{
283
283
  | EString(at, value) {value.startsWith("\"\"\"")} =>
284
284
  "`" + value.dropFirst(3).dropLast(3).replace("`", "\\`") + "`" // TODO: Fix escaping
285
285
  | EString(at, value) => value
@@ -347,6 +347,8 @@ extend self: JsEmitter {
347
347
  let c = if(await) {", $task"} else {""}
348
348
  let call = "(" + self.emitTerm(function, async) + ")(" + self.emitTerm(value, async) + c + ")"
349
349
  if(await) {"(await " + call + ")"} else {call}
350
+ | _ {self.emitAssignment(term, async) | Some(code)} =>
351
+ if(ignored) {code} else {"(" + code + ", void 0)"}
350
352
  | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
351
353
  self.emitSpecialCall(term, async, name, arguments.map {_.value}, dictionaries) | Some(code)
352
354
  } =>
@@ -379,14 +381,14 @@ extend self: JsEmitter {
379
381
  let call = functionCode + "(" + [...emittedArguments, ...ds, ...controller].join(", ") + ")"
380
382
  if(await) {"(await " + call + ")"} else {call}
381
383
  | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
382
- "(" + list.foldLeft(self.emitComma(elseBody, async)) {| otherwise, Pair(condition, body) =>
383
- self.emitComma(condition, async) +
384
- "\n? " + self.emitComma(body, async) + "\n: " + otherwise
384
+ "(" + list.foldLeft(self.emitTerm(elseBody, async)) {| otherwise, Pair(condition, body) =>
385
+ self.emitTerm(condition, async) +
386
+ "\n? " + self.emitTerm(body, async) + "\n: " + otherwise
385
387
  } + ")"
386
388
  | list =>
387
389
  "(" + list.foldLeft("ff_core_Option.None()") {| otherwise, Pair(condition, body) =>
388
- self.emitComma(condition, async) +
389
- "\n? ff_core_Option.Some(" + self.emitComma(body, async) + ")\n: " + otherwise
390
+ self.emitTerm(condition, async) +
391
+ "\n? ff_core_Option.Some(" + self.emitTerm(body, async) + ")\n: " + otherwise
390
392
  } + ")"
391
393
  }
392
394
  | ECall(at, DynamicCall(function, _), effect, typeArguments, arguments, dictionaries) =>
@@ -405,6 +407,24 @@ extend self: JsEmitter {
405
407
  | EWildcard(at, index) =>
406
408
  if(index == 0) {fail(at, "Unbound wildcard")}
407
409
  "_w" + index
410
+ | ESequential(_, ESequential(_, ESequential(_, before1, before2), before3), after) {
411
+ safeCommable(before1) && safeCommable(before2) && safeCommable(before3) && safeCommable(after)
412
+ } =>
413
+ "(" + self.emitTerm(before1, async, ignored = True) + ", " +
414
+ self.emitTerm(before2, async, ignored = True) + ", " +
415
+ self.emitTerm(before3, async, ignored = True) + ", " +
416
+ self.emitTerm(after, async, ignored) + ")"
417
+ | ESequential(_, ESequential(_, before1, before2), after) {
418
+ safeCommable(before1) && safeCommable(before2) && safeCommable(after)
419
+ } =>
420
+ "(" + self.emitTerm(before1, async, ignored = True) + ", " +
421
+ self.emitTerm(before2, async, ignored = True) + ", " +
422
+ self.emitTerm(after, async, ignored) + ")"
423
+ | ESequential(_, before, after) {
424
+ safeCommable(before) && safeCommable(after)
425
+ } =>
426
+ "(" + self.emitTerm(before, async, ignored = True) + ", " +
427
+ self.emitTerm(after, async, ignored) + ")"
408
428
  | _ {async} =>
409
429
  "(await (async function() {\n" + self.emitStatements(term, True, False, async) + "\n})())"
410
430
  | _ =>
@@ -450,11 +470,6 @@ extend self: JsEmitter {
450
470
  | ESequential(at, before, after) =>
451
471
  self.emitStatements(before, False, False, async) + ";\n" +
452
472
  self.emitStatements(after, last, break, async)
453
- | EAssign(at, operator, name, value) =>
454
- escapeKeyword(name) + " " + operator + "= " + self.emitTerm(value, async)
455
- | EAssignField(at, operator, record, field, value) =>
456
- self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
457
- self.emitTerm(value, async)
458
473
  | ECall(at, StaticCall(name, True, instanceCall), effect, _, arguments, _) =>
459
474
  if(instanceCall) {throw(CompileError(at, "Not yet implemented: Tail calls on trait methods."))}
460
475
  self.tailCallUsed = True
@@ -478,15 +493,17 @@ extend self: JsEmitter {
478
493
  self.emitCase(["_1"], c, [], [], True, last, break, lastCase, async)
479
494
  }.join("\n") +
480
495
  "\n}" + if(!last && !break) {" while(false)"}.else {""}
496
+ | _ {self.emitAssignment(term, async) | Some(code)} =>
497
+ code
481
498
  | _ =>
482
499
  detectIfElse(term).{
483
500
  | [] =>
484
501
  if(break) {
485
- "if(!" + self.emitComma(term, async) + ") break"
502
+ "if(!" + self.emitTerm(term, async) + ") break"
486
503
  } elseIf {last} {
487
504
  "return " + self.emitTerm(term, async)
488
505
  } else {
489
- self.emitTerm(term, async)
506
+ self.emitTerm(term, async, ignored = True)
490
507
  }
491
508
  | [Pair(EVariant(_, "ff:core/Bool.True", _, _), elseBody), ...list] =>
492
509
  let initial = "{\n" + self.emitStatements(elseBody, last, break, async) + "\n}"
@@ -508,6 +525,49 @@ extend self: JsEmitter {
508
525
  }
509
526
  }
510
527
 
528
+ emitAssignment(
529
+ term: Term
530
+ async: Bool
531
+ ): Option[String] {
532
+ | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries), _ =>
533
+ name.{
534
+ | "ff:core/JsValue.JsValue_set" {arguments.map {_.value} | [e1, e2, e3]} =>
535
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " = " + self.emitTerm(e3, async))
536
+ | "ff:core/JsValue.JsValue_increment" {arguments.map {_.value} | [e1, e2, e3]} =>
537
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " += " + self.emitTerm(e3, async))
538
+ | "ff:core/JsValue.JsValue_decrement" {arguments.map {_.value} | [e1, e2, e3]} =>
539
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " -= " + self.emitTerm(e3, async))
540
+ | "ff:core/JsSystem.JsSystem_set" {arguments.map {_.value} | [e1, EString(_, q), e3]} {
541
+ noSideEffects(e1)} {safeBare(q) | Some(s)
542
+ } =>
543
+ Some(s + " = " + self.emitTerm(e3, async))
544
+ | "ff:core/JsSystem.JsSystem_increment" {arguments.map {_.value} | [e1, EString(_, q), e3]} {
545
+ noSideEffects(e1)} {safeBare(q) | Some(s)
546
+ } =>
547
+ Some(s + " += " + self.emitTerm(e3, async))
548
+ | "ff:core/JsSystem.JsSystem_decrement" {arguments.map {_.value} | [e1, EString(_, q), e3]} {
549
+ noSideEffects(e1)} {safeBare(q) | Some(s)
550
+ } =>
551
+ Some(s + " -= " + self.emitTerm(e3, async))
552
+ | "ff:core/Js.set" {arguments.map {_.value} | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
553
+ Some(s + " = " + self.emitTerm(e2, async))
554
+ | "ff:core/Js.increment" {arguments.map {_.value} | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
555
+ Some(s + " += " + self.emitTerm(e2, async))
556
+ | "ff:core/Js.decrement" {arguments.map {_.value} | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
557
+ Some(s + " -= " + self.emitTerm(e2, async))
558
+ | _ => None
559
+ }
560
+ | EAssign(at, operator, name, value), _ =>
561
+ Some(escapeKeyword(name) + " " + operator + "= " + self.emitTerm(value, async))
562
+ | EAssignField(at, operator, record, field, value), _ =>
563
+ Some(
564
+ self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
565
+ self.emitTerm(value, async)
566
+ )
567
+ | _, _ =>
568
+ None
569
+ }
570
+
511
571
  emitSpecialCall(
512
572
  term: Term
513
573
  async: Bool
@@ -794,7 +854,7 @@ extend self: JsEmitter {
794
854
  name.{
795
855
  | "ff:core/Core.while" {arguments | [condition, body]} =>
796
856
  Some(
797
- "while(" + self.emitComma(invokeImmediately(condition), async) + ") {\n" +
857
+ "while(" + self.emitTerm(invokeImmediately(condition), async) + ") {\n" +
798
858
  self.emitStatements(invokeImmediately(body), False, False, async) + "\n}"
799
859
  )
800
860
  | "ff:core/Core.doWhile" {arguments | [doWhileBody]} {
@@ -906,7 +966,7 @@ extend self: JsEmitter {
906
966
  Some(self.emitTerm(array, async) + ".array.push(" + self.emitTerm(value, async) + ")")
907
967
  | "ff:core/Core.if" {arguments | [condition, body]} =>
908
968
  Some(
909
- "if(" + self.emitComma(condition, async) + ") {\n" +
969
+ "if(" + self.emitTerm(condition, async) + ") {\n" +
910
970
  if(last) {
911
971
  "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body), async) +
912
972
  ")\n} else return ff_core_Option.None()"
@@ -928,24 +988,6 @@ extend self: JsEmitter {
928
988
  Some(if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""})
929
989
  | "ff:core/Js.throw" {term | ECall c} {c.arguments | [argument]} =>
930
990
  Some("throw " + self.emitTerm(argument.value, async))
931
- | "ff:core/JsValue.JsValue_set" {arguments | [e1, e2, e3]} =>
932
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " = " + self.emitTerm(e3, async))
933
- | "ff:core/JsValue.JsValue_increment" {arguments | [e1, e2, e3]} =>
934
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " += " + self.emitTerm(e3, async))
935
- | "ff:core/JsValue.JsValue_decrement" {arguments | [e1, e2, e3]} =>
936
- Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " -= " + self.emitTerm(e3, async))
937
- | "ff:core/JsSystem.JsSystem_set" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
938
- Some(s + " = " + self.emitTerm(e3, async))
939
- | "ff:core/JsSystem.JsSystem_increment" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
940
- Some(s + " += " + self.emitTerm(e3, async))
941
- | "ff:core/JsSystem.JsSystem_decrement" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
942
- Some(s + " -= " + self.emitTerm(e3, async))
943
- | "ff:core/Js.set" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
944
- Some(s + " = " + self.emitTerm(e2, async))
945
- | "ff:core/Js.increment" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
946
- Some(s + " += " + self.emitTerm(e2, async))
947
- | "ff:core/Js.decrement" {arguments | [EString(_, q), e2]} {safeBare(q) | Some(s)} =>
948
- Some(s + " -= " + self.emitTerm(e2, async))
949
991
  | _ =>
950
992
  None
951
993
  }
@@ -1280,31 +1322,6 @@ extend self: JsEmitter {
1280
1322
  }
1281
1323
  }
1282
1324
 
1283
- emitComma(term: Term, async: Bool): String {
1284
- term.{
1285
- | ESequential(_, ESequential(_, ESequential(_, before1, before2), before3), after) {
1286
- safeCommable(before1) && safeCommable(before2) && safeCommable(before3) && safeCommable(after)
1287
- } =>
1288
- "(" + self.emitStatements(before1, False, False, async) + ", " +
1289
- self.emitStatements(before2, False, False, async) + ", " +
1290
- self.emitStatements(before3, False, False, async) + ", " +
1291
- self.emitTerm(after, async) + ")"
1292
- | ESequential(_, ESequential(_, before1, before2), after) {
1293
- safeCommable(before1) && safeCommable(before2) && safeCommable(after)
1294
- } =>
1295
- "(" + self.emitStatements(before1, False, False, async) + ", " +
1296
- self.emitStatements(before2, False, False, async) + ", " +
1297
- self.emitTerm(after, async) + ")"
1298
- | ESequential(_, before, after) {
1299
- safeCommable(before) && safeCommable(after)
1300
- } =>
1301
- "(" + self.emitStatements(before, False, False, async) + ", " +
1302
- self.emitTerm(after, async) + ")"
1303
- | _ =>
1304
- self.emitTerm(term, async)
1305
- }
1306
- }
1307
-
1308
1325
  }
1309
1326
 
1310
1327
  data ProcessedVariantCase(
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
 
package/core/Map.ff CHANGED
@@ -29,6 +29,14 @@ extend self[K: Order, V]: Map[K, V] {
29
29
  get(key: K): Option[V] {
30
30
  self.redBlack.get(key)
31
31
  }
32
+
33
+ lastBefore(key: K): Option[Pair[K, V]] {
34
+ self.redBlack.lastBefore(key)
35
+ }
36
+
37
+ firstAfter(key: K): Option[Pair[K, V]] {
38
+ self.redBlack.firstAfter(key)
39
+ }
32
40
 
33
41
  remove(key: K): Map[K, V] {
34
42
  Map(RbMap.delete(key, self.redBlack))
package/core/RbMap.ff CHANGED
@@ -123,6 +123,22 @@ extend self[K: Order, V]: RB[K, V] {
123
123
  }
124
124
  }
125
125
  }
126
+
127
+ lastBefore(key: K): Option[Pair[K, V]] {
128
+ self.{
129
+ | E => None
130
+ | T(_, l, k, v, r) {k >= key} => l.lastBefore(key)
131
+ | T(_, l, k, v, r) => r.lastBefore(key).orElse {Some(Pair(k, v))}
132
+ }
133
+ }
134
+
135
+ firstAfter(key: K): Option[Pair[K, V]] {
136
+ self.{
137
+ | E => None
138
+ | T(_, l, k, v, r) {k <= key} => r.firstAfter(key)
139
+ | T(_, l, k, v, r) => l.firstAfter(key).orElse {Some(Pair(k, v))}
140
+ }
141
+ }
126
142
 
127
143
  size(): Int {
128
144
  self.{
package/core/Set.ff CHANGED
@@ -17,6 +17,8 @@ extend self[T: Order]: Set[T] {
17
17
  remove(value: T): Set[T] {Set(self.map.remove(value))}
18
18
  removeAll(that: Set[T]): Set[T] {Set(self.map.removeAll(that.map))}
19
19
  contains(value: T): Bool {self.map.contains(value)}
20
+ lastBefore(key: T): Option[T] {self.map.lastBefore(key).map {_.first}}
21
+ firstAfter(key: T): Option[T] {self.map.firstAfter(key).map {_.first}}
20
22
  size(): Int {self.map.size()}
21
23
  toList(): List[T] {self.map.toList().map {_.first}}
22
24
  toStream(cycle: Bool = False): Stream[T] {self.map.toStream(cycle).map {_.first }}
@@ -41,4 +43,4 @@ instance Set[A: Show: Order]: Show {
41
43
  show(value: Set[A]): String {
42
44
  Show.show(value.toList()) + ".toSet()"
43
45
  }
44
- }
46
+ }
@@ -0,0 +1,8 @@
1
+ nodeMain(system: NodeSystem) {
2
+ let r = if(42 == 1) {
3
+ if(42 == 11) {
4
+ Log.debug("11")
5
+ }
6
+ Log.debug("1")
7
+ }
8
+ }
@@ -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
+ }