diff --git a/docs/samples/fs_sample.lyng b/docs/samples/fs_sample.lyng index b58fe5b..a9c55e4 100755 --- a/docs/samples/fs_sample.lyng +++ b/docs/samples/fs_sample.lyng @@ -1,7 +1,6 @@ #!/bin/env lyng import lyng.io.fs -import lyng.stdlib val files = Path("../..").list().toList() // most long is longest? diff --git a/site/src/jsMain/kotlin/App.kt b/site/src/jsMain/kotlin/App.kt index 873f236..84bbca3 100644 --- a/site/src/jsMain/kotlin/App.kt +++ b/site/src/jsMain/kotlin/App.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,7 @@ fun App() { Div({ classes("col-12", if (isDocsRoute) "col-lg-9" else "col-lg-12") }) { when { route.isBlank() -> HomePage() - route == "tryling" -> TryLyngPage() + route.startsWith("tryling") -> TryLyngPage(route) route.startsWith("search") -> SearchPage(route) !isDocsRoute -> ReferencePage() else -> DocsPage( diff --git a/site/src/jsMain/kotlin/HomePage.kt b/site/src/jsMain/kotlin/HomePage.kt index 43756ab..9c5a36f 100644 --- a/site/src/jsMain/kotlin/HomePage.kt +++ b/site/src/jsMain/kotlin/HomePage.kt @@ -15,7 +15,9 @@ * */ -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* +import kotlinx.browser.window +import kotlinx.coroutines.delay import net.sergeych.lyngweb.ensureBootstrapCodeBlocks import net.sergeych.lyngweb.highlightLyngHtml import net.sergeych.lyngweb.htmlEscape @@ -23,6 +25,141 @@ import org.jetbrains.compose.web.dom.* @Composable fun HomePage() { + val samples = remember { + listOf( + """ + // Everything is an expression + val x = 10 + val status = if (x > 0) "Positive" else "Zero or Negative" + + // Even loops return values! + val result = for (i in 1..5) { + if (i == 3) break "Found 3!" + } else "Not found" + + println("Result: " + result) + """.trimIndent(), + """ + // Functional power with ranges and collections + val squares = (1..10) + .filter { it % 2 == 0 } + .map { it * it } + + println("Even squares: " + squares) + // Output: [4, 16, 36, 64, 100] + """.trimIndent(), + """ + // Flexible map literals and shorthands + val id = 101 + val name = "Lyng" + val base = { id:, name: } // Shorthand for id: id, name: name + + val full = { ...base, version: "1.0", status: "active" } + println(full) + """.trimIndent(), + """ + // Modern null safety + var config = null + config ?= { timeout: 30 } // Assign only if null + + val timeout = config?.timeout ?? 60 + println("Timeout is: " + timeout) + """.trimIndent(), + """ + // Destructuring with splat operator + val [first, middle..., last] = [1, 2, 3, 4, 5, 6] + + println("First: " + first) + println("Middle: " + middle) + println("Last: " + last) + """.trimIndent(), + """ + // Diamond-safe Multiple Inheritance (C3 MRO) + interface Logger { + fun log(m) = println("[LOG] " + m) + } + interface Auth { + fun login(u) = println("Login: " + u) + } + + class App() : Logger, Auth { + fun run() { + log("Starting...") + login("admin") + } + } + App().run() + """.trimIndent(), + """ + // Extension functions and properties + fun String.shout() = this.toUpperCase() + "!!!" + + println("hello".shout()) + + val List.second get = this[1] + println([10, 20, 30].second) + """.trimIndent(), + """ + // Non-local returns from closures + fun findFirst(list, predicate) { + list.forEach { + if (predicate(it)) return@findFirst it + } + null + } + + val found = findFirst([1, 5, 8, 12]) { it > 10 } + println("Found: " + found) + """.trimIndent(), + """ + // Easy operator overloading + class Vector(val x, val y) { + override fun plus(other) = Vector(x + other.x, y + other.y) + override fun toString() = "Vector(%g, %g)"(x, y) + } + + val v1 = Vector(1, 2) + val v2 = Vector(3, 4) + println(v1 + v2) + """.trimIndent(), + """ + // Property delegation to Map + class User() { + var name by Map() + } + + val u = User() + u.name = "Sergey" + println("User name: " + u.name) + """.trimIndent(), + """ + // Implicit coroutines: parallelism without ceremony + import lyng.time + + val d1 = launch { + delay(100.milliseconds) + "Task A finished" + } + val d2 = launch { + delay(50.milliseconds) + "Task B finished" + } + + println(d1.await()) + println(d2.await()) + """.trimIndent() + ) + } + + var currentSlide by remember { mutableStateOf((samples.indices).random()) } + + LaunchedEffect(Unit) { + ensureSlideshowStyles() + while (true) { + delay(7000) + currentSlide = (currentSlide + 1) % samples.size + } + } // Hero section Section({ classes("py-4", "py-lg-5") }) { Div({ classes("text-center") }) { @@ -44,7 +181,7 @@ fun HomePage() { } } // CTA buttons - Div({ classes("d-flex", "justify-content-center", "gap-2", "mb-4") }) { + Div({ classes("d-flex", "justify-content-center", "gap-2", "mb-2") }) { A(attrs = { classes("btn", "btn-primary", "btn-lg") attr("href", "#/docs/tutorial.md") @@ -72,47 +209,25 @@ fun HomePage() { } } - // Code sample - val code = """ -// Create, transform, and verify — the Lyng way -import lyng.stdlib - -fun findFirstPositive(list) { - list.forEach { - if (it > 0) return@findFirstPositive it - } - null -} -assertEquals(42, findFirstPositive([-1, 42, -5])) - -val data = 1..5 // or [1,2,3,4,5] -val evens2 = data.filter { it % 2 == 0 }.map { it * it } -assertEquals([4, 16], evens2) - -// Map literal with identifier keys, shorthand, and spread -val base = { a: 1, b: 2 } -val patch = { b: 3, c: } -val m = { "a": 0, ...base, ...patch, d: 4 } -assertEquals(1, m["a"]) // base overwrites 0 - -// Object expressions: anonymous classes on the fly -val worker = object : Runnable { - override fun run() = println("Working...") -} -worker.run() - -// User-defined exceptions: real classes with custom fields -class MyError(val code, m) : Exception(m) -try { - throw MyError(500, "Something failed") -} catch (e: MyError) { - println("Error " + e.code + ": " + e.message) -} ->>> void -""".trimIndent() - val mapHtml = "
" + htmlEscape(code) + ""
- Div({ classes("markdown-body", "mt-3") }) {
- UnsafeRawHtml(highlightLyngHtml(ensureBootstrapCodeBlocks(mapHtml)))
+ // Code sample slideshow
+ Div({
+ classes("markdown-body", "mt-0", "slide-container", "position-relative")
+ onClick {
+ window.location.hash = "#tryling?code=" + encodeURIComponent(samples[currentSlide])
+ }
+ }) {
+ Small({
+ classes("position-absolute", "top-0", "end-0", "m-2", "text-muted", "fw-light", "try-hint")
+ }) {
+ Text("click to try")
+ }
+ key(currentSlide) {
+ val slideCode = samples[currentSlide]
+ val mapHtml = "" + htmlEscape(slideCode) + ""
+ Div({ classes("slide-animation") }) {
+ UnsafeRawHtml(highlightLyngHtml(ensureBootstrapCodeBlocks(mapHtml)))
+ }
+ }
}
// Short features list
@@ -147,3 +262,36 @@ try {
}
}
}
+
+fun ensureSlideshowStyles() {
+ if (window.document.getElementById("slideshow-styles") != null) return
+ val style = window.document.createElement("style") as org.w3c.dom.HTMLStyleElement
+ style.id = "slideshow-styles"
+ style.textContent = """
+ @keyframes slideIn {
+ from { opacity: 0; transform: translateX(30px); }
+ to { opacity: 1; transform: translateX(0); }
+ }
+ .slide-animation {
+ animation: slideIn 0.4s ease-out;
+ }
+ .slide-container {
+ min-height: 320px;
+ cursor: pointer;
+ transition: transform 0.2s;
+ background-color: var(--bs-body-bg);
+ }
+ .slide-container:hover {
+ transform: scale(1.005);
+ }
+ .try-hint {
+ opacity: 0.5;
+ transition: opacity 0.2s;
+ pointer-events: none;
+ }
+ .slide-container:hover .try-hint {
+ opacity: 1;
+ }
+ """.trimIndent()
+ window.document.head!!.appendChild(style)
+}
diff --git a/site/src/jsMain/kotlin/TryLyngPage.kt b/site/src/jsMain/kotlin/TryLyngPage.kt
index 53abab8..2ab46a6 100644
--- a/site/src/jsMain/kotlin/TryLyngPage.kt
+++ b/site/src/jsMain/kotlin/TryLyngPage.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
+ * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,20 +18,30 @@
import androidx.compose.runtime.*
import kotlinx.coroutines.launch
import net.sergeych.lyng.LyngVersion
-import net.sergeych.lyng.Scope
+import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError
import net.sergeych.lyngweb.EditorWithOverlay
import org.jetbrains.compose.web.dom.*
@Composable
-fun TryLyngPage() {
+fun TryLyngPage(route: String) {
val scope = rememberCoroutineScope()
- var code by remember {
+ val initialCode = remember(route) {
+ val params = route.substringAfter('?', "")
+ val codeParam = params.split('&').find { it.startsWith("code=") }?.substringAfter('=')
+ if (codeParam != null) {
+ try {
+ decodeURIComponent(codeParam)
+ } catch (_: Throwable) {
+ null
+ }
+ } else null
+ }
+ var code by remember(initialCode) {
mutableStateOf(
- """
+ initialCode ?: """
// Welcome to Lyng! Edit and run.
// Try changing the data and press Ctrl+Enter or click Run.
- import lyng.stdlib
val data = 1..5 // or [1, 2, 3, 4, 5]
data.filter { it % 2 == 0 }.map { it * it }
@@ -54,16 +64,16 @@ fun TryLyngPage() {
val printed = StringBuilder()
try {
// Create a fresh module scope each run so imports and vars are clean
- val s = Scope.new()
+ val s = Script.newScope()
// Capture printed output from Lyng `print`/`println` into the UI result window
s.addVoidFn("print") {
- for ((i, a) in args.withIndex()) {
+ for ((i, a) in this.args.withIndex()) {
if (i > 0) printed.append(' ')
printed.append(a.toString(this).value)
}
}
s.addVoidFn("println") {
- for ((i, a) in args.withIndex()) {
+ for ((i, a) in this.args.withIndex()) {
if (i > 0) printed.append(' ')
printed.append(a.toString(this).value)
}
@@ -118,9 +128,8 @@ fun TryLyngPage() {
}
fun resetCode() {
- code = """
+ code = initialCode ?: """
// Welcome to Lyng! Edit and run.
- import lyng.stdlib
[1,2,3].map { it * 10 }
""".trimIndent()
output = null