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.
- package/.vscode/settings.json +1 -1
- package/compiler/Builder.ff +9 -1
- package/compiler/Dependencies.ff +11 -7
- package/compiler/JsEmitter.ff +75 -58
- package/core/Buffer.ff +1 -1
- package/core/Map.ff +8 -0
- package/core/RbMap.ff +16 -0
- package/core/Set.ff +3 -1
- package/experimental/IfBug.ff +8 -0
- package/experimental/site/.firefly/package.ff +3 -0
- package/experimental/site/ChatRoute.ff +26 -0
- package/experimental/site/Main.ff +40 -0
- package/experimental/site/RouteTools.ff +70 -0
- package/experimental/site/Routes.ff +13 -0
- package/experimental/site2/.firefly/package.ff +3 -0
- package/experimental/site2/ChatRoomRoute.ff +27 -0
- package/experimental/site2/Html.ff +49 -0
- package/experimental/site2/Main.ff +37 -0
- package/experimental/site2/Router.ff +23 -0
- package/lsp/LanguageServer.ff +3 -0
- package/output/js/ff/compiler/Builder.mjs +44 -2
- package/output/js/ff/compiler/Dependencies.mjs +26 -12
- package/output/js/ff/compiler/JsEmitter.mjs +698 -598
- package/output/js/ff/core/Buffer.mjs +2 -2
- package/output/js/ff/core/Map.mjs +16 -0
- package/output/js/ff/core/Path.mjs +2 -8
- package/output/js/ff/core/RbMap.mjs +112 -0
- package/output/js/ff/core/Set.mjs +24 -0
- package/package.json +1 -1
- package/vscode/package.json +1 -1
- package/webserver/WebRoute.ff +150 -0
package/.vscode/settings.json
CHANGED
package/compiler/Builder.ff
CHANGED
|
@@ -136,7 +136,15 @@ check(
|
|
|
136
136
|
|
|
137
137
|
packages.filter {!_.files.isEmpty()}.each {package =>
|
|
138
138
|
let firstFile = package.files.grabFirst()
|
|
139
|
-
|
|
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 {
|
package/compiler/Dependencies.ff
CHANGED
|
@@ -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
|
-
|
|
37
|
-
self.parsePackageFile(packagePair, packageFile.
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
package/compiler/JsEmitter.ff
CHANGED
|
@@ -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.
|
|
383
|
-
self.
|
|
384
|
-
"\n? " + self.
|
|
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.
|
|
389
|
-
"\n? ff_core_Option.Some(" + self.
|
|
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.
|
|
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.
|
|
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.
|
|
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->
|
|
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,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,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
|
+
}
|