Add context receiver extensions for DSLs

This commit is contained in:
sergeych committed 2026-04-29 20:49:50 +03:00
1 parent e107296bca
commit b2200e71ff
22 files changed
+860 -71

No files matched your search

+16
View File
@@ -479,6 +479,18 @@ val block: context(Html, Head) Body.()->String = {
}
```
Context receivers can also constrain extension functions. The extension is visible only when the required receiver is
already in the implicit receiver stack:
```lyng
class Tag { fun addText(text: String) { /* ... */ } }
context(Tag)
fun String.unaryPlus() {
this@Tag.addText(this)
}
```
- Field inheritance (`val`/`var`) and collisions
- Instance storage is kept per declaring class, internally disambiguated; unqualified read/write resolves to the first match in the resolution order (leftmost base).
- Qualified read/write (via `this@Type` or casts) targets the chosen ancestor’s storage.
@@ -650,10 +662,14 @@ Unary operators are overloaded by defining methods with no arguments:
| Operator | Method Name |
| :--- | :--- |
| `+a` | `unaryPlus()` |
| `-a` | `negate()` |
| `!a` | `logicalNot()` |
| `~a` | `bitNot()` |
`unaryPlus()` is useful in DSL-style builders where `+"text"` should append text to
the current receiver. See [samples/html_builder_dsl.lyng](samples/html_builder_dsl.lyng).
### Assignment Operators
Assignment operators like `+=` first attempt to call a specific assignment method. If that method is not defined, they fall back to a combination of the binary operator and a regular assignment (e.g., `a = a + b`).
+2
View File
@@ -83,6 +83,7 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
## 4. Operators (implemented)
- Assignment: `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `?=`.
- Logical: `||`, `&&`, unary `!`.
- Unary arithmetic/bitwise: unary `+`, unary `-`, `~`.
- Bitwise: `|`, `^`, `&`, `~`, shifts `<<`, `>>`.
- Equality/comparison: `==`, `!=`, `===`, `!==`, `<`, `<=`, `>`, `>=`, `<=>`, `=~`, `!~`.
- Type/containment: `is`, `!is`, `in`, `!in`, `as`, `as?`.
@@ -119,6 +120,7 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
- shorthand: `fun f(x) = expr`.
- generics: `fun f<T>(x: T): T`.
- extension functions: `fun Type.name(...) { ... }`.
- context-aware extension functions: `context(Tag) fun String.unaryPlus() { this@Tag.addText(this) }`.
- named singleton `object` declarations can be extension receivers too: `fun Config.describe(...) { ... }`, `val Config.tag get() = ...`.
- static extension functions are callable on the type object: `static fun List<T>.fill(...)` -> `List.fill(...)`.
- delegated callable: `fun f(...) by delegate`.
+50
View File
@@ -0,0 +1,50 @@
class Tag(name: String) {
val name = name
var inner = ""
fun child(tagName: String, block: Tag.()->void) {
val child = Tag(tagName)
with(child) { block(this) }
inner += child.render()
}
fun head(block: Tag.()->void) { child("head", block) }
fun body(block: Tag.()->void) { child("body", block) }
fun title(block: Tag.()->void) { child("title", block) }
fun h1(block: Tag.()->void) { child("h1", block) }
fun addText(text: String) {
inner += text
}
fun render() {
"<" + name + ">" + inner + "</" + name + ">"
}
}
context(Tag)
fun String.unaryPlus() {
this@Tag.addText(this)
}
fun html(block: Tag.()->void) {
val root = Tag("html")
with(root) { block(this) }
root.render()
}
val page = html {
head {
title {
+"Demo"
}
}
body {
h1 {
+"Heading 1"
}
}
}
println(page)
assertEquals("<html><head><title>Demo</title></head><body><h1>Heading 1</h1></body></html>", page)
+8
View File
@@ -1,6 +1,9 @@
// Sample: Operator Overloading in Lyng
class Vector<T>(val x: T, val y: T) {
// Overload unary +
fun unaryPlus() = this
// Overload +
fun plus(other: Vector<U>) = Vector(x + other.x, y + other.y)
@@ -28,6 +31,11 @@ val v2 = Vector(5, 5)
println("v1: " + v1)
println("v2: " + v2)
// Test unary +
val v0 = +v1
println("+v1 = " + v0)
assertEquals(Vector(10, 20), v0)
// Test binary +
val v3 = v1 + v2
println("v1 + v2 = " + v3)