diff --git a/docs/Buffer.md b/docs/Buffer.md index fdad4bf..a15723f 100644 --- a/docs/Buffer.md +++ b/docs/Buffer.md @@ -23,11 +23,11 @@ There are a lo of ways to construct a buffer: assertEquals( 5, Buffer("hello").size ) // from bytes, e.g. integers in range 0..255 - assertEquals( 255, Buffer(1,2,3,255).last() ) + assertEquals( 255, Buffer(1,2,3,255).last ) // from whatever iterable that produces bytes, e.g. // integers in 0..255 range: - assertEquals( 129, Buffer([1,2,129]).last() ) + assertEquals( 129, Buffer([1,2,129]).last ) // Empty buffer of fixed size: assertEquals(100, Buffer(100).size) diff --git a/docs/OOP.md b/docs/OOP.md index 891c726..eecf958 100644 --- a/docs/OOP.md +++ b/docs/OOP.md @@ -119,8 +119,9 @@ Properties allow you to define member accessors that look like fields but execut ### Basic Syntax -Properties are declared using `val` (read-only) or `var` (read-write) followed by a name and `get()`/`set()` blocks: +Properties are declared using `val` (read-only) or `var` (read-write) followed by a name and a `get` (and optionally `set`) accessor. Unlike fields, properties do not have automatic storage and must compute their values or delegate to other members. +The standard syntax uses parentheses: ```lyng class Person(private var _age: Int) { // Read-only property @@ -136,21 +137,17 @@ class Person(private var _age: Int) { if (value >= 0) _age = value } } - -val p = Person(15) -assertEquals("Minor", p.ageCategory) -p.age = 20 -assertEquals("Adult", p.ageCategory) ``` -### Laconic Expression Shorthand +### Laconic Syntax (Optional Parentheses) -For simple accessors and methods, you can use the `=` shorthand for a more elegant and laconic form: +For even cleaner code, you can omit the parentheses for `get` and `set`. This is especially useful for simple expression shorthand: ```lyng class Circle(val radius: Real) { - val area get() = π * radius * radius - val circumference get() = 2 * π * radius + // Laconic expression shorthand + val area get = π * radius * radius + val circumference get = 2 * π * radius fun diameter() = radius * 2 } @@ -159,15 +156,16 @@ fun median(a, b) = (a + b) / 2 class Counter { private var _count = 0 - var count get() = _count set(v) = _count = v + var count get = _count set(v) = _count = v } ``` ### Key Rules -- **`val` properties** must have a `get()` accessor and cannot have a `set()`. -- **`var` properties** must have both `get()` and `set()` accessors. +- **`val` properties** must have a `get` accessor (with or without parentheses) and cannot have a `set`. +- **`var` properties** must have both `get` and `set` accessors. - **Functions and methods** can use the `=` shorthand to return the result of a single expression. +- **`override` is mandatory**: If you are overriding a member from a base class, you MUST use the `override` keyword. - **No Backing Fields**: There is no magic `field` identifier. If you need to store state, you must declare a separate (usually `private`) field. - **Type Inference**: You can omit the type declaration if it can be inferred or if you don't need strict typing. diff --git a/docs/embedding.md b/docs/embedding.md index d541581..bada46f 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -103,6 +103,11 @@ scope.addVoidFn("log") { println(items.joinToString(" ") { it.toString(this).value }) } +// When adding a member function to a class, you can use isOverride = true +// myClass.addFn("toString", isOverride = true) { +// ObjString("Custom string representation") +// } + // Call them from Lyng scope.eval("val y = inc(41); log('Answer:', y)") ``` @@ -122,6 +127,9 @@ myClass.createField("version", ObjString("1.0.0"), isMutable = false) // Add a mutable field with an initial value myClass.createField("count", ObjInt(0), isMutable = true) +// If you are overriding a field from a base class, use isOverride = true +// myClass.createField("someBaseField", ObjInt(42), isOverride = true) + scope.addConst("MyClass", myClass) ``` @@ -153,6 +161,16 @@ myClass.addProperty( } ) +// You can also create an ObjProperty explicitly +val explicitProp = ObjProperty( + name = "hexValue", + getter = statement { ObjString(internalValue.toString(16)) } +) +myClass.addProperty("hexValue", prop = explicitProp) + +// Use isOverride = true when overriding a property from a base class +// myClass.addProperty("baseProp", getter = { ... }, isOverride = true) + scope.addConst("MyClass", myClass) ``` diff --git a/docs/parallelism.md b/docs/parallelism.md index e0b247c..0c042c4 100644 --- a/docs/parallelism.md +++ b/docs/parallelism.md @@ -226,13 +226,11 @@ Future work: introduce thread‑safe pooling (e.g., per‑thread pools or confin Closures executed by `launch { ... }` and `flow { ... }` resolve names using the `ClosureScope` rules: -1. Closure frame locals/arguments -2. Captured receiver instance/class members -3. Closure ancestry locals + each frame’s `this` members (cycle‑safe) -4. Caller `this` members -5. Caller ancestry locals + each frame’s `this` members (cycle‑safe) -6. Module pseudo‑symbols (e.g., `__PACKAGE__`) -7. Direct module/global fallback (nearest `ModuleScope` and its parent/root) +1. **Current frame locals and arguments**: Variables defined within the current closure execution. +2. **Captured lexical ancestry**: Outer local variables captured at the site where the closure was defined (the "lexical environment"). +3. **Captured receiver members**: If the closure was defined within a class or explicitly bound to an object, it checks members of that object (`this`), following MRO and respecting visibility. +4. **Caller environment**: Falls back to the calling context (e.g., the caller's `this` or local variables). +5. **Global/Module fallbacks**: Final check for module-level constants and global functions. Implications: - Outer locals (e.g., `counter`) stay visible across suspension points. diff --git a/docs/scopes_and_closures.md b/docs/scopes_and_closures.md index 7e6683f..5e8b0f2 100644 --- a/docs/scopes_and_closures.md +++ b/docs/scopes_and_closures.md @@ -8,16 +8,13 @@ Name lookup across nested scopes and closures can accidentally form recursive re ## Resolution order in ClosureScope When evaluating an identifier `name` inside a closure, `ClosureScope.get(name)` resolves in this order: -1. Closure frame locals and arguments -2. Captured receiver (`closureScope.thisObj`) instance/class members -3. Closure ancestry locals + each frame’s `thisObj` members (cycle‑safe) -4. Caller `this` members -5. Caller ancestry locals + each frame’s `thisObj` members (cycle‑safe) -6. Module pseudo‑symbols (e.g., `__PACKAGE__`) from the nearest `ModuleScope` -7. Direct module/global fallback (nearest `ModuleScope` and its parent/root scope) -8. Final fallback: base local/parent lookup for the current frame +1. **Current frame locals and arguments**: Variables defined within the current closure execution. +2. **Captured lexical ancestry**: Outer local variables captured at the site where the closure was defined (the "lexical environment"). +3. **Captured receiver members**: If the closure was defined within a class or explicitly bound to an object, it checks members of that object (`this`). This includes both instance fields/methods and class-level static members, following the MRO (C3) and respecting visibility rules (private members are only visible if the closure was defined in their class). +4. **Caller environment**: If not found lexically, it falls back to the calling context (e.g., the DSL's `this` or the caller's local variables). +5. **Global/Module fallbacks**: Final check for module-level constants and global functions. -This preserves intuitive visibility (locals → captured receiver → closure chain → caller members → caller chain → module/root) while preventing infinite recursion between scope types. +This ensures that closures primarily interact with their defining environment (lexical capture) while still being able to participate in DSL-style calling contexts. ## Use raw‑chain helpers for ancestry walks When authoring new scope types or advanced lookups, avoid calling virtual `get` while walking parents. Instead, use the non‑dispatch helpers on `Scope`: diff --git a/docs/tutorial.md b/docs/tutorial.md index 2c0749d..2f4c1ca 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1494,7 +1494,7 @@ Typical set of String functions includes: | s1 += s2 | self-modifying concatenation | | toReal() | attempts to parse string as a Real value | | toInt() | parse string to Int value | -| characters() | create [List] of characters (1) | +| characters | create [List] of characters (1) | | encodeUtf8() | returns [Buffer] with characters encoded to utf8 | | matches(re) | matches the regular expression (2) | | | | diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 0d3ce33..7393217 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "1.2.-SNAPSHOT" +version = "1.2.0-SNAPSHOT" // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below