1.1.1-SNPASHOT some serious bugs in initilazation fixed. They were revealed by delegation real world usage
This commit is contained in:
1 parent
20f777f9f6
commit
f792c73b8f
17 files changed
+283
-48
No files matched your search
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
This is a tech proposal under construction, please do not use it yet
|
||||
for any purpose
|
||||
*/
|
||||
|
||||
/*
|
||||
Abstract delegate can be used to proxy read/wrtie field access
|
||||
or method call. Default implementation reports error.
|
||||
*/
|
||||
interface Delegate {
|
||||
fun getValue() = Unset
|
||||
fun setValue(newValue) { throw NotImplementedException("delegate setter is not implemented") }
|
||||
fun invoke(args...) { throw NotImplementedException("delegate setter is not implemented") }
|
||||
}
|
||||
|
||||
/*
|
||||
Delegate cam be used to implement a val, var or fun, so there are
|
||||
access type enum to distinguish:
|
||||
*/
|
||||
enum DelegateAccess {
|
||||
Val,
|
||||
Var,
|
||||
Callable
|
||||
}
|
||||
|
||||
// Delegate can be associated by a val/var/fun in a declaraion site using `by` keyword
|
||||
|
||||
val proxiedVal by proxy(1)
|
||||
var proxiedVar by proxy(2, 3)
|
||||
fun proxiedFun by proxy()
|
||||
|
||||
// each proxy is a Lyng expression returning instance of the Proxy interface:
|
||||
|
||||
/*
|
||||
Proxy interface is connecting some named property of a given kind with the `Delegate`.
|
||||
It removes the burden of dealing with property name and this ref on each get/set value
|
||||
or invoke allowing having one delegate per instance, execution buff.
|
||||
*/
|
||||
interface Proxy {
|
||||
fun getDelegate(propertyName: String,access: DelegateAccess,thisRef: Obj?): Delegate
|
||||
}
|
||||
|
||||
// val, var and fun can be delegated, local or class instance:
|
||||
class TestProxy: Proxy {
|
||||
override getDelegate(name,access,thisRef) {
|
||||
Delegate()
|
||||
}
|
||||
}
|
||||
|
||||
val proxy = TestProxy()
|
||||
|
||||
class Allowed {
|
||||
val v1 by proxy
|
||||
var v2 by proxy
|
||||
fun f1 by proxy
|
||||
}
|
||||
val v3 by proxy
|
||||
var v4 by proxy
|
||||
fun f2 by proxy
|
||||
|
||||
/*
|
||||
It means that for example
|
||||
Allowed().f1("foo")
|
||||
would call a delegate.invoke("foo") on the `Delegate` instance supplied by `proxy`, etc.
|
||||
*/
|
||||
|
||||
// The practic sample: lazy value
|
||||
|
||||
/*
|
||||
The delegate that caches single time evaluated value
|
||||
*/
|
||||
class LazyDelegate(creator): Delegate {
|
||||
private var currentValue=Unset
|
||||
|
||||
override fun getValue() {
|
||||
if( currentValue == Unset )
|
||||
currentValue = creator()
|
||||
currentValue
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The proxy to assign it
|
||||
*/
|
||||
class LazyProxy(creator) {
|
||||
fun getDelegate(name,access,thisRef) {
|
||||
if( access != DelegateAccess.Val )
|
||||
throw IllegalArgumentException("only lazy val are allowed")
|
||||
LazyDelegate(creator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
A helper function to simplify creation:
|
||||
*/
|
||||
fun lazy(creator) {
|
||||
LazyProxy(creator)
|
||||
}
|
||||
|
||||
// Usage sample and the test:
|
||||
var callCounter = 0
|
||||
assertEquals(0, clallCounter)
|
||||
|
||||
val lazyText by lazy { "evaluated text" }
|
||||
|
||||
// the lazy property is not yet evaluated:
|
||||
assertEquals(0, clallCounter)
|
||||
// now evaluate it by using it:
|
||||
assertEquals("evaluated text", lazyText)
|
||||
assertEquals(1, callCounter)
|
||||
|
||||
// lazy delegate should fail on vars or funs:
|
||||
assertThrows { var bad by lazy { "should not happen" } }
|
||||
assertThrows { fun bad by lazy { 42 } }
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
# Map literals — refined proposal
|
||||
|
||||
[//]: # (excludeFromIndex)
|
||||
|
||||
Implement JavaScript-like literals for maps. The syntax and semantics align with named arguments in function calls, but map literals are expressions that construct `Map` values.
|
||||
|
||||
Keys can be either:
|
||||
- string literals: `{ "some key": value }`, or
|
||||
- identifiers: `{ name: expr }`, where the key becomes the string `"name"`.
|
||||
|
||||
Identifier shorthand inside map literals is supported:
|
||||
- `{ name: }` desugars to `{ "name": name }`.
|
||||
|
||||
Property access sugar is not provided for maps: use bracket access only, e.g. `m["a"]`, not `m.a`.
|
||||
|
||||
Examples:
|
||||
|
||||
```lyng
|
||||
val x = 2
|
||||
val m = { "a": 1, x: x*10, y: }
|
||||
assertEquals(1, m["a"]) // string-literal key
|
||||
assertEquals(20, m["x"]) // identifier key
|
||||
assertEquals(2, m["y"]) // identifier shorthand
|
||||
```
|
||||
|
||||
Spreads (splats) in map literals are allowed and merged left-to-right with “rightmost wins” semantics:
|
||||
|
||||
```lyng
|
||||
val base = { a: 1, b: 2 }
|
||||
val m = { a: 0, ...base, b: 3, c: 4 }
|
||||
assertEquals(1, m["a"]) // base overwrites a:0
|
||||
assertEquals(3, m["b"]) // literal overwrites spread
|
||||
assertEquals(4, m["c"]) // new key
|
||||
```
|
||||
|
||||
Trailing commas are allowed (optional):
|
||||
|
||||
```lyng
|
||||
val m = {
|
||||
"a": 1,
|
||||
b: 2,
|
||||
...other,
|
||||
}
|
||||
```
|
||||
|
||||
Duplicate keys among literal entries (including identifier shorthand) are a compile-time error:
|
||||
|
||||
```lyng
|
||||
{ foo: 1, "foo": 2 } // error: duplicate key "foo"
|
||||
{ foo:, foo: 2 } // error: duplicate key "foo"
|
||||
```
|
||||
|
||||
Spreads are evaluated at runtime. Overlaps from spreads are resolved by last write wins. If a spread is not a map, or yields a map with non-string keys, it’s a runtime error.
|
||||
|
||||
Merging with `+`/`+=` and entries:
|
||||
|
||||
```lyng
|
||||
("1" => 10) + ("2" => 20) // Map("1"=>10, "2"=>20)
|
||||
{ "1": 10 } + ("2" => 20) // same
|
||||
{ "1": 10 } + { "2": 20 } // same
|
||||
|
||||
var m = { "a": 1 }
|
||||
m += ("b" => 2) // m = { "a":1, "b":2 }
|
||||
```
|
||||
|
||||
Rightmost wins on duplicates consistently across spreads and merges. All map merging operations require string keys; encountering a non-string key during merge is a runtime error.
|
||||
|
||||
Empty map literal `{}` is not supported to avoid ambiguity with blocks/lambdas. Use `Map()` for an empty map.
|
||||
|
||||
Lambda disambiguation
|
||||
- A `{ ... }` with typed lambda parameters must have a top-level `->` in its header. The compiler disambiguates by looking for a top-level `->`. If none is found, it attempts to parse a map literal; if that fails, it is parsed as a lambda or block.
|
||||
|
||||
Grammar (EBNF)
|
||||
|
||||
```
|
||||
ws = zero or more whitespace (incl. newline/comments)
|
||||
map_literal = '{' ws map_entries ws '}'
|
||||
map_entries = map_entry ( ws ',' ws map_entry )* ( ws ',' )?
|
||||
map_entry = map_key ws ':' ws map_value_opt
|
||||
| '...' ws expression
|
||||
map_key = string_literal | ID
|
||||
map_value_opt = expression | ε // ε allowed only if map_key is ID
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Identifier shorthand (`id:`) is allowed only for identifiers, not string-literal keys.
|
||||
- Spreads accept any expression; at runtime it must yield a `Map` with string keys.
|
||||
- Duplicate keys are detected at compile time among literal keys; spreads are merged at runtime with last-wins.
|
||||
|
||||
Rationale
|
||||
- The `{ name: value }` style is familiar and ergonomic.
|
||||
- Disambiguation with lambdas leverages the required `->` in typed lambda headers.
|
||||
- Avoiding `m.a` sidesteps method/field shadowing and keeps semantics clear.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Named arguments in calls
|
||||
|
||||
[//]: # (excludeFromIndex)
|
||||
|
||||
|
||||
Extend function/method calls to allow setting arguments by name using colon syntax at call sites. This is especially convenient with many parameters and default values.
|
||||
|
||||
Examples:
|
||||
|
||||
```lyng
|
||||
fun test(a="foo", b="bar", c="bazz") { [a, b, c] }
|
||||
|
||||
assertEquals(test(b: "b"), ["foo", "b", "bazz"])
|
||||
assertEquals(test("a", c: "c"), ["a", "bar", "c"])
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Named arguments are optional. If named arguments are present, their order is not important.
|
||||
- Named arguments must follow positional arguments; positional arguments cannot follow named ones (the only exception is the syntactic trailing block outside parentheses, see below).
|
||||
- A named argument cannot reassign a parameter already set positionally.
|
||||
- If the last parameter is already assigned by a named argument (or named splat), the trailing-lambda rule must NOT apply: a following `{ ... }` after the call is an error.
|
||||
|
||||
Rationale for using `:` instead of `=` in calls: in Lyng, assignment `=` is an expression; using `:` avoids ambiguity and keeps declarations (`name: Type`) distinct from call sites, where casting uses `as` / `as?`.
|
||||
|
||||
Migration note: earlier drafts/examples used `name = value`. The final syntax is `name: value` at call sites.
|
||||
|
||||
## Extended call argument splats: named splats
|
||||
|
||||
With named arguments, splats (`...`) are extended to support maps as named splats. When a splat evaluates to a Map, its entries provide name→value assignments:
|
||||
|
||||
```lyng
|
||||
fun test(a="a", b="b", c="c", d="d") { [a, b, c, d] }
|
||||
|
||||
assertEquals(test("A?", ...Map("d" => "D!", "b" => "B!")), ["A?", "B!", "c", "D!"])
|
||||
```
|
||||
|
||||
Constraints for named splats:
|
||||
|
||||
- Only string keys are allowed in map splats; otherwise, a clean error is thrown.
|
||||
- Named splats cannot reassign parameters already set (positionally or by earlier named arguments/splats).
|
||||
- Named splats follow the same ordering as named arguments: they must appear after all positional arguments and positional splats.
|
||||
|
||||
## Trailing-lambda interaction
|
||||
|
||||
Lyng supports a syntactic trailing block after a call: `f(args) { ... }`. With named args/splats, if the last parameter is already assigned by name, the trailing block must not apply and the call is an error:
|
||||
|
||||
```lyng
|
||||
fun f(x, onDone) { onDone(x) }
|
||||
f(x: 1) { 42 } // ERROR: last parameter already assigned by name
|
||||
f(1) { 42 } // OK
|
||||
```
|
||||
|
||||
## Errors (non-exhaustive)
|
||||
|
||||
- Positional argument after any named argument inside parentheses: error.
|
||||
- Positional splat after any named argument: error.
|
||||
- Duplicate named assignment (directly or via map splats): error.
|
||||
- Unknown parameter name in a named argument/splat: error.
|
||||
- Map splat with non-string keys: error.
|
||||
- Attempt to target the ellipsis parameter by name: error.
|
||||
|
||||
## Notes
|
||||
|
||||
- Declarations continue to use `:` for types, while call sites use `:` for named arguments and `as` / `as?` for type casts/checks.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user