Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9ae00b7f4 | ||
|
|
7b1ba71ef0 | ||
|
|
8b94432f66 | ||
|
|
338dc00573 | ||
|
|
d0230c5b89 | ||
|
|
5dc2159024 | ||
|
|
0759346e4b | ||
|
|
f848d79d39 | ||
|
|
52a3a96e3f | ||
|
|
b7dfda2f5d | ||
|
|
05e15e8e42 | ||
|
|
d4bb186f85 | ||
|
|
7bc17037f9 |
No files matched your search
+6
-1
@@ -2,6 +2,11 @@
|
||||
|
||||
### Unreleased
|
||||
|
||||
- Language: Refined `protected` visibility rules
|
||||
- Ancestor classes can now access `protected` members of their descendants, provided the ancestor also defines or inherits a member with the same name (indicating an override of a member known to the ancestor).
|
||||
- This allows patterns where a base class calls a `protected` method that is implemented in a subclass.
|
||||
- Fixed a regression where self-calls to unmangled members sometimes bypassed visibility checks incorrectly; these are now handled by the refined logic.
|
||||
|
||||
- Language: Added `return` statement
|
||||
- `return [expression]` exits the innermost enclosing callable (function or lambda).
|
||||
- Supports non-local returns using `@label` syntax (e.g., `return@outer 42`).
|
||||
@@ -96,7 +101,7 @@
|
||||
- Header-specified constructor arguments are passed to direct bases.
|
||||
- Visibility enforcement under MI:
|
||||
- `private` visible only inside the declaring class body.
|
||||
- `protected` visible inside the declaring class and any of its transitive subclasses; unrelated contexts cannot access it (qualification/casts do not bypass).
|
||||
- `protected` visible inside the declaring class and its transitive subclasses. Additionally, ancestor classes can access protected members of their descendants if it's an override of a member known to the ancestor. Unrelated contexts cannot access it (qualification/casts do not bypass).
|
||||
- Diagnostics improvements:
|
||||
- Missing member/field messages include receiver class and linearization order; hints for `this@Type` or casts when helpful.
|
||||
- Invalid `this@Type` reports that the qualifier is not an ancestor and shows the receiver lineage.
|
||||
|
||||
+3
-2
@@ -14,6 +14,7 @@ High-density specification for LLMs. Reference this for all Lyng code generation
|
||||
4. `void` (if loop body never executed and no `else`).
|
||||
- **Implicit Coroutines**: All functions are coroutines. No `async/await`. Use `launch { ... }` (returns `Deferred`) or `flow { ... }`.
|
||||
- **Variables**: `val` (read-only), `var` (mutable). Supports late-init `val` in classes (must be assigned in `init` or body).
|
||||
- **Serialization**: Use `@Transient` attribute before `val`/`var` or constructor parameters to exclude them from Lynon/JSON serialization. Transient fields are also ignored during `==` structural equality checks.
|
||||
- **Null Safety**: `?` (nullable type), `?.` (safe access), `?( )` (safe invoke), `?{ }` (safe block invoke), `?[ ]` (safe index), `?:` or `??` (elvis), `?=` (assign-if-null).
|
||||
- **Equality**: `==` (equals), `!=` (not equals), `===` (ref identity), `!==` (ref not identity).
|
||||
- **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1).
|
||||
@@ -32,7 +33,7 @@ High-density specification for LLMs. Reference this for all Lyng code generation
|
||||
val area get = π * r * r
|
||||
```
|
||||
- **Mandatory `override`**: Required for all members existing in the ancestor chain.
|
||||
- **Visibility**: `public` (default), `protected` (subclasses), `private` (this class instance only). `private set` / `protected set` allowed on properties.
|
||||
- **Visibility**: `public` (default), `protected` (subclasses and ancestors for overrides), `private` (this class instance only). `private set` / `protected set` allowed on properties.
|
||||
- **Disambiguation**: `this@Base.member()` or `(obj as Base).member()`. `as` returns a qualified view.
|
||||
- **Abstract/Interface**: `interface` is a synonym for `abstract class`. Both support state and constructors.
|
||||
- **Extensions**: `fun Class.ext()` or `val Class.ext get = ...`. Scope-isolated.
|
||||
@@ -93,7 +94,7 @@ m += "c" => 3
|
||||
val [first, middle..., last] = [1, 2, 3, 4, 5]
|
||||
|
||||
// Safe Navigation and Elvis
|
||||
val companyName = person?.job?.company?.name ?? "Freelancer"
|
||||
val companyName = person?.job?.company?.name ?: "Freelancer"
|
||||
```
|
||||
|
||||
## 8. Standard Library Discovery
|
||||
|
||||
+32
-11
@@ -425,7 +425,7 @@ Key rules and features:
|
||||
|
||||
- Visibility
|
||||
- `private`: accessible only inside the declaring class body; not visible in subclasses and cannot be accessed via `this@Type` or casts.
|
||||
- `protected`: accessible in the declaring class and in any of its transitive subclasses (including MI), but not from unrelated contexts; qualification/casts do not bypass it.
|
||||
- `protected`: accessible in the declaring class and in any of its transitive subclasses (including MI). Additionally, ancestor classes can access protected members of their descendants if it's an override of a member known to the ancestor. Protected members are not visible from unrelated contexts; qualification/casts do not bypass it.
|
||||
|
||||
## Abstract Classes and Members
|
||||
|
||||
@@ -857,23 +857,44 @@ Private fields are visible only _inside the class instance_:
|
||||
void
|
||||
>>> void
|
||||
|
||||
### Transient fields
|
||||
|
||||
You can mark a field or a constructor parameter as transient using the `@Transient` attribute. Transient members are ignored during serialization (Lynon and JSON) and are also excluded from structural equality (`==`) checks.
|
||||
|
||||
```lyng
|
||||
class Session(@Transient val token, val userId) {
|
||||
@Transient var lastAccess = time.now()
|
||||
var data = Map()
|
||||
}
|
||||
```
|
||||
|
||||
For more details on how transient fields behave during restoration, see the [Serialization Guide](serialization.md).
|
||||
|
||||
### Protected members
|
||||
|
||||
Protected members are available to the declaring class and all of its transitive subclasses (including via MI), but not from unrelated contexts:
|
||||
Protected members are available to the declaring class and all of its transitive subclasses (including via MI). Additionally, an ancestor class can access a `protected` member of its descendant if the ancestor also defines or inherits a member with the same name (i.e., it is an override of something the ancestor knows about).
|
||||
|
||||
```
|
||||
class A() {
|
||||
protected fun ping() { "pong" }
|
||||
}
|
||||
class B() : A() {
|
||||
fun call() { this@A.ping() }
|
||||
Protected members are not available from unrelated contexts:
|
||||
|
||||
```lyng
|
||||
class Base {
|
||||
abstract protected fun foo()
|
||||
|
||||
fun bar() {
|
||||
// Ancestor can see foo() because it's an override
|
||||
// of a member it defines (even as abstract):
|
||||
foo()
|
||||
}
|
||||
}
|
||||
|
||||
val b = B()
|
||||
assertEquals("pong", b.call())
|
||||
class Derived : Base {
|
||||
override protected fun foo() { "ok" }
|
||||
}
|
||||
|
||||
assertEquals("ok", Derived().bar())
|
||||
|
||||
// Unrelated access is forbidden, even via cast
|
||||
assertThrows { (b as A).ping() }
|
||||
assertThrows { (Derived() as Base).foo() }
|
||||
```
|
||||
|
||||
It is possible to provide private constructor parameters so they can be
|
||||
|
||||
+4
-3
@@ -98,10 +98,11 @@ Exclusive end char ranges are supported too:
|
||||
| isEndInclusive | true for '..' | Bool |
|
||||
| isOpen | at any end | Bool |
|
||||
| isIntRange | both start and end are Int | Bool |
|
||||
| start | | Bool |
|
||||
| end | | Bool |
|
||||
| start | | Any? |
|
||||
| end | | Any? |
|
||||
| size | for finite ranges, see above | Long |
|
||||
| [] | see above | |
|
||||
| | | |
|
||||
|
||||
Ranges are also used with the `clamp(value, range)` function and the `value.clamp(range)` extension method to limit values within boundaries.
|
||||
|
||||
[Iterable]: Iterable.md
|
||||
@@ -19,6 +19,7 @@ you can use it's class to ensure type:
|
||||
|-----------------|-------------------------------------------------------------|------|
|
||||
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
||||
| `.toInt()` | convert integer part of real to `Int` dropping decimal part | Int |
|
||||
| `.clamp(range)` | clamp value within range boundaries | Real |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
|
||||
@@ -20,7 +20,18 @@ Simple classes serialization is supported:
|
||||
assertEquals( "{\"foo\":1,\"bar\":2}", Point(1,2).toJsonString() )
|
||||
>>> void
|
||||
|
||||
Note that mutable members are serialized:
|
||||
Note that mutable members are serialized by default. You can exclude any member (including constructor parameters) from JSON serialization using the `@Transient` attribute:
|
||||
|
||||
import lyng.serialization
|
||||
|
||||
class Point2(@Transient val foo, val bar) {
|
||||
@Transient var reason = 42
|
||||
var visible = 100
|
||||
}
|
||||
assertEquals( "{\"bar\":2,\"visible\":100}", Point2(1,2).toJsonString() )
|
||||
>>> void
|
||||
|
||||
Note that if you override json serialization:
|
||||
|
||||
import lyng.serialization
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ or transformed `Real` otherwise.
|
||||
| pow(x, y) | ${x^y}$ |
|
||||
| sqrt(x) | $ \sqrt {x}$ |
|
||||
| abs(x) | absolute value of x. Int if x is Int, Real otherwise |
|
||||
| clamp(x, range) | limit x to be inside range boundaries |
|
||||
|
||||
For example:
|
||||
|
||||
@@ -102,6 +103,11 @@ For example:
|
||||
// abs() keeps the argument type:
|
||||
assert( abs(-1) is Int)
|
||||
assert( abs(-2.21) == 2.21 )
|
||||
|
||||
// clamp() limits value to the range:
|
||||
assert( clamp(15, 0..10) == 10 )
|
||||
assert( clamp(-5, 0..10) == 0 )
|
||||
assert( 5.clamp(0..10) == 5 )
|
||||
>>> void
|
||||
|
||||
## Scientific constant
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/env lyng
|
||||
|
||||
import lyng.io.fs
|
||||
import lyng.stdlib
|
||||
|
||||
val files = Path("../..").list().toList()
|
||||
// most long is longest?
|
||||
|
||||
+25
-8
@@ -20,20 +20,37 @@ It is as simple as:
|
||||
assert( text.length > encodedBits.toBuffer().size )
|
||||
>>> void
|
||||
|
||||
Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields:
|
||||
Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields.
|
||||
|
||||
import lyng.serialization
|
||||
## Transient Fields
|
||||
|
||||
class Point(x,y)
|
||||
Sometimes you have fields that should not be serialized, for example, temporary caches, secret data, or derived values that are recomputed in `init` blocks. You can mark such fields with the `@Transient` attribute:
|
||||
|
||||
val p = Lynon.decode( Lynon.encode( Point(5,6) ) )
|
||||
```lyng
|
||||
class MyData(@Transient val tempSecret, val publicData) {
|
||||
@Transient var cachedValue = 0
|
||||
var persistentValue = 42
|
||||
|
||||
assertEquals( 5, p.x )
|
||||
assertEquals( 6, p.y )
|
||||
>>> void
|
||||
init {
|
||||
// cachedValue can be recomputed here upon deserialization
|
||||
cachedValue = computeCache(publicData)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Transient fields:
|
||||
- Are **omitted** from Lynon binary streams.
|
||||
- Are **omitted** from JSON output (via `toJson`).
|
||||
- Are **ignored** during structural equality checks (`==`).
|
||||
- If a transient constructor parameter has a **default value**, it will be restored to that default value during deserialization. Otherwise, it will be `null`.
|
||||
- Class body fields marked as `@Transient` will keep their initial values (or values assigned in `init`) after deserialization.
|
||||
|
||||
just as expected.
|
||||
## Serialization of Objects and Classes
|
||||
|
||||
- **Singleton Objects**: `object` declarations are serializable by name. Their state (mutable fields) is also serialized and restored, respecting `@Transient`.
|
||||
- **Classes**: Class objects themselves can be serialized. They are serialized by their full qualified name. When converted to JSON, a class object includes its public static fields (excluding those marked `@Transient`).
|
||||
|
||||
## Custom Serialization
|
||||
|
||||
Important is to understand that normally `Lynon.decode` wants [BitBuffer], as `Lynon.encode` produces. If you have the regular [Buffer], be sure to convert it:
|
||||
|
||||
|
||||
+75
-136
@@ -2,166 +2,105 @@
|
||||
|
||||
Lyng date and time support requires importing `lyng.time` packages. Lyng uses simple yet modern time object models:
|
||||
|
||||
- `Instant` class for time stamps with platform-dependent resolution
|
||||
- `Duration` to represent amount of time not depending on the calendar, e.g. in absolute units (milliseconds, seconds,
|
||||
hours, days)
|
||||
- `Instant` class for absolute time stamps with platform-dependent resolution.
|
||||
- `DateTime` class for calendar-aware points in time within a specific time zone.
|
||||
- `Duration` to represent amount of time not depending on the calendar (e.g., milliseconds, seconds).
|
||||
|
||||
## Time instant: `Instant`
|
||||
|
||||
Represent some moment of time not depending on the calendar (calendar for example may b e changed, daylight saving time
|
||||
can be for example introduced or dropped). It is similar to `TIMESTAMP` in SQL or `Instant` in Kotlin. Some moment of
|
||||
time; not the calendar date.
|
||||
Represent some moment of time not depending on the calendar. It is similar to `TIMESTAMP` in SQL or `Instant` in Kotlin.
|
||||
|
||||
Instant is comparable to other Instant. Subtracting instants produce `Duration`, period in time that is not dependent on
|
||||
the calendar, e.g. absolute time period.
|
||||
|
||||
It is possible to add or subtract `Duration` to and from `Instant`, that gives another `Instant`.
|
||||
|
||||
Instants are converted to and from `Real` number of seconds before or after Unix Epoch, 01.01.1970. Constructor with
|
||||
single number parameter constructs from such number of seconds,
|
||||
and any instance provide `.epochSeconds` member:
|
||||
### Constructing and converting
|
||||
|
||||
import lyng.time
|
||||
|
||||
// default constructor returns time now:
|
||||
val t1 = Instant()
|
||||
val t2 = Instant()
|
||||
assert( t2 - t1 < 1.millisecond )
|
||||
assert( t2.epochSeconds - t1.epochSeconds < 0.001 )
|
||||
>>> void
|
||||
|
||||
## Constructing
|
||||
|
||||
import lyng.time
|
||||
|
||||
// empty constructor gives current time instant using system clock:
|
||||
val now = Instant()
|
||||
|
||||
// constructor with Instant instance makes a copy:
|
||||
assertEquals( now, Instant(now) )
|
||||
|
||||
// constructing from a number is trated as seconds since unix epoch:
|
||||
val copyOfNow = Instant( now.epochSeconds )
|
||||
|
||||
// note that instant resolution is higher that Real can hold
|
||||
// so reconstructed from real slightly differs:
|
||||
assert( abs( (copyOfNow - now).milliseconds ) < 0.01 )
|
||||
>>> void
|
||||
|
||||
The resolution of system clock could be more precise and double precision real number of `Real`, keep it in mind.
|
||||
|
||||
## Comparing and calculating periods
|
||||
|
||||
import lyng.time
|
||||
|
||||
val now = Instant()
|
||||
// constructing from a number is treated as seconds since unix epoch:
|
||||
val t2 = Instant(1704110400) // 2024-01-01T12:00:00Z
|
||||
|
||||
// you cam add or subtract periods, and compare
|
||||
assert( now - 5.minutes < now )
|
||||
val oneHourAgo = now - 1.hour
|
||||
assertEquals( now, oneHourAgo + 1.hour)
|
||||
// from RFC3339 string:
|
||||
val t3 = Instant("2024-01-01T12:00:00.123456Z")
|
||||
|
||||
// truncation:
|
||||
val t4 = t3.truncateToMinute
|
||||
assertEquals(t4.toRFC3339(), "2024-01-01T12:00:00Z")
|
||||
|
||||
// to localized DateTime (uses system default TZ if not specified):
|
||||
val dt = t3.toDateTime("+02:00")
|
||||
assertEquals(dt.hour, 14)
|
||||
|
||||
>>> void
|
||||
|
||||
## Getting the max precision
|
||||
|
||||
Normally, subtracting instants gives precision to microseconds, which is well inside the jitter
|
||||
the language VM adds. Still `Instant()` or `Instant.now()` capture most precise system timer at hand and provide inner
|
||||
value of 12 bytes, up to nanoseconds (hopefully). To access it use:
|
||||
|
||||
import lyng.time
|
||||
|
||||
// capture time
|
||||
val now = Instant.now()
|
||||
|
||||
// this is Int value, number of whole epoch
|
||||
// milliseconds to the moment, it fits 8 bytes Int well
|
||||
val seconds = now.epochWholeSeconds
|
||||
assert(seconds is Int)
|
||||
|
||||
// and this is Int value of nanoseconds _since_ the epochMillis,
|
||||
// it effectively add 4 more mytes int:
|
||||
val nanos = now.nanosecondsOfSecond
|
||||
assert(nanos is Int)
|
||||
assert( nanos in 0..999_999_999 )
|
||||
|
||||
// we can construct epochSeconds from these parts:
|
||||
assertEquals( now.epochSeconds, nanos * 1e-9 + seconds )
|
||||
>>> void
|
||||
|
||||
## Truncating to more realistic precision
|
||||
|
||||
Full precision Instant is way too long and impractical to store, especially when serializing,
|
||||
so it is possible to truncate it to milliseconds, microseconds or seconds:
|
||||
|
||||
import lyng.time
|
||||
import lyng.serialization
|
||||
|
||||
// max supported size (now microseconds for serialized value):
|
||||
// note that encoding return _bit array_ and this is a _bit size_:
|
||||
val s0 = Lynon.encode(Instant.now()).size
|
||||
|
||||
// shorter: milliseconds only
|
||||
val s1 = Lynon.encode(Instant.now().truncateToMillisecond()).size
|
||||
|
||||
// truncated to seconds, good for file mtime, etc:
|
||||
val s2 = Lynon.encode(Instant.now().truncateToSecond()).size
|
||||
assert( s1 < s0 )
|
||||
assert( s2 < s1 )
|
||||
>>> void
|
||||
|
||||
## Formatting instants
|
||||
|
||||
You can freely use `Instant` in string formatting. It supports usual sprintf-style formats:
|
||||
|
||||
import lyng.time
|
||||
val now = Instant()
|
||||
|
||||
// will be something like "now: 12:10:05"
|
||||
val currentTimeOnly24 = "now: %tT"(now)
|
||||
|
||||
// we can extract epoch second with formatting too,
|
||||
// this was since early C time
|
||||
|
||||
// get epoch while seconds from formatting
|
||||
val unixEpoch = "Now is %ts since unix epoch"(now)
|
||||
|
||||
// and it is the same as now.epochSeconds, int part:
|
||||
assertEquals( unixEpoch, "Now is %d since unix epoch"(now.epochSeconds.toInt()) )
|
||||
>>> void
|
||||
|
||||
See
|
||||
the [complete list of available formats](https://github.com/sergeych/mp_stools?tab=readme-ov-file#datetime-formatting)
|
||||
and the [formatting reference](https://github.com/sergeych/mp_stools?tab=readme-ov-file#printf--sprintf): it all works
|
||||
in Lyng as `"format"(args...)`!
|
||||
|
||||
## Instant members
|
||||
### Instant members
|
||||
|
||||
| member | description |
|
||||
|--------------------------------|---------------------------------------------------------|
|
||||
| epochSeconds: Real | positive or negative offset in seconds since Unix epoch |
|
||||
| epochWholeSeconds: Int | same, but in _whole seconds_. Slightly faster |
|
||||
| nanosecondsOfSecond: Int | offset from epochWholeSeconds in nanos (1) |
|
||||
| nanosecondsOfSecond: Int | offset from epochWholeSeconds in nanos |
|
||||
| isDistantFuture: Bool | true if it `Instant.distantFuture` |
|
||||
| isDistantPast: Bool | true if it `Instant.distantPast` |
|
||||
| truncateToSecond: Intant | create new instnce truncated to second |
|
||||
| truncateToMillisecond: Instant | truncate new instance with to millisecond |
|
||||
| truncateToMinute: Instant | create new instance truncated to minute |
|
||||
| truncateToSecond: Instant | create new instance truncated to second |
|
||||
| truncateToMillisecond: Instant | truncate new instance to millisecond |
|
||||
| truncateToMicrosecond: Instant | truncate new instance to microsecond |
|
||||
| toRFC3339(): String | format as RFC3339 string (UTC) |
|
||||
| toDateTime(tz?): DateTime | localize to a TimeZone (ID string or offset seconds) |
|
||||
|
||||
(1)
|
||||
: The value of nanoseconds is to be added to `epochWholeSeconds` to get exact time point. It is in 0..999_999_999 range.
|
||||
The precise time instant value therefore needs as for now 12 bytes integer; we might use bigint later (it is planned to
|
||||
be added)
|
||||
## Calendar time: `DateTime`
|
||||
|
||||
## Class members
|
||||
`DateTime` represents a point in time in a specific timezone. It provides access to calendar components like year,
|
||||
month, and day.
|
||||
|
||||
| member | description |
|
||||
|--------------------------------|----------------------------------------------|
|
||||
| Instant.now() | create new instance with current system time |
|
||||
| Instant.distantPast: Instant | most distant instant in past |
|
||||
| Instant.distantFuture: Instant | most distant instant in future |
|
||||
### Constructing
|
||||
|
||||
# `Duraion` class
|
||||
import lyng.time
|
||||
|
||||
// Current time in system default timezone
|
||||
val now = DateTime.now()
|
||||
|
||||
// Specific timezone
|
||||
val offsetTime = DateTime.now("+02:00")
|
||||
|
||||
// From Instant
|
||||
val dt = Instant().toDateTime("Z")
|
||||
|
||||
// By components (year, month, day, hour=0, minute=0, second=0, timeZone="UTC")
|
||||
val dt2 = DateTime(2024, 1, 1, 12, 0, 0, "Z")
|
||||
|
||||
// From RFC3339 string
|
||||
val dt3 = DateTime.parseRFC3339("2024-01-01T12:00:00+02:00")
|
||||
|
||||
### DateTime members
|
||||
|
||||
| member | description |
|
||||
|----------------------------------|-----------------------------------------------|
|
||||
| year: Int | year component |
|
||||
| month: Int | month component (1..12) |
|
||||
| day: Int | day of month (alias `dayOfMonth`) |
|
||||
| hour: Int | hour component (0..23) |
|
||||
| minute: Int | minute component (0..59) |
|
||||
| second: Int | second component (0..59) |
|
||||
| dayOfWeek: Int | day of week (1=Monday, 7=Sunday) |
|
||||
| timeZone: String | timezone ID string |
|
||||
| toInstant(): Instant | convert back to absolute Instant |
|
||||
| toUTC(): DateTime | shortcut to convert to UTC |
|
||||
| toTimeZone(tz): DateTime | convert to another timezone |
|
||||
| addMonths(n): DateTime | add/subtract months (normalizes end of month) |
|
||||
| addYears(n): DateTime | add/subtract years |
|
||||
| toRFC3339(): String | format with timezone offset |
|
||||
| static now(tz?): DateTime | create DateTime with current time |
|
||||
| static parseRFC3339(s): DateTime | parse RFC3339 string |
|
||||
|
||||
### Arithmetic and normalization
|
||||
|
||||
`DateTime` handles calendar arithmetic correctly:
|
||||
|
||||
val leapDay = Instant("2024-02-29T12:00:00Z").toDateTime("Z")
|
||||
val nextYear = leapDay.addYears(1)
|
||||
assertEquals(nextYear.day, 28) // Feb 29, 2024 -> Feb 28, 2025
|
||||
|
||||
# `Duration` class
|
||||
|
||||
Represent absolute time distance between two `Instant`.
|
||||
|
||||
|
||||
+4
-1
@@ -1484,6 +1484,9 @@ A typical set of String functions includes:
|
||||
| lower(), lowercase() | change case to unicode upper |
|
||||
| upper(), uppercase() | change case to unicode lower |
|
||||
| trim() | trim space chars from both ends |
|
||||
| isEmpty() | true if string is empty |
|
||||
| isNotEmpty() | true if string is not empty |
|
||||
| isBlank() | true if empty or contains only whitespace |
|
||||
| startsWith(prefix) | true if starts with a prefix |
|
||||
| endsWith(prefix) | true if ends with a prefix |
|
||||
| last() | get last character of a string or throw |
|
||||
@@ -1701,7 +1704,7 @@ assertEquals(null, (buzz as? Foo)?.runA())
|
||||
|
||||
Notes:
|
||||
- Resolution order uses C3 MRO (active): deterministic, monotonic order suitable for diamonds and complex hierarchies. Example: for `class D() : B(), C()` where both `B()` and `C()` derive from `A()`, the C3 order is `D → B → C → A`. The first visible match wins.
|
||||
- `private` is visible only inside the declaring class; `protected` is visible from the declaring class and any of its transitive subclasses. Qualialsofication (`this@Type`) or casts do not bypass visibility.
|
||||
- `private` is visible only inside the declaring class; `protected` is visible from the declaring class and its subclasses. Additionally, ancestors can access protected members of descendants if they override a member known to the ancestor. Qualification (`this@Type`) or casts do not bypass visibility.
|
||||
- Safe‑call `?.` works with `as?` for optional dispatch.
|
||||
|
||||
## Extension members
|
||||
|
||||
@@ -24,6 +24,9 @@ class Person(private var _age: Int) {
|
||||
### Private and Protected Setters
|
||||
You can now restrict the visibility of a property's or field's setter using `private set` or `protected set`. This allows members to be publicly readable but only writable from within the declaring class or its subclasses.
|
||||
|
||||
### Refined Protected Visibility
|
||||
Ancestor classes can now access `protected` members of their descendants if it is an override of a member known to the ancestor. This enables base classes to call protected methods that are implemented or overridden in subclasses.
|
||||
|
||||
```lyng
|
||||
class Counter {
|
||||
var count = 0
|
||||
@@ -168,6 +171,41 @@ settings["theme"] ?= "dark"
|
||||
|
||||
The operator returns the final value of the receiver (the original value if it was not `null`, or the new value if the assignment occurred).
|
||||
|
||||
### Transient Attribute (`@Transient`)
|
||||
The `@Transient` attribute can now be applied to class fields, constructor parameters, and static fields to exclude them from serialization.
|
||||
|
||||
```lyng
|
||||
class MyData(@Transient val tempSecret, val publicData) {
|
||||
@Transient var cachedValue = 0
|
||||
var persistentValue = 42
|
||||
}
|
||||
```
|
||||
|
||||
Key features:
|
||||
- **Serialization**: Transient members are omitted from both Lynon binary streams and JSON output.
|
||||
- **Structural Equality**: Transient fields are automatically ignored during `==` equality checks.
|
||||
- **Deserialization**: Transient constructor parameters with default values are correctly restored to those defaults upon restoration.
|
||||
|
||||
### Value Clamping (`clamp`)
|
||||
A new `clamp()` function has been added to the standard library to limit a value within a specified range. It is available as both a global function and an extension method on all objects.
|
||||
|
||||
```lyng
|
||||
// Global function
|
||||
clamp(15, 0..10) // returns 10
|
||||
clamp(-5, 0..10) // returns 0
|
||||
|
||||
// Extension method
|
||||
val x = 15
|
||||
x.clamp(0..10) // returns 10
|
||||
|
||||
// Exclusive and open-ended ranges
|
||||
15.clamp(0..<10) // returns 9
|
||||
15.clamp(..10) // returns 10
|
||||
-5.clamp(0..) // returns 0
|
||||
```
|
||||
|
||||
`clamp()` correctly handles inclusive (`..`) and exclusive (`..<`) ranges. For discrete types like `Int` and `Char`, clamping to an exclusive upper bound returns the previous value.
|
||||
|
||||
## Tooling and Infrastructure
|
||||
|
||||
### CLI: Formatting Command
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -16,7 +16,7 @@
|
||||
#
|
||||
|
||||
#Gradle
|
||||
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
#Kotlin
|
||||
|
||||
@@ -5,6 +5,7 @@ kotlin = "2.3.0"
|
||||
android-minSdk = "24"
|
||||
android-compileSdk = "34"
|
||||
kotlinx-coroutines = "1.10.2"
|
||||
kotlinx-datetime = "0.6.1"
|
||||
mp_bintools = "0.3.2"
|
||||
firebaseCrashlyticsBuildtools = "3.0.3"
|
||||
okioVersion = "3.10.2"
|
||||
@@ -16,6 +17,7 @@ clikt-markdown = { module = "com.github.ajalt.clikt:clikt-markdown", version.ref
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||
mp_bintools = { module = "net.sergeych:mp_bintools", version.ref = "mp_bintools" }
|
||||
firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" }
|
||||
okio = { module = "com.squareup.okio:okio", version.ref = "okioVersion" }
|
||||
|
||||
@@ -139,7 +139,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
val e: Int = miniSource.offsetOf(d.range.end)
|
||||
if (offset >= s && offset <= e) {
|
||||
// For enum constant, we don't have detailed docs in MiniAst yet, but we can render a title
|
||||
return "<div class='doc-title'>enum constant ${d.name}.${name}</div>"
|
||||
return renderTitle("enum constant ${d.name}.${name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,7 +361,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
"Print values to the standard output and append a newline. Accepts any number of arguments." else
|
||||
"Print values to the standard output without a trailing newline. Accepts any number of arguments."
|
||||
val title = "function $ident(values)"
|
||||
return "<div class='doc-title'>${htmlEscape(title)}</div>" + styledMarkdown(htmlEscape(fallback))
|
||||
return renderTitle(title) + styledMarkdown(htmlEscape(fallback))
|
||||
}
|
||||
// 4b) try class members like ClassName.member with inheritance fallback
|
||||
val lhs = previousWordBefore(text, idRange.startOffset)
|
||||
@@ -519,7 +519,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
}
|
||||
}
|
||||
val sb = StringBuilder()
|
||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||
sb.append(renderTitle(title))
|
||||
sb.append(renderDocBody(d.doc))
|
||||
return sb.toString()
|
||||
}
|
||||
@@ -527,7 +527,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
private fun renderParamDoc(fn: MiniFunDecl, p: MiniParam): String {
|
||||
val title = "parameter ${p.name}${typeOf(p.type)} in ${fn.name}${signatureOf(fn)}"
|
||||
val sb = StringBuilder()
|
||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||
sb.append(renderTitle(title))
|
||||
|
||||
// Find matching @param tag
|
||||
fn.doc?.tags?.get("param")?.forEach { tag ->
|
||||
@@ -549,7 +549,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
val staticStr = if (m.isStatic) "static " else ""
|
||||
val title = "${staticStr}method $className.${m.name}(${params})${ret}"
|
||||
val sb = StringBuilder()
|
||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||
sb.append(renderTitle(title))
|
||||
sb.append(renderDocBody(m.doc))
|
||||
return sb.toString()
|
||||
}
|
||||
@@ -560,7 +560,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
val staticStr = if (m.isStatic) "static " else ""
|
||||
val title = "${staticStr}${kind} $className.${m.name}${ts}"
|
||||
val sb = StringBuilder()
|
||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||
sb.append(renderTitle(title))
|
||||
sb.append(renderDocBody(m.doc))
|
||||
return sb.toString()
|
||||
}
|
||||
@@ -579,6 +579,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
return "(${params})${ret}"
|
||||
}
|
||||
|
||||
private fun renderTitle(title: String): String {
|
||||
return "<div class='doc-title' style='margin-bottom: 0.8em;'>${htmlEscape(title)}</div>"
|
||||
}
|
||||
|
||||
private fun htmlEscape(s: String): String = buildString(s.length) {
|
||||
for (ch in s) append(
|
||||
when (ch) {
|
||||
@@ -645,7 +649,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
|
||||
private fun renderOverloads(name: String, overloads: List<MiniFunDecl>): String {
|
||||
val sb = StringBuilder()
|
||||
sb.append("<div class='doc-title'>Overloads for ").append(htmlEscape(name)).append("</div>")
|
||||
sb.append(renderTitle("Overloads for $name"))
|
||||
sb.append("<ul>")
|
||||
overloads.forEach { fn ->
|
||||
sb.append("<li><code>")
|
||||
|
||||
@@ -121,12 +121,39 @@ class LyngLexer : LexerBase() {
|
||||
|
||||
// Number
|
||||
if (ch.isDigit()) {
|
||||
// Check for hex: 0x...
|
||||
if (ch == '0' && i + 1 < endOffset && buffer[i + 1] == 'x') {
|
||||
i += 2
|
||||
while (i < endOffset && (buffer[i].isDigit() || buffer[i] in 'a'..'f' || buffer[i] in 'A'..'F')) i++
|
||||
myTokenEnd = i
|
||||
myTokenType = LyngTokenTypes.NUMBER
|
||||
return
|
||||
}
|
||||
|
||||
// Decimal or integer
|
||||
i++
|
||||
var hasDot = false
|
||||
var hasE = false
|
||||
while (i < endOffset) {
|
||||
val c = buffer[i]
|
||||
if (c.isDigit()) { i++; continue }
|
||||
if (c == '.' && !hasDot) { hasDot = true; i++; continue }
|
||||
if (c.isDigit() || c == '_') {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (c == '.' && !hasDot && !hasE) {
|
||||
// Check if it's a fractional part (must be followed by a digit)
|
||||
if (i + 1 < endOffset && buffer[i + 1].isDigit()) {
|
||||
hasDot = true
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ((c == 'e' || c == 'E') && !hasE) {
|
||||
hasE = true
|
||||
i++
|
||||
if (i < endOffset && (buffer[i] == '+' || buffer[i] == '-')) i++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
myTokenEnd = i
|
||||
@@ -161,6 +188,35 @@ class LyngLexer : LexerBase() {
|
||||
// Punctuation
|
||||
if (isPunct(ch)) {
|
||||
i++
|
||||
// Handle common multi-char operators for better highlighting
|
||||
when (ch) {
|
||||
'.' -> {
|
||||
if (i < endOffset && buffer[i] == '.') {
|
||||
i++
|
||||
if (i < endOffset && (buffer[i] == '.' || buffer[i] == '<')) i++
|
||||
}
|
||||
}
|
||||
'=' -> {
|
||||
if (i < endOffset && (buffer[i] == '=' || buffer[i] == '>' || buffer[i] == '~')) {
|
||||
i++
|
||||
if (buffer[i - 1] == '=' && i < endOffset && buffer[i] == '=') i++
|
||||
}
|
||||
}
|
||||
'+', '-', '*', '/', '%', '!', '<', '>', '&', '|', '?', ':', '^' -> {
|
||||
if (i < endOffset) {
|
||||
val next = buffer[i]
|
||||
if (next == '=' || next == ch) {
|
||||
i++
|
||||
if (ch == '<' && next == '=' && i < endOffset && buffer[i] == '>') i++
|
||||
if (ch == '!' && next == '=' && i < endOffset && buffer[i] == '=') i++
|
||||
} else if (ch == '?' && (next == '.' || next == '[' || next == '(' || next == '{' || next == ':' || next == '?')) {
|
||||
i++
|
||||
} else if (ch == '-' && next == '>') {
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
myTokenEnd = i
|
||||
myTokenType = LyngTokenTypes.PUNCT
|
||||
return
|
||||
|
||||
@@ -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.
|
||||
@@ -23,9 +23,11 @@ package net.sergeych.lyng.io.fs
|
||||
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ModuleBuilder
|
||||
import net.sergeych.lyngio.fs.LyngFS
|
||||
import net.sergeych.lyngio.fs.LyngFs
|
||||
import net.sergeych.lyngio.fs.LyngPath
|
||||
@@ -50,9 +52,11 @@ fun createFsModule(policy: FsAccessPolicy, manager: ImportManager): Boolean {
|
||||
// Avoid re-registering in this ImportManager
|
||||
if (manager.packageNames.contains(name)) return false
|
||||
|
||||
manager.addPackage(name) { module ->
|
||||
buildFsModule(module, policy)
|
||||
}
|
||||
manager.addPackage(name, object : ModuleBuilder {
|
||||
override suspend fun build(module: ModuleScope) {
|
||||
buildFsModule(module, policy)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -78,322 +82,394 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
||||
name = "name",
|
||||
doc = "Base name of the path (last segment).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjPath>()
|
||||
self.path.name.toObj()
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjPath>()
|
||||
return self.path.name.toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "parent",
|
||||
doc = "Parent directory as a Path or null if none.",
|
||||
returns = type("Path", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjPath>()
|
||||
self.path.parent?.let {
|
||||
ObjPath( this@apply, self.secured, it)
|
||||
} ?: ObjNull
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjPath>()
|
||||
return self.path.parent?.let {
|
||||
ObjPath(this@apply, self.secured, it)
|
||||
} ?: ObjNull
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "segments",
|
||||
doc = "List of path segments.",
|
||||
// returns: List<String>
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjPath>()
|
||||
ObjList(self.path.segments.map { ObjString(it) }.toMutableList())
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjPath>()
|
||||
return ObjList(self.path.segments.map { ObjString(it) }.toMutableList())
|
||||
}
|
||||
}
|
||||
)
|
||||
// exists(): Bool
|
||||
addFnDoc(
|
||||
name = "exists",
|
||||
doc = "Check whether this path exists.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
(self.secured.exists(self.path)).toObj()
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
(self.secured.exists(self.path)).toObj()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// isFile(): Bool — cached metadata
|
||||
addFnDoc(
|
||||
name = "isFile",
|
||||
doc = "True if this path is a regular file (based on cached metadata).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isRegularFile) }
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isRegularFile) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// isDirectory(): Bool — cached metadata
|
||||
addFnDoc(
|
||||
name = "isDirectory",
|
||||
doc = "True if this path is a directory (based on cached metadata).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isDirectory) }
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isDirectory) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// size(): Int? — null when unavailable
|
||||
addFnDoc(
|
||||
name = "size",
|
||||
doc = "File size in bytes, or null when unavailable.",
|
||||
returns = type("lyng.Int", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.size?.let { ObjInt(it) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.size?.let { ObjInt(it) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// createdAt(): Instant? — Lyng Instant, null when unavailable
|
||||
addFnDoc(
|
||||
name = "createdAt",
|
||||
doc = "Creation time as `Instant`, or null when unavailable.",
|
||||
returns = type("lyng.Instant", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// createdAtMillis(): Int? — milliseconds since epoch or null
|
||||
addFnDoc(
|
||||
name = "createdAtMillis",
|
||||
doc = "Creation time in milliseconds since epoch, or null when unavailable.",
|
||||
returns = type("lyng.Int", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// modifiedAt(): Instant? — Lyng Instant, null when unavailable
|
||||
addFnDoc(
|
||||
name = "modifiedAt",
|
||||
doc = "Last modification time as `Instant`, or null when unavailable.",
|
||||
returns = type("lyng.Instant", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// modifiedAtMillis(): Int? — milliseconds since epoch or null
|
||||
addFnDoc(
|
||||
name = "modifiedAtMillis",
|
||||
doc = "Last modification time in milliseconds since epoch, or null when unavailable.",
|
||||
returns = type("lyng.Int", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// list(): List<Path>
|
||||
addFnDoc(
|
||||
name = "list",
|
||||
doc = "List directory entries as `Path` objects.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) }
|
||||
ObjList(items.toMutableList())
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) }
|
||||
ObjList(items.toMutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// readBytes(): Buffer
|
||||
addFnDoc(
|
||||
name = "readBytes",
|
||||
doc = "Read the file into a binary buffer.",
|
||||
returns = type("lyng.Buffer"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjBuffer(bytes.asUByteArray())
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjBuffer(bytes.asUByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// writeBytes(bytes: Buffer)
|
||||
addFnDoc(
|
||||
name = "writeBytes",
|
||||
doc = "Write a binary buffer to the file, replacing content.",
|
||||
params = listOf(ParamDoc("bytes", type("lyng.Buffer"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val buf = requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val buf = scp.requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// appendBytes(bytes: Buffer)
|
||||
addFnDoc(
|
||||
name = "appendBytes",
|
||||
doc = "Append a binary buffer to the end of the file.",
|
||||
params = listOf(ParamDoc("bytes", type("lyng.Buffer"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val buf = requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val buf = scp.requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// readUtf8(): String
|
||||
addFnDoc(
|
||||
name = "readUtf8",
|
||||
doc = "Read the file as a UTF-8 string.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
self.secured.readUtf8(self.path).toObj()
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
self.secured.readUtf8(self.path).toObj()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// writeUtf8(text: String)
|
||||
addFnDoc(
|
||||
name = "writeUtf8",
|
||||
doc = "Write a UTF-8 string to the file, replacing content.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val text = requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = false)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val text = scp.requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = false)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// appendUtf8(text: String)
|
||||
addFnDoc(
|
||||
name = "appendUtf8",
|
||||
doc = "Append UTF-8 text to the end of the file.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val text = requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = true)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val text = scp.requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = true)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// metadata(): Map
|
||||
addFnDoc(
|
||||
name = "metadata",
|
||||
doc = "Fetch cached metadata as a map of fields: `isFile`, `isDirectory`, `size`, `createdAtMillis`, `modifiedAtMillis`, `isSymlink`.",
|
||||
returns = TypeGenericDoc(type("lyng.Map"), listOf(type("lyng.String"), type("lyng.Any"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.secured.metadata(self.path)
|
||||
ObjMap(mutableMapOf(
|
||||
ObjString("isFile") to ObjBool(m.isRegularFile),
|
||||
ObjString("isDirectory") to ObjBool(m.isDirectory),
|
||||
ObjString("size") to (m.size?.toLong() ?: 0L).toObj(),
|
||||
ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(),
|
||||
ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(),
|
||||
ObjString("isSymlink") to ObjBool(m.isSymlink),
|
||||
))
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.secured.metadata(self.path)
|
||||
ObjMap(mutableMapOf(
|
||||
ObjString("isFile") to ObjBool(m.isRegularFile),
|
||||
ObjString("isDirectory") to ObjBool(m.isDirectory),
|
||||
ObjString("size") to (m.size?.toLong() ?: 0L).toObj(),
|
||||
ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(),
|
||||
ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(),
|
||||
ObjString("isSymlink") to ObjBool(m.isSymlink),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// mkdirs(mustCreate: Bool=false)
|
||||
addFnDoc(
|
||||
name = "mkdirs",
|
||||
doc = "Create directories (like `mkdir -p`). If `mustCreate` is true and the path already exists, the call fails. Otherwise it is a no‑op when the directory exists.",
|
||||
params = listOf(ParamDoc("mustCreate", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val mustCreate = args.list.getOrNull(0)?.toBool() ?: false
|
||||
self.secured.createDirectories(self.path, mustCreate)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val mustCreate = scp.args.list.getOrNull(0)?.toBool() ?: false
|
||||
self.secured.createDirectories(self.path, mustCreate)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// move(to: Path|String, overwrite: Bool=false)
|
||||
addFnDoc(
|
||||
name = "move",
|
||||
doc = "Move this path to a new location. `to` may be a `Path` or `String`. When `overwrite` is false and the target exists, the operation fails (provider may throw `AccessDeniedException`).",
|
||||
// types vary; keep generic description in doc
|
||||
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val toPath = parsePathArg(this, self, requiredArg<Obj>(0))
|
||||
val overwrite = args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.move(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val toPath = parsePathArg(scp, self, scp.requiredArg<Obj>(0))
|
||||
val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.move(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// delete(mustExist: Bool=false, recursively: Bool=false)
|
||||
addFnDoc(
|
||||
name = "delete",
|
||||
doc = "Delete this path. `mustExist=true` causes failure if the path does not exist. `recursively=true` removes directories with their contents. Providers can throw `AccessDeniedException` on policy violations.",
|
||||
params = listOf(ParamDoc("mustExist", type("lyng.Bool")), ParamDoc("recursively", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val mustExist = args.list.getOrNull(0)?.toBool() ?: false
|
||||
val recursively = args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.delete(self.path, mustExist, recursively)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val mustExist = scp.args.list.getOrNull(0)?.toBool() ?: false
|
||||
val recursively = scp.args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.delete(self.path, mustExist, recursively)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// copy(to: Path|String, overwrite: Bool=false)
|
||||
addFnDoc(
|
||||
name = "copy",
|
||||
doc = "Copy this path to a new location. `to` may be a `Path` or `String`. When `overwrite` is false and the target exists, the operation fails (provider may throw `AccessDeniedException`).",
|
||||
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val toPath = parsePathArg(this, self, requiredArg<Obj>(0))
|
||||
val overwrite = args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.copy(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val toPath = parsePathArg(scp, self, scp.requiredArg<Obj>(0))
|
||||
val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.copy(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// glob(pattern: String): List<Path>
|
||||
addFnDoc(
|
||||
name = "glob",
|
||||
doc = "List entries matching a glob pattern (no recursion).",
|
||||
params = listOf(ParamDoc("pattern", type("lyng.String"))),
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val pattern = requireOnlyArg<ObjString>().value
|
||||
val matches = self.secured.glob(self.path, pattern)
|
||||
ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList())
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val pattern = scp.requireOnlyArg<ObjString>().value
|
||||
val matches = self.secured.glob(self.path, pattern)
|
||||
ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// --- streaming readers (initial version: chunk from whole content, API stable) ---
|
||||
|
||||
@@ -403,15 +479,18 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
||||
doc = "Read file in fixed-size chunks as an iterator of `Buffer`.",
|
||||
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val size = args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjFsBytesIterator(bytes, size)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjFsBytesIterator(bytes, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// readUtf8Chunks(size: Int = 65536) -> Iterator<String>
|
||||
addFnDoc(
|
||||
@@ -419,28 +498,34 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
||||
doc = "Read UTF-8 text in fixed-size chunks as an iterator of `String`.",
|
||||
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val size = args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val text = self.secured.readUtf8(self.path)
|
||||
ObjFsStringChunksIterator(text, size)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val text = self.secured.readUtf8(self.path)
|
||||
ObjFsStringChunksIterator(text, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// lines() -> Iterator<String>, implemented via readUtf8Chunks
|
||||
addFnDoc(
|
||||
name = "lines",
|
||||
doc = "Iterate lines of the file as `String` values.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks")
|
||||
ObjFsLinesIterator(chunkIt)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val chunkIt = scp.thisObj.invokeInstanceMethod(scp, "readUtf8Chunks")
|
||||
ObjFsLinesIterator(chunkIt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Export into the module scope with docs
|
||||
@@ -518,39 +603,51 @@ class ObjFsBytesIterator(
|
||||
name = "iterator",
|
||||
doc = "Return this iterator instance (enables `for` loops).",
|
||||
returns = type("BytesIterator"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) { thisObj }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether there is another chunk available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsBytesIterator>()
|
||||
(self.pos < self.data.size).toObj()
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsBytesIterator>()
|
||||
return (self.pos < self.data.size).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next chunk as a `Buffer`.",
|
||||
returns = type("lyng.Buffer"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsBytesIterator>()
|
||||
if (self.pos >= self.data.size) raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkSize, self.data.size)
|
||||
val chunk = self.data.copyOfRange(self.pos, end)
|
||||
self.pos = end
|
||||
ObjBuffer(chunk.asUByteArray())
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsBytesIterator>()
|
||||
if (self.pos >= self.data.size) scp.raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkSize, self.data.size)
|
||||
val chunk = self.data.copyOfRange(self.pos, end)
|
||||
self.pos = end
|
||||
return ObjBuffer(chunk.asUByteArray())
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop the iteration early; subsequent `hasNext` returns false.",
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsBytesIterator>()
|
||||
self.pos = self.data.size
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsBytesIterator>()
|
||||
self.pos = self.data.size
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,35 +670,47 @@ class ObjFsStringChunksIterator(
|
||||
name = "iterator",
|
||||
doc = "Return this iterator instance (enables `for` loops).",
|
||||
returns = type("StringChunksIterator"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) { thisObj }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether there is another chunk available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsStringChunksIterator>()
|
||||
(self.pos < self.text.length).toObj()
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsStringChunksIterator>()
|
||||
return (self.pos < self.text.length).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next UTF-8 chunk as a `String`.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsStringChunksIterator>()
|
||||
if (self.pos >= self.text.length) raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkChars, self.text.length)
|
||||
val chunk = self.text.substring(self.pos, end)
|
||||
self.pos = end
|
||||
ObjString(chunk)
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsStringChunksIterator>()
|
||||
if (self.pos >= self.text.length) scp.raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkChars, self.text.length)
|
||||
val chunk = self.text.substring(self.pos, end)
|
||||
self.pos = end
|
||||
return ObjString(chunk)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop the iteration early; subsequent `hasNext` returns false.",
|
||||
moduleName = "lyng.io.fs"
|
||||
) { ObjVoid }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -624,46 +733,58 @@ class ObjFsLinesIterator(
|
||||
name = "iterator",
|
||||
doc = "Return this iterator instance (enables `for` loops).",
|
||||
returns = type("LinesIterator"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) { thisObj }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether another line is available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(this)
|
||||
(self.buffer.isNotEmpty() || !self.exhausted).toObj()
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(scp)
|
||||
return (self.buffer.isNotEmpty() || !self.exhausted).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next line as `String`.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(this)
|
||||
if (self.buffer.isEmpty() && self.exhausted) raiseIllegalState("iterator exhausted")
|
||||
val idx = self.buffer.indexOf('\n')
|
||||
val line = if (idx >= 0) {
|
||||
val l = self.buffer.substring(0, idx)
|
||||
self.buffer = self.buffer.substring(idx + 1)
|
||||
l
|
||||
} else {
|
||||
// last line without trailing newline
|
||||
val l = self.buffer
|
||||
self.buffer = ""
|
||||
self.exhausted = true
|
||||
l
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(scp)
|
||||
if (self.buffer.isEmpty() && self.exhausted) scp.raiseIllegalState("iterator exhausted")
|
||||
val idx = self.buffer.indexOf('\n')
|
||||
val line = if (idx >= 0) {
|
||||
val l = self.buffer.substring(0, idx)
|
||||
self.buffer = self.buffer.substring(idx + 1)
|
||||
l
|
||||
} else {
|
||||
// last line without trailing newline
|
||||
val l = self.buffer
|
||||
self.buffer = ""
|
||||
self.exhausted = true
|
||||
l
|
||||
}
|
||||
return ObjString(line)
|
||||
}
|
||||
}
|
||||
ObjString(line)
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop the iteration early; subsequent `hasNext` returns false.",
|
||||
moduleName = "lyng.io.fs"
|
||||
) { ObjVoid }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,11 @@ package net.sergeych.lyng.io.process
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ModuleBuilder
|
||||
import net.sergeych.lyng.statement
|
||||
import net.sergeych.lyngio.process.*
|
||||
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
||||
@@ -39,9 +41,11 @@ fun createProcessModule(policy: ProcessAccessPolicy, manager: ImportManager): Bo
|
||||
val name = "lyng.io.process"
|
||||
if (manager.packageNames.contains(name)) return false
|
||||
|
||||
manager.addPackage(name) { module ->
|
||||
buildProcessModule(module, policy)
|
||||
}
|
||||
manager.addPackage(name, object : ModuleBuilder {
|
||||
override suspend fun build(module: ModuleScope) {
|
||||
buildProcessModule(module, policy)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -59,59 +63,74 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
|
||||
name = "stdout",
|
||||
doc = "Get standard output stream as a Flow of lines.",
|
||||
returns = type("lyng.Flow"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjRunningProcess>()
|
||||
self.process.stdout.toLyngFlow(this)
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRunningProcess>()
|
||||
return self.process.stdout.toLyngFlow(scp)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "stderr",
|
||||
doc = "Get standard error stream as a Flow of lines.",
|
||||
returns = type("lyng.Flow"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjRunningProcess>()
|
||||
self.process.stderr.toLyngFlow(this)
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRunningProcess>()
|
||||
return self.process.stderr.toLyngFlow(scp)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "signal",
|
||||
doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').",
|
||||
params = listOf(ParamDoc("signal", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
processGuard {
|
||||
val sigStr = requireOnlyArg<ObjString>().value.uppercase()
|
||||
val sig = try {
|
||||
ProcessSignal.valueOf(sigStr)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
ProcessSignal.valueOf("SIG$sigStr")
|
||||
} catch (e2: Exception) {
|
||||
raiseIllegalArgument("Unknown signal: $sigStr")
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.processGuard {
|
||||
val sigStr = scp.requireOnlyArg<ObjString>().value.uppercase()
|
||||
val sig = try {
|
||||
ProcessSignal.valueOf(sigStr)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
ProcessSignal.valueOf("SIG$sigStr")
|
||||
} catch (e2: Exception) {
|
||||
scp.raiseIllegalArgument("Unknown signal: $sigStr")
|
||||
}
|
||||
}
|
||||
scp.thisAs<ObjRunningProcess>().process.sendSignal(sig)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
thisAs<ObjRunningProcess>().process.sendSignal(sig)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "waitFor",
|
||||
doc = "Wait for the process to exit and return its exit code.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
processGuard {
|
||||
thisAs<ObjRunningProcess>().process.waitFor().toObj()
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.processGuard {
|
||||
scp.thisAs<ObjRunningProcess>().process.waitFor().toObj()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "destroy",
|
||||
doc = "Forcefully terminate the process.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
thisAs<ObjRunningProcess>().process.destroy()
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjRunningProcess>().process.destroy()
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val processType = object : ObjClass("Process") {}
|
||||
@@ -122,30 +141,36 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
|
||||
doc = "Execute a process with arguments.",
|
||||
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
|
||||
returns = type("RunningProcess"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
if (runner == null) raiseError("Processes are not supported on this platform")
|
||||
processGuard {
|
||||
val executable = requiredArg<ObjString>(0).value
|
||||
val args = requiredArg<ObjList>(1).list.map { it.toString() }
|
||||
val lp = runner.execute(executable, args)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (runner == null) scp.raiseError("Processes are not supported on this platform")
|
||||
return scp.processGuard {
|
||||
val executable = scp.requiredArg<ObjString>(0).value
|
||||
val args = scp.requiredArg<ObjList>(1).list.map { it.toString() }
|
||||
val lp = runner.execute(executable, args)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
addClassFnDoc(
|
||||
name = "shell",
|
||||
doc = "Execute a command via system shell.",
|
||||
params = listOf(ParamDoc("command", type("lyng.String"))),
|
||||
returns = type("RunningProcess"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
if (runner == null) raiseError("Processes are not supported on this platform")
|
||||
processGuard {
|
||||
val command = requireOnlyArg<ObjString>().value
|
||||
val lp = runner.shell(command)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (runner == null) scp.raiseError("Processes are not supported on this platform")
|
||||
return scp.processGuard {
|
||||
val command = scp.requireOnlyArg<ObjString>().value
|
||||
val lp = runner.shell(command)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val platformType = object : ObjClass("Platform") {}
|
||||
@@ -155,24 +180,28 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
|
||||
name = "details",
|
||||
doc = "Get platform core details.",
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val d = getPlatformDetails()
|
||||
ObjMap(mutableMapOf(
|
||||
ObjString("name") to ObjString(d.name),
|
||||
ObjString("version") to ObjString(d.version),
|
||||
ObjString("arch") to ObjString(d.arch),
|
||||
ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull)
|
||||
))
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = getPlatformDetails()
|
||||
return ObjMap(mutableMapOf(
|
||||
ObjString("name") to ObjString(d.name),
|
||||
ObjString("version") to ObjString(d.version),
|
||||
ObjString("arch") to ObjString(d.arch),
|
||||
ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull)
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
addClassFnDoc(
|
||||
name = "isSupported",
|
||||
doc = "Check if processes are supported on this platform.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
isProcessSupported().toObj()
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = isProcessSupported().toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module.addConstDoc(
|
||||
@@ -216,19 +245,21 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O
|
||||
}
|
||||
|
||||
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
|
||||
val producer = statement {
|
||||
val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||
?: this.thisObj as? ObjFlowBuilder
|
||||
val producer = statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val builder = (scp as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||
?: scp.thisObj as? ObjFlowBuilder
|
||||
|
||||
this@toLyngFlow.collect {
|
||||
try {
|
||||
builder?.output?.send(ObjString(it))
|
||||
} catch (e: Exception) {
|
||||
// Channel closed or other error, stop collecting
|
||||
return@collect
|
||||
this@toLyngFlow.collect {
|
||||
try {
|
||||
builder?.output?.send(ObjString(it))
|
||||
} catch (e: Exception) {
|
||||
// Channel closed or other error, stop collecting
|
||||
return@collect
|
||||
}
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
})
|
||||
return ObjFlow(producer, flowScope)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1-SNAPSHOT"
|
||||
|
||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||
|
||||
@@ -97,6 +97,7 @@ kotlin {
|
||||
kotlin.srcDir("$buildDir/generated/buildConfig/commonMain/kotlin")
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
||||
//put your multiplatform dependencies here
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
api(libs.mp.bintools)
|
||||
|
||||
@@ -71,7 +71,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
value.byValueCopy(),
|
||||
a.visibility ?: defaultVisibility,
|
||||
recordType = ObjRecord.Type.Argument,
|
||||
declaringClass = declaringClass)
|
||||
declaringClass = declaringClass,
|
||||
isTransient = a.isTransient)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -82,7 +83,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
value.byValueCopy(),
|
||||
a.visibility ?: defaultVisibility,
|
||||
recordType = ObjRecord.Type.Argument,
|
||||
declaringClass = declaringClass)
|
||||
declaringClass = declaringClass,
|
||||
isTransient = a.isTransient)
|
||||
}
|
||||
|
||||
// Prepare positional args and parameter count, handle tail-block binding
|
||||
@@ -152,69 +154,73 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: assign head part, consuming from headPos; stop at ellipsis
|
||||
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> {
|
||||
var i = index
|
||||
var hp = headPos
|
||||
// Locate ellipsis index within considered parameters
|
||||
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||
|
||||
var headPos = 0
|
||||
var tailPos = callArgs.size - 1
|
||||
|
||||
if (ellipsisIndex >= 0) {
|
||||
// Assign head first to know how many positionals are consumed from the start
|
||||
var i = 0
|
||||
while (i < paramsSize) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
if (assignedByName[i]) {
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||
val value = if (headPos < callArgs.size) callArgs[headPos++]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
||||
assign(a, value)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i to hp
|
||||
}
|
||||
val afterHead = i
|
||||
val headConsumedTo = headPos
|
||||
|
||||
// Helper: assign tail part from the end, consuming from tailPos; stop before ellipsis index
|
||||
// Do not consume elements below headPosBound to avoid overlap with head consumption
|
||||
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int {
|
||||
var i = paramsSize - 1
|
||||
var tp = tailStart
|
||||
while (i > startExclusive) {
|
||||
// Then assign tail consuming from the end down to headConsumedTo boundary
|
||||
i = paramsSize - 1
|
||||
var tp = tailPos
|
||||
while (i > ellipsisIndex) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
if (i < assignedByName.size && assignedByName[i]) {
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (tp >= headPosBound) callArgs[tp--]
|
||||
val value = if (tp >= headConsumedTo) callArgs[tp--]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call")
|
||||
assign(a, value)
|
||||
}
|
||||
i--
|
||||
}
|
||||
return tp
|
||||
}
|
||||
val tailConsumedFrom = tp
|
||||
|
||||
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) {
|
||||
val a = params[index]
|
||||
val from = headPos
|
||||
val to = tailPos
|
||||
// Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom
|
||||
val a = params[ellipsisIndex]
|
||||
val from = headConsumedTo
|
||||
val to = tailConsumedFrom
|
||||
val l = if (from > to) ObjList()
|
||||
else ObjList(callArgs.subList(from, to + 1).toMutableList())
|
||||
assign(a, l)
|
||||
}
|
||||
|
||||
// Locate ellipsis index within considered parameters
|
||||
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||
|
||||
if (ellipsisIndex >= 0) {
|
||||
// Assign head first to know how many positionals are consumed from the start
|
||||
val (afterHead, headConsumedTo) = processHead(0, 0)
|
||||
// Then assign tail consuming from the end down to headConsumedTo boundary
|
||||
val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo)
|
||||
// Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom
|
||||
processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom)
|
||||
} else {
|
||||
// No ellipsis: assign head only; any leftover positionals → error
|
||||
val (_, headConsumedTo) = processHead(0, 0)
|
||||
var i = 0
|
||||
while (i < paramsSize) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
if (assignedByName[i]) {
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (headPos < callArgs.size) callArgs[headPos++]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
||||
assign(a, value)
|
||||
}
|
||||
i++
|
||||
}
|
||||
val headConsumedTo = headPos
|
||||
if (headConsumedTo != callArgs.size)
|
||||
scope.raiseIllegalArgument("too many arguments for the call")
|
||||
}
|
||||
@@ -239,5 +245,6 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
val defaultValue: Statement? = null,
|
||||
val accessType: AccessType? = null,
|
||||
val visibility: Visibility? = null,
|
||||
val isTransient: Boolean = false,
|
||||
)
|
||||
}
|
||||
@@ -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.
|
||||
@@ -244,10 +244,16 @@ data class ParsedArgument(
|
||||
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
||||
*/
|
||||
suspend fun toKotlinList(scope: Scope): List<Any?> {
|
||||
return list.map { it.toKotlin(scope) }
|
||||
val res = ArrayList<Any?>(list.size)
|
||||
for (i in list) res.add(i.toKotlin(scope))
|
||||
return res
|
||||
}
|
||||
|
||||
suspend fun inspect(scope: Scope): String {
|
||||
val res = ArrayList<String>(list.size)
|
||||
for (i in list) res.add(i.inspect(scope))
|
||||
return res.joinToString(",")
|
||||
}
|
||||
|
||||
suspend fun inspect(scope: Scope): String = list.map{ it.inspect(scope)}.joinToString(",")
|
||||
|
||||
companion object {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
|
||||
@@ -54,14 +54,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
for (cls in effectiveClass.mro) {
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, root object fallback
|
||||
Obj.rootObjectType.members[name]?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -21,6 +21,23 @@ import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ImportProvider
|
||||
|
||||
interface ScopeCallable {
|
||||
suspend fun call(scope: Scope): Obj
|
||||
}
|
||||
|
||||
interface VoidScopeCallable {
|
||||
suspend fun call(scope: Scope)
|
||||
}
|
||||
|
||||
interface ScopeBlock<R> {
|
||||
suspend fun call(scope: Scope): R
|
||||
}
|
||||
|
||||
private class FnStatement(val fn: ScopeCallable) : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(scope: Scope): Obj = fn.call(scope)
|
||||
}
|
||||
|
||||
// Simple per-frame id generator for perf caches (not thread-safe, fine for scripts)
|
||||
object FrameIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
||||
fun nextFrameId(): Long = FrameIdGen.nextId()
|
||||
@@ -112,7 +129,7 @@ open class Scope(
|
||||
}
|
||||
}
|
||||
s.objects[name]?.let { rec ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
caller?.let { ctx ->
|
||||
s.localBindings[ctx.mangledName(name)]?.let { rec ->
|
||||
@@ -120,11 +137,11 @@ open class Scope(
|
||||
}
|
||||
}
|
||||
s.localBindings[name]?.let { rec ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
s.getSlotIndexOf(name)?.let { idx ->
|
||||
val rec = s.getSlotRecord(idx)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -163,7 +180,7 @@ open class Scope(
|
||||
this.extensions[cls]?.get(name)?.let { return it }
|
||||
}
|
||||
return thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||
else rec
|
||||
} else null
|
||||
@@ -186,7 +203,7 @@ open class Scope(
|
||||
s.extensions[cls]?.get(name)?.let { return it }
|
||||
}
|
||||
s.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, caller)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, caller, name)) {
|
||||
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) {
|
||||
// ignore fields, properties and abstracts here, they will be handled by the caller via readField
|
||||
} else return rec
|
||||
@@ -358,14 +375,14 @@ open class Scope(
|
||||
for (cls in effectiveClass.mro) {
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, root object fallback
|
||||
Obj.rootObjectType.members[name]?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
@@ -447,17 +464,17 @@ open class Scope(
|
||||
* Execute a block inside a child frame. Guarded for future pooling via [PerfFlags.SCOPE_POOL].
|
||||
* Currently always creates a fresh child scope to preserve unique frameId semantics.
|
||||
*/
|
||||
inline suspend fun <R> withChildFrame(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null, crossinline block: suspend (Scope) -> R): R {
|
||||
suspend fun <R> withChildFrame(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null, block: ScopeBlock<R>): R {
|
||||
if (PerfFlags.SCOPE_POOL) {
|
||||
val child = ScopePool.borrow(this, args, pos, newThisObj ?: thisObj)
|
||||
try {
|
||||
return block(child)
|
||||
return block.call(child)
|
||||
} finally {
|
||||
ScopePool.release(child)
|
||||
}
|
||||
} else {
|
||||
val child = createChildScope(args, newThisObj)
|
||||
return block(child)
|
||||
return block.call(child)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,7 +533,8 @@ open class Scope(
|
||||
declaringClass: net.sergeych.lyng.obj.ObjClass? = currentClassCtx,
|
||||
isAbstract: Boolean = false,
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false
|
||||
isOverride: Boolean = false,
|
||||
isTransient: Boolean = false
|
||||
): ObjRecord {
|
||||
val rec = ObjRecord(
|
||||
value, isMutable, visibility, writeVisibility,
|
||||
@@ -524,7 +542,8 @@ open class Scope(
|
||||
type = recordType,
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient
|
||||
)
|
||||
objects[name] = rec
|
||||
// Index this binding within the current frame to help resolve locals across suspension
|
||||
@@ -561,20 +580,17 @@ open class Scope(
|
||||
return ns.objClass
|
||||
}
|
||||
|
||||
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) {
|
||||
addFn(*names) {
|
||||
fn(this)
|
||||
ObjVoid
|
||||
}
|
||||
fun addVoidFn(vararg names: String, fn: VoidScopeCallable) {
|
||||
addFn(*names, fn = object : ScopeCallable {
|
||||
override suspend fun call(scope: Scope): Obj {
|
||||
fn.call(scope)
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
||||
val newFn = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj = scope.fn()
|
||||
|
||||
}
|
||||
fun addFn(vararg names: String, fn: ScopeCallable) {
|
||||
val newFn = FnStatement(fn)
|
||||
for (name in names) {
|
||||
addItem(
|
||||
name,
|
||||
|
||||
@@ -18,17 +18,13 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.yield
|
||||
import net.sergeych.lyng.Script.Companion.defaultImportManager
|
||||
import net.sergeych.lyng.miniast.addConstDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addVoidFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ModuleBuilder
|
||||
import net.sergeych.lyng.stdlib_included.rootLyng
|
||||
import net.sergeych.lynon.ObjLynonClass
|
||||
import net.sergeych.mp_tools.globalDefer
|
||||
import kotlin.math.*
|
||||
|
||||
@Suppress("TYPE_INTERSECTION_AS_REIFIED_WARNING")
|
||||
@@ -62,150 +58,173 @@ class Script(
|
||||
internal val rootScope: Scope = Scope(null).apply {
|
||||
ObjException.addExceptionsToContext(this)
|
||||
addConst("Unset", ObjUnset)
|
||||
addFn("print") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(this).value)
|
||||
else print(a.toString(this).value)
|
||||
addFn("print", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
for ((i, a) in scp.args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(scp).value)
|
||||
else print(a.toString(scp).value)
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
addFn("println") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(this).value)
|
||||
else print(a.toString(this).value)
|
||||
})
|
||||
addFn("println", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
for ((i, a) in scp.args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(scp).value)
|
||||
else print(a.toString(scp).value)
|
||||
}
|
||||
println()
|
||||
return ObjVoid
|
||||
}
|
||||
println()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("floor") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(floor(x.toDouble())))
|
||||
}
|
||||
addFn("ceil") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(ceil(x.toDouble())))
|
||||
}
|
||||
addFn("round") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(round(x.toDouble())))
|
||||
}
|
||||
})
|
||||
addFn("floor", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return (if (x is ObjInt) x
|
||||
else ObjReal(floor(x.toDouble())))
|
||||
}
|
||||
})
|
||||
addFn("ceil", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return (if (x is ObjInt) x
|
||||
else ObjReal(ceil(x.toDouble())))
|
||||
}
|
||||
})
|
||||
addFn("round", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return (if (x is ObjInt) x
|
||||
else ObjReal(round(x.toDouble())))
|
||||
}
|
||||
})
|
||||
|
||||
addFn("sin") {
|
||||
ObjReal(sin(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("cos") {
|
||||
ObjReal(cos(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("tan") {
|
||||
ObjReal(tan(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("asin") {
|
||||
ObjReal(asin(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("acos") {
|
||||
ObjReal(acos(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("atan") {
|
||||
ObjReal(atan(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("sin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sin(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("cos", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(cos(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("tan", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(tan(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("asin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asin(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("acos", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(acos(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("atan", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(atan(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("sinh") {
|
||||
ObjReal(sinh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("cosh") {
|
||||
ObjReal(cosh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("tanh") {
|
||||
ObjReal(tanh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("asinh") {
|
||||
ObjReal(asinh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("acosh") {
|
||||
ObjReal(acosh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("atanh") {
|
||||
ObjReal(atanh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("sinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sinh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("cosh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(cosh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("tanh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(tanh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("asinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asinh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("acosh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(acosh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("atanh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(atanh(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("exp") {
|
||||
ObjReal(exp(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("ln") {
|
||||
ObjReal(ln(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("exp", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(exp(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("ln", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(ln(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("log10") {
|
||||
ObjReal(log10(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("log10", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log10(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("log2") {
|
||||
ObjReal(log2(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("log2", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log2(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("pow") {
|
||||
requireExactCount(2)
|
||||
ObjReal(
|
||||
(args[0].toDouble()).pow(args[1].toDouble())
|
||||
)
|
||||
}
|
||||
addFn("sqrt") {
|
||||
ObjReal(
|
||||
sqrt(args.firstAndOnly().toDouble())
|
||||
)
|
||||
}
|
||||
addFn("abs") {
|
||||
val x = args.firstAndOnly()
|
||||
if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
|
||||
}
|
||||
addFn("pow", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireExactCount(2)
|
||||
return ObjReal((scp.args[0].toDouble()).pow(scp.args[1].toDouble()))
|
||||
}
|
||||
})
|
||||
addFn("sqrt", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sqrt(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("abs", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
|
||||
}
|
||||
})
|
||||
|
||||
addVoidFn("assert") {
|
||||
val cond = requiredArg<ObjBool>(0)
|
||||
val message = if (args.size > 1)
|
||||
": " + (args[1] as Statement).execute(this).toString(this).value
|
||||
else ""
|
||||
if (!cond.value == true)
|
||||
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
|
||||
}
|
||||
addFnDoc<Obj>(
|
||||
"clamp",
|
||||
doc = "Clamps the value within the specified range. If the value is outside the range, it is set to the nearest boundary. Respects inclusive/exclusive range ends.",
|
||||
params = listOf(ParamDoc("value"), ParamDoc("range")),
|
||||
moduleName = "lyng.stdlib",
|
||||
fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val value = scp.requiredArg<Obj>(0)
|
||||
val range = scp.requiredArg<ObjRange>(1)
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if (a.compareTo(this, b) != 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||
var result = value
|
||||
if (range.start != null && !range.start.isNull) {
|
||||
if (result.compareTo(scp, range.start) < 0) {
|
||||
result = range.start
|
||||
}
|
||||
}
|
||||
if (range.end != null && !range.end.isNull) {
|
||||
val cmp = range.end.compareTo(scp, result)
|
||||
if (range.isEndInclusive) {
|
||||
if (cmp < 0) result = range.end
|
||||
} else {
|
||||
if (cmp <= 0) {
|
||||
if (range.end is ObjInt) {
|
||||
result = ObjInt.of(range.end.value - 1)
|
||||
} else if (range.end is ObjChar) {
|
||||
result = ObjChar((range.end.value.code - 1).toChar())
|
||||
} else {
|
||||
// For types where we can't easily find "previous" value (like Real),
|
||||
// we just return the exclusive boundary as a fallback.
|
||||
result = range.end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addVoidFn("assert", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val cond = scp.requiredArg<ObjBool>(0)
|
||||
val message = if (scp.args.size > 1)
|
||||
": " + (scp.args[1] as Statement).execute(scp).toString(scp).value
|
||||
else ""
|
||||
if (!cond.value == true)
|
||||
scp.raiseError(ObjAssertionFailedException(scp, "Assertion failed$message"))
|
||||
}
|
||||
})
|
||||
|
||||
addVoidFn("assertEquals", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.requiredArg<Obj>(0)
|
||||
val b = scp.requiredArg<Obj>(1)
|
||||
if (a.compareTo(scp, b) != 0)
|
||||
scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
// alias used in tests
|
||||
addVoidFn("assertEqual") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if (a.compareTo(this, b) != 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||
addVoidFn("assertEqual", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.requiredArg<Obj>(0)
|
||||
val b = scp.requiredArg<Obj>(1)
|
||||
if (a.compareTo(scp, b) != 0)
|
||||
scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
addVoidFn("assertNotEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if (a.compareTo(this, b) == 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
|
||||
}
|
||||
})
|
||||
addVoidFn("assertNotEquals", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.requiredArg<Obj>(0)
|
||||
val b = scp.requiredArg<Obj>(1)
|
||||
if (a.compareTo(scp, b) == 0)
|
||||
scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Assertion failed: ${a.inspect(scp)} != ${b.inspect(scp)}"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
addFnDoc(
|
||||
}
|
||||
})
|
||||
addFnDoc<Obj>(
|
||||
"assertThrows",
|
||||
doc = """
|
||||
Asserts that the provided code block throws an exception, with or without exception:
|
||||
@@ -216,83 +235,97 @@ class Script(
|
||||
If an expected exception class is provided,
|
||||
it checks that the thrown exception is of that class. If no expected class is provided, any exception
|
||||
will be accepted.
|
||||
""".trimIndent()
|
||||
) {
|
||||
val code: Statement
|
||||
val expectedClass: ObjClass?
|
||||
when (args.size) {
|
||||
1 -> {
|
||||
code = requiredArg<Statement>(0)
|
||||
expectedClass = null
|
||||
}
|
||||
""".trimIndent(),
|
||||
fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val code: Statement
|
||||
val expectedClass: ObjClass?
|
||||
when (scp.args.size) {
|
||||
1 -> {
|
||||
code = scp.requiredArg<Statement>(0)
|
||||
expectedClass = null
|
||||
}
|
||||
|
||||
2 -> {
|
||||
code = requiredArg<Statement>(1)
|
||||
expectedClass = requiredArg<ObjClass>(0)
|
||||
}
|
||||
2 -> {
|
||||
code = scp.requiredArg<Statement>(1)
|
||||
expectedClass = scp.requiredArg<ObjClass>(0)
|
||||
}
|
||||
|
||||
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}")
|
||||
}
|
||||
val result = try {
|
||||
code.execute(this)
|
||||
null
|
||||
} catch (e: ExecutionError) {
|
||||
e.errorObject
|
||||
} catch (_: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
if (result == null) raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Expected exception but nothing was thrown"
|
||||
)
|
||||
)
|
||||
expectedClass?.let {
|
||||
if (!result.isInstanceOf(it)) {
|
||||
val actual = if (result is ObjException) result.exceptionClass else result.objClass
|
||||
raiseError("Expected $it, got $actual")
|
||||
else -> scp.raiseIllegalArgument("Expected 1 or 2 arguments, got ${scp.args.size}")
|
||||
}
|
||||
val result = try {
|
||||
code.execute(scp)
|
||||
null
|
||||
} catch (e: ExecutionError) {
|
||||
e.errorObject
|
||||
} catch (_: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
if (result == null) scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Expected exception but nothing was thrown"
|
||||
)
|
||||
)
|
||||
expectedClass?.let {
|
||||
if (!result.isInstanceOf(it)) {
|
||||
val actual = if (result is ObjException) result.exceptionClass else result.objClass
|
||||
scp.raiseError("Expected $it, got $actual")
|
||||
}
|
||||
}
|
||||
return result ?: ObjNull
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
|
||||
addFn("dynamic") {
|
||||
ObjDynamic.create(this, requireOnlyArg())
|
||||
}
|
||||
addFn("dynamic", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjDynamic.create(scp, scp.requireOnlyArg())
|
||||
}
|
||||
})
|
||||
|
||||
val root = this
|
||||
val mathClass = ObjClass("Math").apply {
|
||||
addFn("sqrt") {
|
||||
ObjReal(sqrt(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("sqrt", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjReal(sqrt(scp.args.firstAndOnly().toDouble()))
|
||||
}
|
||||
})
|
||||
}
|
||||
addItem("Math", false, ObjInstance(mathClass).apply {
|
||||
instanceScope = Scope(root, thisObj = this)
|
||||
})
|
||||
|
||||
addFn("require") {
|
||||
val condition = requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(this)
|
||||
raiseIllegalArgument(message?.toString() ?: "requirement not met")
|
||||
addFn("require", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val condition = scp.requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = scp.args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(scp)
|
||||
scp.raiseIllegalArgument(message?.toString() ?: "requirement not met")
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
addFn("check") {
|
||||
val condition = requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(this)
|
||||
raiseIllegalState(message?.toString() ?: "check failed")
|
||||
})
|
||||
addFn("check", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val condition = scp.requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = scp.args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(scp)
|
||||
scp.raiseIllegalState(message?.toString() ?: "check failed")
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
addFn("traceScope") {
|
||||
this.trace(args.getOrNull(0)?.toString() ?: "")
|
||||
ObjVoid
|
||||
}
|
||||
})
|
||||
addFn("traceScope", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.trace(scp.args.getOrNull(0)?.toString() ?: "")
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
addVoidFn("delay") {
|
||||
val a = args.firstAndOnly()
|
||||
when (a) {
|
||||
@@ -302,7 +335,7 @@ class Script(
|
||||
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}")
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
addConst("Object", rootObjectType)
|
||||
addConst("Real", ObjReal.type)
|
||||
addConst("String", ObjString.type)
|
||||
@@ -329,7 +362,7 @@ class Script(
|
||||
addConst("Flow", ObjFlow.type)
|
||||
|
||||
addConst("Regex", ObjRegex.type)
|
||||
|
||||
/*
|
||||
addFn("launch") {
|
||||
val callable = requireOnlyArg<Statement>()
|
||||
ObjDeferred(globalDefer {
|
||||
@@ -347,7 +380,7 @@ class Script(
|
||||
// we'll need it for the producer
|
||||
ObjFlow(requireOnlyArg<Statement>(), this)
|
||||
}
|
||||
|
||||
*/
|
||||
val pi = ObjReal(PI)
|
||||
addConstDoc(
|
||||
name = "π",
|
||||
@@ -370,54 +403,69 @@ class Script(
|
||||
addTextPackages(
|
||||
rootLyng
|
||||
)
|
||||
addPackage("lyng.buffer") {
|
||||
it.addConstDoc(
|
||||
name = "Buffer",
|
||||
value = ObjBuffer.type,
|
||||
doc = "Immutable sequence of bytes. Use for binary data and IO.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addConstDoc(
|
||||
name = "MutableBuffer",
|
||||
value = ObjMutableBuffer.type,
|
||||
doc = "Mutable byte buffer. Supports in-place modifications.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
addPackage("lyng.serialization") {
|
||||
it.addConstDoc(
|
||||
name = "Lynon",
|
||||
value = ObjLynonClass,
|
||||
doc = "Lynon serialization utilities: encode/decode data structures to a portable binary/text form.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
addPackage("lyng.time") {
|
||||
it.addConstDoc(
|
||||
name = "Instant",
|
||||
value = ObjInstant.type,
|
||||
doc = "Point in time (epoch-based).",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addConstDoc(
|
||||
name = "Duration",
|
||||
value = ObjDuration.type,
|
||||
doc = "Time duration with millisecond precision.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addVoidFnDoc(
|
||||
"delay",
|
||||
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds."
|
||||
) {
|
||||
val a = args.firstAndOnly()
|
||||
when (a) {
|
||||
is ObjInt -> delay(a.value * 1000)
|
||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||
is ObjDuration -> delay(a.duration)
|
||||
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}")
|
||||
}
|
||||
addPackage("lyng.buffer", object : ModuleBuilder {
|
||||
override suspend fun build(ms: ModuleScope) {
|
||||
ms.addConstDoc(
|
||||
name = "Buffer",
|
||||
value = ObjBuffer.type,
|
||||
doc = "Immutable sequence of bytes. Use for binary data and IO.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addConstDoc(
|
||||
name = "MutableBuffer",
|
||||
value = ObjMutableBuffer.type,
|
||||
doc = "Mutable byte buffer. Supports in-place modifications.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
addPackage("lyng.serialization", object : ModuleBuilder {
|
||||
override suspend fun build(ms: ModuleScope) {
|
||||
ms.addConstDoc(
|
||||
name = "Lynon",
|
||||
value = ObjLynonClass,
|
||||
doc = "Lynon serialization utilities: encode/decode data structures to a portable binary/text form.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
})
|
||||
addPackage("lyng.time", object : ModuleBuilder {
|
||||
override suspend fun build(ms: ModuleScope) {
|
||||
ms.addConstDoc(
|
||||
name = "Instant",
|
||||
value = ObjInstant.type,
|
||||
doc = "Point in time (epoch-based).",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addConstDoc(
|
||||
name = "DateTime",
|
||||
value = ObjDateTime.type,
|
||||
doc = "Point in time in a specific time zone.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addConstDoc(
|
||||
name = "Duration",
|
||||
value = ObjDuration.type,
|
||||
doc = "Time duration with millisecond precision.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addVoidFnDoc(
|
||||
"delay",
|
||||
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds.",
|
||||
fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.args.firstAndOnly()
|
||||
when (a) {
|
||||
is ObjInt -> delay(a.value * 1000)
|
||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||
is ObjDuration -> delay(a.duration)
|
||||
else -> scp.raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(scp)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -25,7 +25,12 @@ enum class Visibility {
|
||||
}
|
||||
|
||||
/** MI-aware visibility check: whether [caller] can access a member declared in [decl] with [visibility]. */
|
||||
fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass?, caller: net.sergeych.lyng.obj.ObjClass?): Boolean {
|
||||
fun canAccessMember(
|
||||
visibility: Visibility,
|
||||
decl: net.sergeych.lyng.obj.ObjClass?,
|
||||
caller: net.sergeych.lyng.obj.ObjClass?,
|
||||
name: String? = null
|
||||
): Boolean {
|
||||
val res = when (visibility) {
|
||||
Visibility.Public -> true
|
||||
Visibility.Private -> (decl != null && caller === decl)
|
||||
@@ -33,7 +38,14 @@ fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass
|
||||
decl == null -> false
|
||||
caller == null -> false
|
||||
caller === decl -> true
|
||||
else -> (caller.allParentsSet.contains(decl))
|
||||
caller.allParentsSet.contains(decl) -> true
|
||||
name != null && decl.allParentsSet.contains(caller) -> {
|
||||
// Ancestor can access protected member of its descendant if it also has this member
|
||||
// (i.e. it's an override of something the ancestor knows about)
|
||||
val existing = caller.getInstanceMemberOrNull(name, includeAbstract = true)
|
||||
existing != null && (existing.visibility == Visibility.Protected || existing.visibility == Visibility.Public)
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -108,8 +108,9 @@ object LyngFormatter {
|
||||
if (t == "finally") return true
|
||||
|
||||
// Short definition form: fun x() = or val x =
|
||||
if (Regex("^(override\\s+)?(fun|fn)\\b.*=\\s*$").matches(t)) return true
|
||||
if (Regex("^(private\\s+|protected\\s+|public\\s+|override\\s+)?(val|var)\\b.*=\\s*$").matches(t)) return true
|
||||
// We allow optional 'static' as well
|
||||
if (Regex("^(static\\s+)?(override\\s+)?(fun|fn)\\b.*=\\s*$").matches(t)) return true
|
||||
if (Regex("^(static\\s+)?(private\\s+|protected\\s+|public\\s+|override\\s+)?(val|var)\\b.*=\\s*$").matches(t)) return true
|
||||
|
||||
// property accessors ending with ) or =
|
||||
if (isAccessorRelated(t)) {
|
||||
@@ -122,7 +123,8 @@ object LyngFormatter {
|
||||
val t = s.trim()
|
||||
if (!t.endsWith("=")) return false
|
||||
// Is it a function or property definition?
|
||||
if (Regex("\\b(fun|fn|val|var)\\b").containsMatchIn(t)) return true
|
||||
// (Note: we exclude 'static' here to avoid double indent if it's already handled)
|
||||
if (Regex("^(override\\s+)?(fun|fn|val|var)\\b").containsMatchIn(t)) return true
|
||||
// Is it an accessor?
|
||||
if (isPropertyAccessor(t)) return true
|
||||
return false
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
package net.sergeych.lyng.miniast
|
||||
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Visibility
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
@@ -39,10 +37,10 @@ inline fun <reified T : Obj> Scope.addFnDoc(
|
||||
returns: TypeDoc? = null,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
crossinline fn: suspend Scope.() -> T
|
||||
fn: ScopeCallable
|
||||
) {
|
||||
// Register runtime function(s)
|
||||
addFn(*names) { fn() }
|
||||
addFn(*names, fn = fn)
|
||||
// Determine module
|
||||
val mod = moduleName ?: findModuleNameOrUnknown()
|
||||
// Register docs once per name
|
||||
@@ -56,7 +54,7 @@ inline fun Scope.addVoidFnDoc(
|
||||
doc: String,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
crossinline fn: suspend Scope.() -> Unit
|
||||
fn: VoidScopeCallable
|
||||
) {
|
||||
addFnDoc<ObjVoid>(
|
||||
*names,
|
||||
@@ -64,11 +62,14 @@ inline fun Scope.addVoidFnDoc(
|
||||
params = emptyList(),
|
||||
returns = null,
|
||||
tags = tags,
|
||||
moduleName = moduleName
|
||||
) {
|
||||
fn(this)
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = moduleName,
|
||||
fn = object : ScopeCallable {
|
||||
override suspend fun call(sc: Scope): Obj {
|
||||
fn.call(sc)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun Scope.addConstDoc(
|
||||
@@ -97,7 +98,7 @@ fun ObjClass.addFnDoc(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
code: suspend Scope.() -> Obj
|
||||
code: ScopeCallable
|
||||
) {
|
||||
// Register runtime method
|
||||
addFn(name, isOpen, visibility, code = code)
|
||||
@@ -135,7 +136,7 @@ fun ObjClass.addClassFnDoc(
|
||||
isOpen: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
code: suspend Scope.() -> Obj
|
||||
code: ScopeCallable
|
||||
) {
|
||||
addClassFn(name, isOpen, code)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
@@ -151,8 +152,8 @@ fun ObjClass.addPropertyDoc(
|
||||
type: TypeDoc? = null,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
moduleName: String? = null,
|
||||
getter: (suspend Scope.() -> Obj)? = null,
|
||||
setter: (suspend Scope.(Obj) -> Unit)? = null
|
||||
getter: ScopeCallable? = null,
|
||||
setter: ScopeCallable? = null
|
||||
) {
|
||||
addProperty(name, getter, setter, visibility)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
|
||||
@@ -44,13 +44,22 @@ data class MiniDoc(
|
||||
fun parse(range: MiniRange, lines: Iterable<String>, extraTags: Map<String, List<String>> = emptyMap()): MiniDoc {
|
||||
val parsedTags = mutableMapOf<String, MutableList<String>>()
|
||||
var currentTag: String? = null
|
||||
val currentContent = StringBuilder()
|
||||
val currentContent = mutableListOf<String>()
|
||||
|
||||
fun flush() {
|
||||
currentTag?.let { tag ->
|
||||
parsedTags.getOrPut(tag) { mutableListOf() }.add(currentContent.toString().trim())
|
||||
val trimmedContent = if (currentContent.size > 1) {
|
||||
// First line is common content, we want to trim indent from subsequent lines
|
||||
// based on THEIR common indent.
|
||||
val firstLine = currentContent[0]
|
||||
val remaining = currentContent.drop(1).joinToString("\n").trimIndent()
|
||||
if (remaining.isEmpty()) firstLine else firstLine + "\n" + remaining
|
||||
} else {
|
||||
currentContent.joinToString("\n")
|
||||
}
|
||||
parsedTags.getOrPut(tag) { mutableListOf() }.add(trimmedContent.trim())
|
||||
}
|
||||
currentContent.setLength(0)
|
||||
currentContent.clear()
|
||||
}
|
||||
|
||||
val descriptionLines = mutableListOf<String>()
|
||||
@@ -58,33 +67,45 @@ data class MiniDoc(
|
||||
|
||||
for (rawLine in lines) {
|
||||
for (line in rawLine.lines()) {
|
||||
val trimmed = line.trim()
|
||||
// Strip leading '*' and space if present (standard doc comment style)
|
||||
val cleanedLine = line.trim().let {
|
||||
if (it.startsWith("*")) it.substring(1).removePrefix(" ") else line
|
||||
}
|
||||
val trimmed = cleanedLine.trim()
|
||||
if (trimmed.startsWith("@")) {
|
||||
inTags = true
|
||||
flush()
|
||||
val parts = trimmed.substring(1).split(Regex("\\s+"), 2)
|
||||
currentTag = parts[0]
|
||||
currentContent.append(parts.getOrNull(1) ?: "")
|
||||
val tagFirstLine = parts.getOrNull(1) ?: ""
|
||||
if (tagFirstLine.isNotEmpty()) {
|
||||
// Find where the content starts in the cleaned line to preserve relative indentation
|
||||
val contentIndex = cleanedLine.indexOf(tagFirstLine)
|
||||
if (contentIndex >= 0) {
|
||||
currentContent.add(cleanedLine.substring(contentIndex))
|
||||
} else {
|
||||
currentContent.add(tagFirstLine)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (inTags) {
|
||||
if (currentContent.isNotEmpty()) currentContent.append("\n")
|
||||
currentContent.append(line)
|
||||
currentContent.add(cleanedLine)
|
||||
} else {
|
||||
descriptionLines.add(line)
|
||||
descriptionLines.add(cleanedLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
flush()
|
||||
|
||||
val raw = descriptionLines.joinToString("\n").trimEnd()
|
||||
val raw = descriptionLines.joinToString("\n").trimIndent().trim()
|
||||
val summary = raw.lines().firstOrNull { it.isNotBlank() }?.trim()
|
||||
|
||||
|
||||
val finalTags = parsedTags.toMutableMap()
|
||||
extraTags.forEach { (k, v) ->
|
||||
finalTags.getOrPut(k) { mutableListOf() }.addAll(v)
|
||||
}
|
||||
|
||||
|
||||
return MiniDoc(range, raw, summary, finalTags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -32,29 +32,41 @@ typealias DocCompiler = Compiler
|
||||
*/
|
||||
typealias Accessor = ObjRef
|
||||
|
||||
interface AccessorGetter {
|
||||
suspend fun call(scope: Scope): ObjRecord
|
||||
}
|
||||
|
||||
interface AccessorSetter {
|
||||
suspend fun call(scope: Scope, value: Obj)
|
||||
}
|
||||
|
||||
/** Lambda-based reference for edge cases that still construct access via lambdas. */
|
||||
private class LambdaRef(
|
||||
private val getterFn: suspend (Scope) -> ObjRecord,
|
||||
private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null
|
||||
private val getterFn: AccessorGetter,
|
||||
private val setterFn: AccessorSetter? = null
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord = getterFn(scope)
|
||||
override suspend fun get(scope: Scope): ObjRecord = getterFn.call(scope)
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
val s = setterFn ?: throw ScriptError(pos, "can't assign value")
|
||||
s(pos, scope, newValue)
|
||||
s.call(scope, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Factory functions to preserve current call sites like `Accessor { ... }`
|
||||
fun Accessor(getter: suspend (Scope) -> ObjRecord): Accessor = LambdaRef(getter)
|
||||
fun Accessor(getter: AccessorGetter): Accessor = LambdaRef(getter)
|
||||
fun Accessor(
|
||||
getter: suspend (Scope) -> ObjRecord,
|
||||
setter: suspend (Scope, Obj) -> Unit
|
||||
): Accessor = LambdaRef(getter) { _, scope, value -> setter(scope, value) }
|
||||
getter: AccessorGetter,
|
||||
setter: AccessorSetter
|
||||
): Accessor = LambdaRef(getter, setter)
|
||||
|
||||
// Compatibility shims used throughout Compiler: `.getter(...)` and `.setter(pos)`
|
||||
val Accessor.getter: suspend (Scope) -> ObjRecord
|
||||
get() = { scope -> this.get(scope) }
|
||||
val Accessor.getter: AccessorGetter
|
||||
get() = object : AccessorGetter {
|
||||
override suspend fun call(scope: Scope): ObjRecord = this@getter.get(scope)
|
||||
}
|
||||
|
||||
fun Accessor.setter(pos: Pos): suspend (Scope, Obj) -> Unit = { scope, newValue ->
|
||||
this.setAt(pos, scope, newValue)
|
||||
fun Accessor.setter(pos: Pos): AccessorSetter = object : AccessorSetter {
|
||||
override suspend fun call(scope: Scope, value: Obj) {
|
||||
this@setter.setAt(pos, scope, value)
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,14 @@ import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
fun interface OnNotFound {
|
||||
suspend fun call(): Obj?
|
||||
}
|
||||
|
||||
fun interface EnumerateCallback {
|
||||
suspend fun call(element: Obj): Boolean
|
||||
}
|
||||
|
||||
open class Obj {
|
||||
|
||||
open val isConst: Boolean = false
|
||||
@@ -91,7 +99,7 @@ open class Obj {
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
onNotFoundResult: (suspend () -> Obj?)? = null
|
||||
onNotFoundResult: OnNotFound? = null
|
||||
): Obj {
|
||||
// 0. Prefer private member of current class context
|
||||
scope.currentClassCtx?.let { caller ->
|
||||
@@ -100,7 +108,7 @@ open class Obj {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, caller)
|
||||
return rec.value.invokeCallable(scope, this, args, caller)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,13 +121,13 @@ open class Obj {
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
return rec.value.invokeCallable(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +138,7 @@ open class Obj {
|
||||
if (extension.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
|
||||
} else if (extension.type != ObjRecord.Type.Delegated) {
|
||||
return extension.value.invoke(scope, this, args)
|
||||
return extension.value.invokeCallable(scope, this, args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,19 +148,19 @@ open class Obj {
|
||||
cls.members[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
return rec.value.invokeCallable(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onNotFoundResult?.invoke()
|
||||
return onNotFoundResult?.call()
|
||||
?: scope.raiseError(
|
||||
"no such member: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}. " +
|
||||
"Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous"
|
||||
@@ -174,9 +182,11 @@ open class Obj {
|
||||
open suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other === this) return 0
|
||||
if (other === ObjNull || other === ObjUnset || other === ObjVoid) return 2
|
||||
return invokeInstanceMethod(scope, "compareTo", Arguments(other)) {
|
||||
scope.raiseNotImplemented("compareTo for ${objClass.className}")
|
||||
}.cast<ObjInt>(scope).toInt()
|
||||
return invokeInstanceMethod(scope, "compareTo", Arguments(other), onNotFoundResult = object : OnNotFound {
|
||||
override suspend fun call(): Obj? {
|
||||
scope.raiseNotImplemented("compareTo for ${objClass.className}")
|
||||
}
|
||||
}).toInt()
|
||||
}
|
||||
|
||||
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
@@ -202,16 +212,16 @@ open class Obj {
|
||||
*
|
||||
* IF callback returns false, iteration is stopped.
|
||||
*/
|
||||
open suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
open suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||
val next = iterator.getInstanceMethod(scope, "next")
|
||||
var closeIt = false
|
||||
try {
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
val nextValue = next.invoke(scope, iterator)
|
||||
while (hasNext.invokeCallable(scope, iterator).toBool()) {
|
||||
val nextValue = next.invokeCallable(scope, iterator)
|
||||
val shouldContinue = try {
|
||||
callback(nextValue)
|
||||
callback.call(nextValue)
|
||||
} catch (e: Exception) {
|
||||
// iteration aborted due to exception in callback
|
||||
closeIt = true
|
||||
@@ -429,10 +439,10 @@ open class Obj {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Lyng object to its Kotlin counterpart
|
||||
* Convert a Lyng object to its Kotlin counterpart
|
||||
*/
|
||||
open suspend fun toKotlin(scope: Scope): Any? {
|
||||
return toString()
|
||||
return toString(scope).value
|
||||
}
|
||||
|
||||
fun willMutate(scope: Scope) {
|
||||
@@ -448,7 +458,7 @@ open class Obj {
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
val resolved = resolveRecord(scope, rec, name, caller)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, caller))
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
@@ -462,7 +472,7 @@ open class Obj {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val resolved = resolveRecord(scope, rec, name, decl)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
@@ -472,7 +482,7 @@ open class Obj {
|
||||
if (extension != null) {
|
||||
val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, extension.declaringClass))
|
||||
return resolved
|
||||
}
|
||||
|
||||
@@ -482,11 +492,11 @@ open class Obj {
|
||||
cls.members[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
val resolved = resolveRecord(scope, rec, name, decl)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
@@ -502,13 +512,13 @@ open class Obj {
|
||||
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
val th = if (this === ObjVoid) ObjNull else this
|
||||
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = {
|
||||
// If getValue not found, return a wrapper that calls invoke
|
||||
// If getValue not found, return a wrapper that calls invokeCallable
|
||||
object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(s: Scope): Obj {
|
||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs))
|
||||
return del.invokeInstanceMethod(s, "invokeCallable", Arguments(*allArgs))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -526,7 +536,7 @@ open class Obj {
|
||||
}
|
||||
val caller = scope.currentClassCtx
|
||||
// Check visibility for non-property members here if they weren't checked before
|
||||
if (!canAccessMember(obj.visibility, decl, caller))
|
||||
if (!canAccessMember(obj.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
return obj
|
||||
}
|
||||
@@ -574,7 +584,7 @@ open class Obj {
|
||||
|
||||
val decl = field.declaringClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
if (field.type == ObjRecord.Type.Delegated) {
|
||||
val del = field.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
@@ -605,18 +615,20 @@ open class Obj {
|
||||
scope.raiseNotImplemented()
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
if (PerfFlags.SCOPE_POOL)
|
||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
callOn(child)
|
||||
}
|
||||
scope.withChildFrame(args, newThisObj = thisObj, block = object : ScopeBlock<Obj> {
|
||||
override suspend fun call(child: Scope): Obj {
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
return callOn(child)
|
||||
}
|
||||
})
|
||||
else
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||
})
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
callOn(
|
||||
scope.createChildScope(
|
||||
scope.pos,
|
||||
@@ -625,7 +637,7 @@ open class Obj {
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, thisObj: Obj): Obj =
|
||||
callOn(
|
||||
scope.createChildScope(
|
||||
scope.pos,
|
||||
@@ -634,7 +646,7 @@ open class Obj {
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||
callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj))
|
||||
|
||||
|
||||
@@ -681,85 +693,138 @@ open class Obj {
|
||||
name = "toString",
|
||||
doc = "Returns a string representation of the object.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.toString(this, true)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj.toString(scp, true)
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "inspect",
|
||||
doc = "Returns a detailed string representation for debugging.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.inspect(this).toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj.inspect(scp).toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "contains",
|
||||
doc = "Returns true if the object contains the given element.",
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisObj.contains(scp, scp.args.firstAndOnly()))
|
||||
}
|
||||
)
|
||||
// utilities
|
||||
addFnDoc(
|
||||
name = "let",
|
||||
doc = "Calls the specified function block with `this` value as its argument and returns its result.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj)))
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "apply",
|
||||
doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val body = args.firstAndOnly()
|
||||
(thisObj as? ObjInstance)?.let {
|
||||
body.callOn(ApplyScope(this, it.instanceScope))
|
||||
} ?: run {
|
||||
body.callOn(this)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val body = scp.args.firstAndOnly()
|
||||
(scp.thisObj as? ObjInstance)?.let {
|
||||
body.callOn(ApplyScope(scp, it.instanceScope))
|
||||
} ?: run {
|
||||
body.callOn(scp)
|
||||
}
|
||||
return scp.thisObj
|
||||
}
|
||||
}
|
||||
thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "also",
|
||||
doc = "Calls the specified function block with `this` value as its argument and returns `this` value.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
|
||||
thisObj
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj)))
|
||||
return scp.thisObj
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "run",
|
||||
doc = "Calls the specified function block with `this` value as its receiver and returns its result.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
args.firstAndOnly().callOn(this)
|
||||
}
|
||||
addFn("getAt") {
|
||||
requireExactCount(1)
|
||||
thisObj.getAt(this, requiredArg<Obj>(0))
|
||||
}
|
||||
addFn("putAt") {
|
||||
requireExactCount(2)
|
||||
val newValue = args[1]
|
||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||
newValue
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.args.firstAndOnly().callOn(scp)
|
||||
}
|
||||
)
|
||||
addFn("getAt", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireExactCount(1)
|
||||
return scp.thisObj.getAt(scp, scp.requiredArg<Obj>(0))
|
||||
}
|
||||
})
|
||||
addFn("putAt", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireExactCount(2)
|
||||
val newValue = scp.args[1]
|
||||
scp.thisObj.putAt(scp, scp.requiredArg<Obj>(0), newValue)
|
||||
return newValue
|
||||
}
|
||||
})
|
||||
addFnDoc(
|
||||
name = "toJsonString",
|
||||
doc = "Encodes this object to a JSON string.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.toJson(this).toString().toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisObj.toJson(scp).toString().toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "clamp",
|
||||
doc = "Clamps this value within the specified range. If the value is outside the range, it is set to the nearest boundary. Respects inclusive/exclusive range ends.",
|
||||
params = listOf(ParamDoc("range")),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val range = scp.requiredArg<ObjRange>(0)
|
||||
|
||||
var result = scp.thisObj
|
||||
if (range.start != null && !range.start.isNull) {
|
||||
if (result.compareTo(scp, range.start) < 0) {
|
||||
result = range.start
|
||||
}
|
||||
}
|
||||
if (range.end != null && !range.end.isNull) {
|
||||
val cmp = range.end.compareTo(scp, result)
|
||||
if (range.isEndInclusive) {
|
||||
if (cmp < 0) result = range.end
|
||||
} else {
|
||||
if (cmp <= 0) {
|
||||
if (range.end is ObjInt) {
|
||||
result = ObjInt.of(range.end.value - 1)
|
||||
} else if (range.end is ObjChar) {
|
||||
result = ObjChar((range.end.value.code - 1).toChar())
|
||||
} else {
|
||||
result = range.end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -887,7 +952,7 @@ object ObjUnset : Obj() {
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj = scope.raiseUnset()
|
||||
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj = scope.raiseUnset()
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
val ObjArray by lazy {
|
||||
@@ -31,8 +33,11 @@ val ObjArray by lazy {
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements of this array using its indexer.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjArrayIterator(thisObj).also { it.init(this) } }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjArrayIterator(scp.thisObj).also { it.init(scp) }
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "contains",
|
||||
@@ -40,26 +45,31 @@ val ObjArray by lazy {
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Bool"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val obj = args.firstAndOnly()
|
||||
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
||||
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFnDoc ObjTrue
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val obj = scp.args.firstAndOnly()
|
||||
for (i in 0..<scp.thisObj.invokeInstanceMethod(scp, "size").toInt()) {
|
||||
if (scp.thisObj.getAt(scp, ObjInt(i.toLong())).compareTo(scp, obj) == 0) return ObjTrue
|
||||
}
|
||||
return ObjFalse
|
||||
}
|
||||
}
|
||||
ObjFalse
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "last",
|
||||
doc = "The last element of this array.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
this.thisObj.invokeInstanceMethod(
|
||||
this,
|
||||
"getAt",
|
||||
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
||||
)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.thisObj.invokeInstanceMethod(
|
||||
scp,
|
||||
"getAt",
|
||||
(scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1).toObj()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -68,7 +78,9 @@ val ObjArray by lazy {
|
||||
doc = "Index of the last element (size - 1).",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1).toObj()
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
@@ -76,7 +88,9 @@ val ObjArray by lazy {
|
||||
doc = "Range of valid indices for this array.",
|
||||
type = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjRange(0.toObj(), scp.thisObj.invokeInstanceMethod(scp, "size"), false)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
@@ -84,25 +98,28 @@ val ObjArray by lazy {
|
||||
doc = "Binary search for a target in a sorted array. Returns index or negative insertion point - 1.",
|
||||
params = listOf(ParamDoc("target")),
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val target = args.firstAndOnly()
|
||||
var low = 0
|
||||
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val target = scp.args.firstAndOnly()
|
||||
var low = 0
|
||||
var high = scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1
|
||||
|
||||
while (low <= high) {
|
||||
val mid = (low + high) / 2
|
||||
val midVal = thisObj.getAt(this, ObjInt(mid.toLong()))
|
||||
while (low <= high) {
|
||||
val mid = (low + high) / 2
|
||||
val midVal = scp.thisObj.getAt(scp, ObjInt(mid.toLong()))
|
||||
|
||||
val cmp = midVal.compareTo(this, target)
|
||||
when {
|
||||
cmp == 0 -> return@addFnDoc (mid).toObj()
|
||||
cmp > 0 -> high = mid - 1
|
||||
else -> low = mid + 1
|
||||
val cmp = midVal.compareTo(scp, target)
|
||||
when {
|
||||
cmp == 0 -> return (mid).toObj()
|
||||
cmp > 0 -> high = mid - 1
|
||||
else -> low = mid + 1
|
||||
}
|
||||
}
|
||||
|
||||
return (-low - 1).toObj()
|
||||
}
|
||||
}
|
||||
|
||||
(-low - 1).toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
|
||||
class ObjArrayIterator(val array: Obj) : Obj() {
|
||||
|
||||
@@ -35,16 +36,20 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
||||
companion object {
|
||||
val type by lazy {
|
||||
ObjClass("ArrayIterator", ObjIterator).apply {
|
||||
addFn("next") {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
if (self.nextIndex < self.lastIndex) {
|
||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
||||
} else raiseError(ObjIterationFinishedException(this))
|
||||
}
|
||||
addFn("hasNext") {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
|
||||
}
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjArrayIterator>()
|
||||
return if (self.nextIndex < self.lastIndex) {
|
||||
self.array.invokeInstanceMethod(scp, "getAt", (self.nextIndex++).toObj())
|
||||
} else scp.raiseError(ObjIterationFinishedException(scp))
|
||||
}
|
||||
})
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjArrayIterator>()
|
||||
return if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.bintools.toDump
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.BitArray
|
||||
@@ -35,29 +36,37 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
||||
val type = object: ObjClass("BitBuffer", ObjArray) {
|
||||
|
||||
}.apply {
|
||||
addFn("toBuffer") {
|
||||
requireNoArgs()
|
||||
ObjBuffer(thisAs<ObjBitBuffer>().bitArray.asUByteArray())
|
||||
}
|
||||
addFn("toDump") {
|
||||
requireNoArgs()
|
||||
ObjString(
|
||||
thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump()
|
||||
)
|
||||
}
|
||||
addFn("toBuffer", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireNoArgs()
|
||||
return ObjBuffer(scp.thisAs<ObjBitBuffer>().bitArray.asUByteArray())
|
||||
}
|
||||
})
|
||||
addFn("toDump", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireNoArgs()
|
||||
return ObjString(
|
||||
scp.thisAs<ObjBitBuffer>().bitArray.asUByteArray().toByteArray().toDump()
|
||||
)
|
||||
}
|
||||
})
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Size of the bit buffer in bits.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjBitBuffer>().bitArray.size.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBitBuffer>().bitArray.size.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "sizeInBytes",
|
||||
doc = "Size of the bit buffer in full bytes (rounded up).",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt((thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt(((scp.thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3).toLong())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import net.sergeych.bintools.decodeHex
|
||||
import net.sergeych.bintools.encodeToHex
|
||||
import net.sergeych.bintools.toDump
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.BitArray
|
||||
@@ -169,51 +171,85 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
||||
})
|
||||
|
||||
}.apply {
|
||||
addClassFn("decodeBase64") {
|
||||
ObjBuffer(requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
|
||||
}
|
||||
addClassFn("decodeHex") {
|
||||
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
||||
}
|
||||
addClassFn("decodeBase64", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBuffer(scp.requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
|
||||
})
|
||||
addClassFn("decodeHex", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBuffer(scp.requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
||||
})
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of bytes in this buffer.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjBuffer).byteArray.size.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjBuffer).byteArray.size.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "hex",
|
||||
doc = "Hexadecimal string representation of the buffer.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjBuffer>().hex.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBuffer>().hex.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "base64",
|
||||
doc = "Base64 (URL-safe) string representation of the buffer.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjBuffer>().base64.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBuffer>().base64.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "decodeUtf8",
|
||||
doc = "Decode the buffer content as a UTF-8 string.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjString(scp.thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toMutable",
|
||||
doc = "Return a mutable copy of this buffer.",
|
||||
returns = type("lyng.MutableBuffer"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireNoArgs()
|
||||
return ObjMutableBuffer(scp.thisAs<ObjBuffer>().byteArray.copyOf())
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toDump",
|
||||
doc = "Return a hexadecimal dump string of the buffer.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireNoArgs()
|
||||
return ObjString(scp.thisAs<ObjBuffer>().byteArray.toByteArray().toDump())
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toBitInput",
|
||||
doc = "Return a bit buffer for reading bits from this buffer.",
|
||||
returns = type("lyng.BitBuffer"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBitBuffer(BitArray(scp.thisAs<ObjBuffer>().byteArray, 8))
|
||||
}
|
||||
)
|
||||
addFn("decodeUtf8") {
|
||||
ObjString(
|
||||
thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString()
|
||||
)
|
||||
}
|
||||
addFn("toMutable") {
|
||||
requireNoArgs()
|
||||
ObjMutableBuffer(thisAs<ObjBuffer>().byteArray.copyOf())
|
||||
}
|
||||
addFn("toDump") {
|
||||
requireNoArgs()
|
||||
ObjString(
|
||||
thisAs<ObjBuffer>().byteArray.toByteArray().toDump()
|
||||
)
|
||||
}
|
||||
addFn("toBitInput") {
|
||||
ObjBitBuffer(BitArray(thisAs<ObjBuffer>().byteArray, 8))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
@@ -52,14 +53,16 @@ class ObjChar(val value: Char): Obj() {
|
||||
doc = "Unicode code point (UTF-16 code unit) of this character.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt((this.thisObj as ObjChar).value.code.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt((scp.thisObj as ObjChar).value.code.toLong())
|
||||
}
|
||||
)
|
||||
addFn("isDigit") {
|
||||
thisAs<ObjChar>().value.isDigit().toObj()
|
||||
}
|
||||
addFn("isSpace") {
|
||||
thisAs<ObjChar>().value.isWhitespace().toObj()
|
||||
}
|
||||
addFn("isDigit", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjChar>().value.isDigit().toObj()
|
||||
})
|
||||
addFn("isSpace", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjChar>().value.isWhitespace().toObj()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,29 +17,41 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
// Simple id generator for class identities (not thread-safe; fine for scripts)
|
||||
private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
||||
|
||||
val ObjClassType by lazy {
|
||||
ObjClass("Class").apply {
|
||||
object : ObjClass("Class") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
val name = decoder.decodeObject(scope, ObjString.type, null) as ObjString
|
||||
return scope.resolveQualifiedIdentifier(name.value)
|
||||
}
|
||||
}.apply {
|
||||
addPropertyDoc(
|
||||
name = "className",
|
||||
doc = "Full name of this class including package if available.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjClass).classNameObj
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "name",
|
||||
doc = "Simple name of this class (without package).",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjClass).classNameObj
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
@@ -47,16 +59,18 @@ val ObjClassType by lazy {
|
||||
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val cls = this.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val cls = scp.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
||||
}
|
||||
}
|
||||
return ObjList(names.toMutableList())
|
||||
}
|
||||
ObjList(names.toMutableList())
|
||||
}
|
||||
)
|
||||
|
||||
@@ -65,16 +79,18 @@ val ObjClassType by lazy {
|
||||
doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val cls = this.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val cls = scp.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
||||
}
|
||||
}
|
||||
return ObjList(names.toMutableList())
|
||||
}
|
||||
ObjList(names.toMutableList())
|
||||
}
|
||||
)
|
||||
|
||||
@@ -83,13 +99,16 @@ val ObjClassType by lazy {
|
||||
doc = "Lookup a member by name in this class (including ancestors) and return it, or null if absent.",
|
||||
params = listOf(ParamDoc("name", type("lyng.String"))),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val cls = thisAs<ObjClass>()
|
||||
val name = requiredArg<ObjString>(0).value
|
||||
val rec = cls.getInstanceMemberOrNull(name)
|
||||
rec?.value ?: ObjNull
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val cls = scp.thisAs<ObjClass>()
|
||||
val name = scp.requiredArg<ObjString>(0).value
|
||||
val rec = cls.getInstanceMemberOrNull(name)
|
||||
return rec?.value ?: ObjNull
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +470,7 @@ open class ObjClass(
|
||||
isAbstract: Boolean = false,
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
isTransient: Boolean = false,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||
): ObjRecord {
|
||||
// Validation of override rules: only for non-system declarations
|
||||
@@ -461,7 +481,7 @@ open class ObjClass(
|
||||
if (existing != null && existing.declaringClass != this) {
|
||||
// If the existing member is private in the ancestor, it's not visible for overriding.
|
||||
// It should be treated as a new member in this class.
|
||||
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this)) {
|
||||
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this, name)) {
|
||||
// It's effectively not there for us, so actualOverride remains false
|
||||
} else {
|
||||
actualOverride = true
|
||||
@@ -494,6 +514,7 @@ open class ObjClass(
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
type = type
|
||||
)
|
||||
members[name] = rec
|
||||
@@ -514,13 +535,14 @@ open class ObjClass(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
writeVisibility: Visibility? = null,
|
||||
pos: Pos = Pos.builtIn,
|
||||
isTransient: Boolean = false,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field
|
||||
): ObjRecord {
|
||||
initClassScope()
|
||||
val existing = classScope!!.objects[name]
|
||||
if (existing != null)
|
||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type)
|
||||
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type, isTransient = isTransient)
|
||||
// Structural change: bump layout version for PIC invalidation
|
||||
layoutVersion += 1
|
||||
return rec
|
||||
@@ -536,9 +558,9 @@ open class ObjClass(
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
code: (suspend Scope.() -> Obj)? = null
|
||||
code: ScopeCallable? = null
|
||||
) {
|
||||
val stmt = code?.let { statement { it() } } ?: ObjNull
|
||||
val stmt = code?.let { statement(pos, f = it) } ?: ObjNull
|
||||
createField(
|
||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||
@@ -550,8 +572,8 @@ open class ObjClass(
|
||||
|
||||
fun addProperty(
|
||||
name: String,
|
||||
getter: (suspend Scope.() -> Obj)? = null,
|
||||
setter: (suspend Scope.(Obj) -> Unit)? = null,
|
||||
getter: ScopeCallable? = null,
|
||||
setter: ScopeCallable? = null,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
writeVisibility: Visibility? = null,
|
||||
declaringClass: ObjClass? = this,
|
||||
@@ -561,8 +583,8 @@ open class ObjClass(
|
||||
pos: Pos = Pos.builtIn,
|
||||
prop: ObjProperty? = null
|
||||
) {
|
||||
val g = getter?.let { statement { it() } }
|
||||
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
||||
val g = getter?.let { statement(pos, f = it) }
|
||||
val s = setter?.let { statement(pos, f = it) }
|
||||
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
|
||||
createField(
|
||||
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
||||
@@ -572,8 +594,8 @@ open class ObjClass(
|
||||
}
|
||||
|
||||
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
||||
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||
createClassField(name, statement { code() }, isOpen, type = ObjRecord.Type.Fun)
|
||||
fun addClassFn(name: String, isOpen: Boolean = false, code: ScopeCallable) {
|
||||
createClassField(name, statement(f = code), isOpen, type = ObjRecord.Type.Fun)
|
||||
}
|
||||
|
||||
|
||||
@@ -683,30 +705,46 @@ open class ObjClass(
|
||||
|
||||
override suspend fun invokeInstanceMethod(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
getInstanceMemberOrNull(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: findDeclaringClassOf(name) ?: this
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
|
||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
||||
return del.invokeInstanceMethod(scope, "invokeCallable", Arguments(*allArgs), onNotFoundResult = {
|
||||
// Fallback: property delegation
|
||||
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
||||
propVal.invoke(scope, this, args, decl)
|
||||
propVal.invokeCallable(scope, this, args, decl)
|
||||
})
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
return rec.value.invokeCallable(scope, this, args, decl)
|
||||
} else {
|
||||
// Resolved field or property value
|
||||
val resolved = readField(scope, name)
|
||||
return resolved.value.invoke(scope, this, args, decl)
|
||||
return resolved.value.invokeCallable(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
|
||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||
if (isAnonymous) scope.raiseError("Cannot serialize anonymous class")
|
||||
encoder.encodeObject(scope, classNameObj, ObjString.type.lynonType())
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
val result = mutableMapOf<String, JsonElement>()
|
||||
result["__class_name"] = classNameObj.toJson(scope)
|
||||
classScope?.objects?.forEach { (name, rec) ->
|
||||
if (rec.type.serializable && rec.visibility.isPublic && !rec.isTransient) {
|
||||
result[name] = rec.value.toJson(scope)
|
||||
}
|
||||
}
|
||||
return JsonObject(result)
|
||||
}
|
||||
|
||||
open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
scope.raiseNotImplemented()
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@@ -38,11 +39,14 @@ class ObjCompletableDeferred(val completableDeferred: CompletableDeferred<Obj>):
|
||||
doc = "Complete this deferred with the given value. Subsequent calls have no effect.",
|
||||
params = listOf(ParamDoc("value")),
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjCompletableDeferred>().completableDeferred.complete(args.firstAndOnly())
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjCompletableDeferred>().completableDeferred.complete(scp.args.firstAndOnly())
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.addClassFnDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
|
||||
override val objClass: ObjClass get() = type
|
||||
|
||||
val localDateTime: LocalDateTime by lazy {
|
||||
instant.toLocalDateTime(timeZone)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return localDateTime.toString() + timeZone.toString()
|
||||
}
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
for (cls in objClass.mro) {
|
||||
val rec = cls.members[name]
|
||||
if (rec != null) {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
val prop = rec.value as? ObjProperty
|
||||
?: (rec.value as? Statement)?.execute(scope) as? ObjProperty
|
||||
if (prop != null) {
|
||||
return ObjRecord(prop.callGetter(scope, this, rec.declaringClass ?: cls), rec.isMutable)
|
||||
}
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) {
|
||||
val s = rec.value as Statement
|
||||
return ObjRecord(net.sergeych.lyng.statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = s.execute(scp.createChildScope(newThisObj = this@ObjDateTime))
|
||||
}), rec.isMutable)
|
||||
}
|
||||
return resolveRecord(scope, rec, name, rec.declaringClass ?: cls)
|
||||
}
|
||||
}
|
||||
return super.readField(scope, name)
|
||||
}
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||
return when (other) {
|
||||
is ObjDuration -> ObjDateTime(instant + other.duration, timeZone)
|
||||
else -> super.plus(scope, other)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||
return when (other) {
|
||||
is ObjDuration -> ObjDateTime(instant - other.duration, timeZone)
|
||||
is ObjDateTime -> ObjDuration(instant - other.instant)
|
||||
is ObjInstant -> ObjDuration(instant - other.instant)
|
||||
else -> super.minus(scope, other)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other is ObjDateTime) {
|
||||
return instant.compareTo(other.instant)
|
||||
}
|
||||
if (other is ObjInstant) {
|
||||
return instant.compareTo(other.instant)
|
||||
}
|
||||
return super.compareTo(scope, other)
|
||||
}
|
||||
|
||||
override suspend fun toKotlin(scope: Scope): Any {
|
||||
return localDateTime
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return instant.hashCode() xor timeZone.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is ObjDateTime) return false
|
||||
return instant == other.instant && timeZone == other.timeZone
|
||||
}
|
||||
|
||||
override suspend fun lynonType(): LynonType = LynonType.Other
|
||||
|
||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||
encoder.encodeCached(this) {
|
||||
encodeAny(scope, ObjInstant(instant))
|
||||
encodeCached(timeZone.id) {
|
||||
encodeBinaryData(timeZone.id.encodeToByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement = JsonPrimitive(toRFC3339())
|
||||
|
||||
fun toRFC3339(): String {
|
||||
val s = localDateTime.toString()
|
||||
val tz = if (timeZone == TimeZone.UTC) "Z" else timeZone.id
|
||||
return if (tz.startsWith("+") || tz.startsWith("-") || tz == "Z") s + tz else s + "[" + tz + "]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("DateTime") {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
val args = scope.args
|
||||
return when (val a0 = args.list.getOrNull(0)) {
|
||||
is ObjInstant -> {
|
||||
val tz = when (val a1 = args.list.getOrNull(1)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a1.value)
|
||||
is ObjInt -> UtcOffset(seconds = a1.value.toInt()).asTimeZone()
|
||||
else -> scope.raiseIllegalArgument("invalid timezone: $a1")
|
||||
}
|
||||
ObjDateTime(a0.instant, tz)
|
||||
}
|
||||
|
||||
is ObjInt -> {
|
||||
// DateTime(year, month, day, hour=0, minute=0, second=0, timeZone="UTC")
|
||||
val year = a0.value.toInt()
|
||||
val month = args.list.getOrNull(1)?.toInt() ?: scope.raiseIllegalArgument("month is required")
|
||||
val day = args.list.getOrNull(2)?.toInt() ?: scope.raiseIllegalArgument("day is required")
|
||||
val hour = args.list.getOrNull(3)?.toInt() ?: 0
|
||||
val minute = args.list.getOrNull(4)?.toInt() ?: 0
|
||||
val second = args.list.getOrNull(5)?.toInt() ?: 0
|
||||
val tz = when (val a6 = args.list.getOrNull(6)) {
|
||||
null -> TimeZone.UTC
|
||||
is ObjString -> TimeZone.of(a6.value)
|
||||
is ObjInt -> UtcOffset(seconds = a6.value.toInt()).asTimeZone()
|
||||
else -> scope.raiseIllegalArgument("invalid timezone: $a6")
|
||||
}
|
||||
val ldt = LocalDateTime(year, month, day, hour, minute, second)
|
||||
ObjDateTime(ldt.toInstant(tz), tz)
|
||||
}
|
||||
|
||||
is ObjString -> {
|
||||
val instant = Instant.parse(a0.value)
|
||||
ObjDateTime(instant, TimeZone.UTC)
|
||||
}
|
||||
|
||||
else -> scope.raiseIllegalArgument("can't construct DateTime from ${args.inspect(scope)}")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
return decoder.decodeCached {
|
||||
val instant = (decoder.decodeAny(scope) as ObjInstant).instant
|
||||
val tzId = decoder.decodeCached { decoder.unpackBinaryData().decodeToString() }
|
||||
ObjDateTime(instant, TimeZone.of(tzId))
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.year.toObj() })
|
||||
addPropertyDoc("month", "The month component (1..12).", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.monthNumber.toObj() })
|
||||
addPropertyDoc("dayOfMonth", "The day of month component.", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() })
|
||||
addPropertyDoc("day", "Alias to dayOfMonth.", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() })
|
||||
addPropertyDoc("hour", "The hour component (0..23).", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.hour.toObj() })
|
||||
addPropertyDoc("minute", "The minute component (0..59).", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.minute.toObj() })
|
||||
addPropertyDoc("second", "The second component (0..59).", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.second.toObj() })
|
||||
addPropertyDoc("dayOfWeek", "The day of week (1=Monday, 7=Sunday).", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.dayOfWeek.isoDayNumber.toObj() })
|
||||
addPropertyDoc("timeZone", "The time zone ID (e.g. 'Z', '+02:00', 'Europe/Prague').", type("lyng.String"), moduleName = "lyng.time",
|
||||
getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().timeZone.id.toObj() })
|
||||
|
||||
addFnDoc("toInstant", "Convert this localized date time back to an absolute Instant.", returns = type("lyng.Instant"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjInstant(scp.thisAs<ObjDateTime>().instant) })
|
||||
addFnDoc("toEpochSeconds", "Return the number of full seconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().instant.epochSeconds.toObj() })
|
||||
addFnDoc("toRFC3339", "Return the RFC3339 string representation of this date time, including its timezone offset.", returns = type("lyng.String"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().toRFC3339().toObj() })
|
||||
addFnDoc("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().toRFC3339().toObj() })
|
||||
|
||||
addFnDoc("toEpochMilliseconds", "Return the number of milliseconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().instant.toEpochMilliseconds().toObj() })
|
||||
addFnDoc("toTimeZone", "Return a new DateTime representing the same instant but in a different time zone. " +
|
||||
"Accepts a timezone ID string (e.g., 'UTC', '+02:00') or an integer offset in seconds.",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any"))),
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val tz = when (val a = scp.args.list.getOrNull(0)) {
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> scp.raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
return ObjDateTime(scp.thisAs<ObjDateTime>().instant, tz)
|
||||
}
|
||||
})
|
||||
addFnDoc("toUTC", "Shortcut to convert this date time to the UTC time zone.", returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjDateTime(scp.thisAs<ObjDateTime>().instant, TimeZone.UTC) })
|
||||
|
||||
addFnDoc("addMonths", "Return a new DateTime with the specified number of months added (or subtracted if negative). " +
|
||||
"Normalizes the day of month if necessary (e.g., Jan 31 + 1 month = Feb 28/29).",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("months", type = type("lyng.Int"))),
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val n = scp.args.list.getOrNull(0)?.toInt() ?: 0
|
||||
val res = scp.thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.MONTH, scp.thisAs<ObjDateTime>().timeZone)
|
||||
return ObjDateTime(res, scp.thisAs<ObjDateTime>().timeZone)
|
||||
}
|
||||
})
|
||||
addFnDoc("addYears", "Return a new DateTime with the specified number of years added (or subtracted if negative).",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("years", type = type("lyng.Int"))),
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val n = scp.args.list.getOrNull(0)?.toInt() ?: 0
|
||||
val res = scp.thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.YEAR, scp.thisAs<ObjDateTime>().timeZone)
|
||||
return ObjDateTime(res, scp.thisAs<ObjDateTime>().timeZone)
|
||||
}
|
||||
})
|
||||
|
||||
addClassFn("now", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val tz = when (val a = scp.args.list.getOrNull(0)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> scp.raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
return ObjDateTime(kotlin.time.Clock.System.now(), tz)
|
||||
}
|
||||
})
|
||||
|
||||
addClassFnDoc("parseRFC3339",
|
||||
"Parse an RFC3339 string into a DateTime object. " +
|
||||
"Note: if the string does not specify a timezone, UTC is assumed.",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("string", type = type("lyng.String"))),
|
||||
returns = type("lyng.DateTime"),
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val s = (scp.args.firstAndOnly() as ObjString).value
|
||||
// kotlinx-datetime's Instant.parse handles RFC3339
|
||||
// But we want to preserve the offset if present for DateTime.
|
||||
// However, Instant.parse("...") always gives an Instant.
|
||||
// If we want the specific offset from the string, we might need a more complex parse.
|
||||
// For now, let's stick to parsing it as Instant and converting to UTC or specified TZ.
|
||||
// Actually, if the string has an offset, Instant.parse handles it but returns UTC instant.
|
||||
|
||||
// Let's try to detect if there is an offset in the string.
|
||||
// If not, use UTC.
|
||||
val instant = Instant.parse(s)
|
||||
|
||||
// RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH
|
||||
val tz = try {
|
||||
if (s.endsWith("Z", ignoreCase = true)) {
|
||||
TimeZone.of("Z")
|
||||
} else {
|
||||
// Look for the last + or - which is likely the start of the offset
|
||||
val lastPlus = s.lastIndexOf('+')
|
||||
val lastMinus = s.lastIndexOf('-')
|
||||
val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus
|
||||
if (offsetStart > s.lastIndexOf('T')) {
|
||||
// Likely an offset
|
||||
val offsetStr = s.substring(offsetStart)
|
||||
TimeZone.of(offsetStr)
|
||||
} else {
|
||||
TimeZone.UTC
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
TimeZone.UTC
|
||||
}
|
||||
return ObjDateTime(instant, tz)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@@ -37,23 +38,30 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
name = "await",
|
||||
doc = "Suspend until completion and return the result value (or throw if failed).",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjDeferred>().deferred.await() }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.await()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isCompleted",
|
||||
doc = "Whether this deferred has completed (successfully or with an error).",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.isCompleted.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isActive",
|
||||
doc = "Whether this deferred is currently active (not completed and not cancelled).",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val d = thisAs<ObjDeferred>().deferred
|
||||
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = scp.thisAs<ObjDeferred>().deferred
|
||||
return (d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
@@ -61,7 +69,9 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
doc = "Whether this deferred was cancelled.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjDeferred>().deferred.isCancelled.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.isCancelled.toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import kotlin.time.Duration
|
||||
@@ -79,42 +80,54 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Return this duration as a real number of days.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Return this duration as a real number of hours.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "minutes",
|
||||
doc = "Return this duration as a real number of minutes.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "seconds",
|
||||
doc = "Return this duration as a real number of seconds.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "milliseconds",
|
||||
doc = "Return this duration as a real number of milliseconds.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "microseconds",
|
||||
doc = "Return this duration as a real number of microseconds.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj()
|
||||
}
|
||||
)
|
||||
// extensions
|
||||
|
||||
@@ -123,7 +136,9 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.seconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjInt.type.addPropertyDoc(
|
||||
@@ -131,14 +146,18 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.seconds)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "milliseconds",
|
||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjInt.type.addPropertyDoc(
|
||||
@@ -146,14 +165,18 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "seconds",
|
||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.seconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjReal.type.addPropertyDoc(
|
||||
@@ -161,7 +184,9 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.seconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjReal.type.addPropertyDoc(
|
||||
@@ -169,14 +194,18 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "millisecond",
|
||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjInt.type.addPropertyDoc(
|
||||
@@ -184,84 +213,108 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "minutes",
|
||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "minute",
|
||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "minute",
|
||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "hour",
|
||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "hour",
|
||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.days)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Construct a `Duration` equal to this real number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.days)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "day",
|
||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.days)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "day",
|
||||
doc = "Construct a `Duration` equal to this real number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.days)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -17,31 +17,32 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.ClosureScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.*
|
||||
|
||||
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
|
||||
override val objClass: ObjClass get() = type
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("DelegateContext").apply {
|
||||
addFn("get") {
|
||||
val d = thisAs<ObjDynamicContext>().delegate
|
||||
if (d.readCallback != null)
|
||||
raiseIllegalState("get already defined")
|
||||
d.readCallback = requireOnlyArg()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("get", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = scp.thisAs<ObjDynamicContext>().delegate
|
||||
if (d.readCallback != null)
|
||||
scp.raiseIllegalState("get already defined")
|
||||
d.readCallback = scp.requireOnlyArg()
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
|
||||
addFn("set") {
|
||||
val d = thisAs<ObjDynamicContext>().delegate
|
||||
if (d.writeCallback != null)
|
||||
raiseIllegalState("set already defined")
|
||||
d.writeCallback = requireOnlyArg()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("set", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = scp.thisAs<ObjDynamicContext>().delegate
|
||||
if (d.writeCallback != null)
|
||||
scp.raiseIllegalState("set already defined")
|
||||
d.writeCallback = scp.requireOnlyArg()
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -81,11 +82,11 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
||||
val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))
|
||||
return over?.invoke(scope, scope.thisObj, args)
|
||||
return over?.invokeCallable(scope, scope.thisObj, args)
|
||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ package net.sergeych.lyng.obj/*
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
@@ -74,12 +75,18 @@ class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
|
||||
|
||||
init {
|
||||
addClassConst("entries", objEntries )
|
||||
addClassFn("valueOf") {
|
||||
val name = requireOnlyArg<ObjString>()
|
||||
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name")
|
||||
}
|
||||
addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = { thisAs<ObjEnumEntry>().name })
|
||||
addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = { thisAs<ObjEnumEntry>().ordinal })
|
||||
addClassFn("valueOf", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val name = scp.requireOnlyArg<ObjString>()
|
||||
return byName[name] ?: scp.raiseSymbolNotFound("does not exists: enum ${className}.$name")
|
||||
}
|
||||
})
|
||||
addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjEnumEntry>().name
|
||||
})
|
||||
addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjEnumEntry>().ordinal
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,9 @@ open class ObjException(
|
||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
init {
|
||||
constructorMeta = ArgsDeclaration(
|
||||
listOf(ArgsDeclaration.Item("message", defaultValue = statement { ObjString(name) })),
|
||||
listOf(ArgsDeclaration.Item("message", defaultValue = statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scope: Scope): Obj = ObjString(name)
|
||||
}))),
|
||||
Token.Type.RPAREN
|
||||
)
|
||||
}
|
||||
@@ -163,27 +165,33 @@ open class ObjException(
|
||||
}
|
||||
|
||||
val Root = ExceptionClass("Exception").apply {
|
||||
instanceInitializers.add(statement {
|
||||
if (thisObj is ObjInstance) {
|
||||
val msg = get("message")?.value ?: ObjString("Exception")
|
||||
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
|
||||
instanceInitializers.add(statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (scp.thisObj is ObjInstance) {
|
||||
val msg = scp.get("message")?.value ?: ObjString("Exception")
|
||||
(scp.thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
|
||||
|
||||
val stack = captureStackTrace(this)
|
||||
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
|
||||
val stack = captureStackTrace(scp)
|
||||
(scp.thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}))
|
||||
instanceConstructor = statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
})
|
||||
instanceConstructor = statement { ObjVoid }
|
||||
addPropertyDoc(
|
||||
name = "message",
|
||||
doc = "Human‑readable error message.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.message
|
||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||
else -> ObjNull
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return when (val t = scp.thisObj) {
|
||||
is ObjException -> t.message
|
||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||
else -> ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -192,10 +200,12 @@ open class ObjException(
|
||||
doc = "Extra data associated with the exception.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.extraData
|
||||
else -> ObjNull
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return when (val t = scp.thisObj) {
|
||||
is ObjException -> t.extraData
|
||||
else -> ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -204,11 +214,13 @@ open class ObjException(
|
||||
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return when (val t = scp.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -216,23 +228,26 @@ open class ObjException(
|
||||
name = "toString",
|
||||
doc = "Human‑readable string representation of the error.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val msg = when (val t = thisObj) {
|
||||
is ObjException -> t.message.value
|
||||
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value
|
||||
?: t.objClass.className
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val msg = when (val t = scp.thisObj) {
|
||||
is ObjException -> t.message.value
|
||||
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value
|
||||
?: t.objClass.className
|
||||
|
||||
else -> t.objClass.className
|
||||
else -> t.objClass.className
|
||||
}
|
||||
val stack = when (val t = scp.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
val at = stack.list.firstOrNull()?.toString(scp) ?: ObjString("(unknown)")
|
||||
return ObjString("${scp.thisObj.objClass.className}: $msg at $at")
|
||||
}
|
||||
}
|
||||
val stack = when (val t = thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)")
|
||||
ObjString("${thisObj.objClass.className}: $msg at $at")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val op = ProtectedOp()
|
||||
|
||||
@@ -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.
|
||||
@@ -45,28 +45,31 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
|
||||
doc = "Send a value to the flow consumer. Suspends if back‑pressured; no‑ops after consumer stops.",
|
||||
params = listOf(ParamDoc("value", type("lyng.Any"))),
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val data = requireOnlyArg<Obj>()
|
||||
try {
|
||||
val channel = thisAs<ObjFlowBuilder>().output
|
||||
if (!channel.isClosedForSend)
|
||||
channel.send(data)
|
||||
else
|
||||
// Flow consumer is no longer collecting; signal producer to stop
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} catch (x: Exception) {
|
||||
// Any failure to send (including closed channel) should gracefully stop the producer.
|
||||
if (x is CancellationException) {
|
||||
// Cancellation is a normal control-flow event
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} else {
|
||||
// Treat other send failures as normal flow termination as well
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val data = scp.requireOnlyArg<Obj>()
|
||||
try {
|
||||
val channel = scp.thisAs<ObjFlowBuilder>().output
|
||||
if (!channel.isClosedForSend)
|
||||
channel.send(data)
|
||||
else
|
||||
// Flow consumer is no longer collecting; signal producer to stop
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} catch (x: Exception) {
|
||||
// Any failure to send (including closed channel) should gracefully stop the producer.
|
||||
if (x is CancellationException) {
|
||||
// Cancellation is a normal control-flow event
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} else {
|
||||
// Treat other send failures as normal flow termination as well
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
}
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,15 +106,21 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
|
||||
name = "iterator",
|
||||
doc = "Create a pull‑based iterator over this flow. Each step resumes the producer as needed.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val objFlow = thisAs<ObjFlow>()
|
||||
ObjFlowIterator(statement {
|
||||
objFlow.producer.execute(
|
||||
ClosureScope(this, objFlow.scope)
|
||||
)
|
||||
})
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val objFlow = scp.thisAs<ObjFlow>()
|
||||
return ObjFlowIterator(statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp2: Scope): Obj {
|
||||
objFlow.producer.execute(
|
||||
ClosureScope(scp2, objFlow.scope)
|
||||
)
|
||||
return ObjVoid
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,27 +173,36 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
|
||||
name = "hasNext",
|
||||
doc = "Whether another element is available from the flow.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjFlowIterator>().hasNext(this).toObj() }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFlowIterator>().hasNext(scp).toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Receive the next element from the flow or throw if completed.",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val x = thisAs<ObjFlowIterator>()
|
||||
x.next(this)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.thisAs<ObjFlowIterator>()
|
||||
return x.next(scp)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop iteration and cancel the underlying flow producer.",
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val x = thisAs<ObjFlowIterator>()
|
||||
x.cancel()
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.thisAs<ObjFlowIterator>()
|
||||
x.cancel()
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (cls.className == "Obj") break
|
||||
val mangled = cls.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
||||
if (canAccessMember(rec.visibility, cls, caller, name)) {
|
||||
return resolveRecord(scope, rec, name, cls)
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// 2. Unmangled storage
|
||||
instanceScope.objects[name]?.let { rec ->
|
||||
val decl = rec.declaringClass
|
||||
// Unmangled access is only allowed if it's public OR we are inside the same instance's method
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller)) {
|
||||
if (canAccessMember(rec.visibility, decl, caller, name)) {
|
||||
return resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
}
|
||||
@@ -174,7 +173,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (cls.className == "Obj") break
|
||||
val mangled = cls.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
|
||||
updateRecord(scope, rec, name, newValue, cls)
|
||||
return
|
||||
}
|
||||
@@ -184,7 +183,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// 2. Unmangled storage
|
||||
instanceScope.objects[name]?.let { rec ->
|
||||
val decl = rec.declaringClass
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, decl, caller)) {
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
||||
updateRecord(scope, rec, name, newValue, decl)
|
||||
return
|
||||
}
|
||||
@@ -219,7 +218,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
override suspend fun invokeInstanceMethod(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
val caller = scope.currentClassCtx
|
||||
|
||||
@@ -232,7 +231,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, decl)
|
||||
return rec.value.invokeCallable(instanceScope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +246,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, c)
|
||||
return rec.value.invokeCallable(instanceScope, this, args, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +255,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, c)
|
||||
return rec.value.invokeCallable(instanceScope, this, args, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,28 +271,28 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||
|
||||
// For delegated member, try 'invoke' first if it's a function-like call
|
||||
// For delegated member, try 'invokeCallable' first if it's a function-like call
|
||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
||||
return del.invokeInstanceMethod(scope, "invokeCallable", Arguments(*allArgs), onNotFoundResult = {
|
||||
// Fallback: property delegation (getValue then call result)
|
||||
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
||||
propVal.invoke(scope, this, args, rec.declaringClass ?: cls)
|
||||
propVal.invokeCallable(scope, this, args, rec.declaringClass ?: cls)
|
||||
})
|
||||
}
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller))
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl.className})"
|
||||
"can't invokeCallable method $name (declared in ${decl.className})"
|
||||
)
|
||||
)
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(
|
||||
return rec.value.invokeCallable(
|
||||
instanceScope,
|
||||
this,
|
||||
args,
|
||||
@@ -301,7 +300,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
)
|
||||
} else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) {
|
||||
val resolved = readField(scope, name)
|
||||
return resolved.value.invoke(scope, this, args, resolved.declaringClass)
|
||||
return resolved.value.invokeCallable(scope, this, args, resolved.declaringClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +338,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// values, so we save size of the construction:
|
||||
|
||||
// using objlist allow for some optimizations:
|
||||
val params = meta.params.map { readField(scope, it.name).value }
|
||||
val params = meta.params.filter { !it.isTransient }.map { readField(scope, it.name).value }
|
||||
encoder.encodeAnyList(scope, params)
|
||||
val vars = serializingVars.values.map { it.value }
|
||||
if (vars.isNotEmpty()) {
|
||||
@@ -358,8 +357,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
val result = mutableMapOf<String, JsonElement>()
|
||||
val meta = objClass.constructorMeta
|
||||
?: scope.raiseError("can't serialize non-serializable object (no constructor meta)")
|
||||
for (entry in meta.params)
|
||||
result[entry.name] = readField(scope, entry.name).value.toJson(scope)
|
||||
for (entry in meta.params) {
|
||||
if (!entry.isTransient)
|
||||
result[entry.name] = readField(scope, entry.name).value.toJson(scope)
|
||||
}
|
||||
for (i in serializingVars) {
|
||||
// remove T:: prefix from the field name for JSON
|
||||
val parts = i.key.split("::")
|
||||
@@ -378,7 +379,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
it.value.type.serializable &&
|
||||
it.value.type == ObjRecord.Type.Field &&
|
||||
it.value.isMutable &&
|
||||
!metaParams.contains(it.key)
|
||||
!metaParams.contains(it.key) &&
|
||||
!it.value.isTransient
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +401,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
protected val comparableVars: Map<String, ObjRecord> by lazy {
|
||||
instanceScope.objects.filter {
|
||||
it.value.type.comparable && (it.value.type != ObjRecord.Type.Field || it.value.isMutable)
|
||||
it.value.type.comparable && (it.value.type != ObjRecord.Type.Field || it.value.isMutable) && !it.value.isTransient
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +435,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
// Visibility: declaring class is the qualified ancestor for mangled storage
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
return instance.resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
@@ -442,7 +444,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
@@ -456,7 +458,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||
val decl = r.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(r.visibility, decl, caller))
|
||||
if (!canAccessMember(r.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
|
||||
return instance.resolveRecord(scope, r, name, decl)
|
||||
@@ -468,7 +470,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope.objects[mangled]?.let { f ->
|
||||
val decl = f.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't assign to field $name (declared in ${decl.className})"
|
||||
@@ -482,7 +484,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope[name]?.let { f ->
|
||||
val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
||||
@@ -495,7 +497,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||
val decl = r.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(r.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(r.effectiveWriteVisibility, decl, caller, name))
|
||||
ObjIllegalAccessException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
||||
if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name")
|
||||
if (r.value.assign(scope, newValue) == null) r.value = newValue
|
||||
@@ -505,18 +507,18 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
// Qualified method dispatch must start from the specified ancestor, not from the instance scope.
|
||||
memberFromAncestor(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable method $name (declared in ${decl.className})"))
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(instance.instanceScope, instance, args)
|
||||
return rec.value.invokeCallable(instance.instanceScope, instance, args)
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = saved
|
||||
}
|
||||
@@ -526,23 +528,23 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
||||
"can't invokeCallable method $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(instance.instanceScope, instance, args)
|
||||
return rec.value.invokeCallable(instance.instanceScope, instance, args)
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
return onNotFoundResult?.invoke() ?: scope.raiseSymbolNotFound(name)
|
||||
return onNotFoundResult?.call() ?: scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
override fun toString(): String = instance.toString()
|
||||
|
||||
@@ -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.
|
||||
@@ -28,13 +28,26 @@ import net.sergeych.lynon.LynonType
|
||||
class ObjInstanceClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
val args = decoder.decodeAnyList(scope)
|
||||
val actualSize = constructorMeta?.params?.size ?: 0
|
||||
if (args.size > actualSize)
|
||||
scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}")
|
||||
val newScope = scope.createChildScope(args = Arguments(args))
|
||||
val instance = createInstance(newScope)
|
||||
initializeInstance(instance, newScope.args, runConstructors = false)
|
||||
val serializedArgs = decoder.decodeAnyList(scope)
|
||||
val meta = constructorMeta ?: scope.raiseError("no constructor meta for $name")
|
||||
val nonTransientCount = meta.params.count { !it.isTransient }
|
||||
if (serializedArgs.size != nonTransientCount)
|
||||
scope.raiseIllegalArgument("constructor $name expects $nonTransientCount non-transient arguments, but serialized version has ${serializedArgs.size}")
|
||||
|
||||
var sIdx = 0
|
||||
val namedArgs = mutableMapOf<String, Obj>()
|
||||
for (p in meta.params) {
|
||||
if (!p.isTransient) {
|
||||
namedArgs[p.name] = serializedArgs[sIdx++]
|
||||
} else if (p.defaultValue == null) {
|
||||
// If transient parameter has no default value, we use ObjNull to avoid "too few arguments" error
|
||||
namedArgs[p.name] = ObjNull
|
||||
}
|
||||
}
|
||||
// Using named arguments allows the constructor to apply default values for transient parameters
|
||||
val newArgs = Arguments(list = emptyList<Obj>(), named = namedArgs)
|
||||
val instance = createInstance(scope.createChildScope(args = newArgs))
|
||||
initializeInstance(instance, newArgs, runConstructors = false)
|
||||
return instance.apply {
|
||||
deserializeStateVars(scope, decoder)
|
||||
invokeInstanceMethod(scope, "onDeserialized") { ObjVoid }
|
||||
|
||||
@@ -17,18 +17,17 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonSettings
|
||||
import net.sergeych.lynon.LynonType
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.time.isDistantFuture
|
||||
import kotlin.time.isDistantPast
|
||||
|
||||
@@ -113,7 +112,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
return ObjInstant(
|
||||
when (a0) {
|
||||
null -> {
|
||||
val t = Clock.System.now()
|
||||
val t = kotlin.time.Clock.System.now()
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond)
|
||||
}
|
||||
is ObjInt -> Instant.fromEpochSeconds(a0.value)
|
||||
@@ -122,6 +121,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
val nanos = (a0.value - seconds) * 1e9
|
||||
Instant.fromEpochSeconds(seconds, nanos.toLong())
|
||||
}
|
||||
is ObjString -> Instant.parse(a0.value)
|
||||
is ObjInstant -> a0.instant
|
||||
|
||||
else -> {
|
||||
@@ -156,9 +156,11 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
doc = "Return the number of seconds since the Unix epoch as a real number (including fractions).",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = {
|
||||
val instant = thisAs<ObjInstant>().instant
|
||||
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val instant = scp.thisAs<ObjInstant>().instant
|
||||
return ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
@@ -166,69 +168,143 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
doc = "Whether this instant represents the distant future.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjInstant>().instant.isDistantFuture.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.isDistantFuture.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isDistantPast",
|
||||
doc = "Whether this instant represents the distant past.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjInstant>().instant.isDistantPast.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.isDistantPast.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "epochWholeSeconds",
|
||||
doc = "Return the number of full seconds since the Unix epoch.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjInt(thisAs<ObjInstant>().instant.epochSeconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt(scp.thisAs<ObjInstant>().instant.epochSeconds)
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "nanosecondsOfSecond",
|
||||
doc = "The number of nanoseconds within the current second.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjInt(thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt(scp.thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToMinute",
|
||||
doc = "Truncate this instant to the nearest minute.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
val tz = TimeZone.UTC
|
||||
val dt = t.toLocalDateTime(tz)
|
||||
val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0)
|
||||
return ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToSecond",
|
||||
doc = "Truncate this instant to the nearest second.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
return ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToMillisecond",
|
||||
doc = "Truncate this instant to the nearest millisecond.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
||||
LynonSettings.InstantTruncateMode.Millisecond
|
||||
)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
return ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
||||
LynonSettings.InstantTruncateMode.Millisecond
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToMicrosecond",
|
||||
doc = "Truncate this instant to the nearest microsecond.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
|
||||
LynonSettings.InstantTruncateMode.Microsecond
|
||||
)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
return ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
|
||||
LynonSettings.InstantTruncateMode.Microsecond
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "toRFC3339",
|
||||
doc = "Return the RFC3339 string representation of this instant in UTC (e.g., '1970-01-01T00:00:00Z').",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.toString().toObj()
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "toSortableString",
|
||||
doc = "Alias to toRFC3339.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.toString().toObj()
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "toDateTime",
|
||||
doc = "Convert this absolute instant to a localized DateTime object in the specified time zone. " +
|
||||
"Accepts a timezone ID string (e.g., 'UTC', '+02:00') or an integer offset in seconds. " +
|
||||
"If no argument is provided, the system's current default time zone is used.",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any", true))),
|
||||
returns = type("lyng.DateTime"),
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val tz = when (val a = scp.args.list.getOrNull(0)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> scp.raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
return ObjDateTime(scp.thisAs<ObjInstant>().instant, tz)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// class members
|
||||
|
||||
addClassConst("distantFuture", distantFuture)
|
||||
addClassConst("distantPast", distantPast)
|
||||
addClassFn("now") {
|
||||
ObjInstant(Clock.System.now())
|
||||
}
|
||||
addClassFn("now", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInstant(kotlin.time.Clock.System.now())
|
||||
})
|
||||
// addFn("epochMilliseconds") {
|
||||
// ObjInt(instant.toEpochMilliseconds())
|
||||
// }
|
||||
|
||||
@@ -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.
|
||||
@@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@@ -183,10 +184,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
name = "toInt",
|
||||
doc = "Returns this integer (identity operation).",
|
||||
returns = net.sergeych.lyng.miniast.type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@@ -35,13 +37,15 @@ val ObjIterable by lazy {
|
||||
doc = "Collect elements of this iterable into a new list.",
|
||||
type = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(scp, "next"))
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -52,16 +56,19 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Bool"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val obj = args.firstAndOnly()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
|
||||
return@addFnDoc ObjTrue
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val obj = scp.args.firstAndOnly()
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
if (obj.equals(scp, it.invokeInstanceMethod(scp, "next")))
|
||||
return ObjTrue
|
||||
}
|
||||
return ObjFalse
|
||||
}
|
||||
}
|
||||
ObjFalse
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "indexOf",
|
||||
@@ -69,34 +76,39 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Int"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val obj = args.firstAndOnly()
|
||||
var index = 0
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
|
||||
return@addFnDoc ObjInt(index.toLong())
|
||||
index++
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val obj = scp.args.firstAndOnly()
|
||||
var index = 0
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
if (obj.equals(scp, it.invokeInstanceMethod(scp, "next")))
|
||||
return ObjInt(index.toLong())
|
||||
index++
|
||||
}
|
||||
return ObjInt(-1L)
|
||||
}
|
||||
}
|
||||
ObjInt(-1L)
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "toSet",
|
||||
doc = "Collect elements of this iterable into a new set.",
|
||||
type = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
if( this.thisObj.isInstanceOf(ObjSet.type) )
|
||||
this.thisObj
|
||||
else {
|
||||
val result = mutableSetOf<Obj>()
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return if( scp.thisObj.isInstanceOf(ObjSet.type) )
|
||||
scp.thisObj
|
||||
else {
|
||||
val result = mutableSetOf<Obj>()
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(scp, "next"))
|
||||
}
|
||||
ObjSet(result)
|
||||
}
|
||||
ObjSet(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -106,16 +118,20 @@ val ObjIterable by lazy {
|
||||
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
|
||||
type = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableMapOf<Obj, Obj>()
|
||||
this.thisObj.enumerate(this) { pair ->
|
||||
when (pair) {
|
||||
is ObjMapEntry -> result[pair.key] = pair.value
|
||||
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1)
|
||||
}
|
||||
true
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val result = mutableMapOf<Obj, Obj>()
|
||||
scp.thisObj.enumerate(scp, object : EnumerateCallback {
|
||||
override suspend fun call(pair: Obj): Boolean {
|
||||
when (pair) {
|
||||
is ObjMapEntry -> result[pair.key] = pair.value
|
||||
else -> result[pair.getAt(scp, ObjInt(0))] = pair.getAt(scp, ObjInt(1))
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
return ObjMap(result)
|
||||
}
|
||||
ObjMap(result)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -124,31 +140,37 @@ val ObjIterable by lazy {
|
||||
doc = "Build a map from elements using the lambda result as key.",
|
||||
params = listOf(ParamDoc("keySelector")),
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val association = requireOnlyArg<Statement>()
|
||||
val result = ObjMap()
|
||||
thisObj.toFlow(this).collect {
|
||||
result.map[association.call(this, it)] = it
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val association = scp.requireOnlyArg<Statement>()
|
||||
val result = ObjMap()
|
||||
scp.thisObj.toFlow(scp).collect {
|
||||
result.map[association.invokeCallable(scp, scp.thisObj, it)] = it
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "forEach",
|
||||
doc = "Apply the lambda to each element in iteration order.",
|
||||
params = listOf(ParamDoc("action")),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
val fn = requiredArg<Statement>(0)
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
val x = it.invokeInstanceMethod(this, "next")
|
||||
fn.execute(this.createChildScope(Arguments(listOf(x))))
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
val fn = scp.requiredArg<Statement>(0)
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
val x = it.invokeInstanceMethod(scp, "next")
|
||||
fn.execute(scp.createChildScope(Arguments(listOf(x))))
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "map",
|
||||
@@ -156,15 +178,18 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
result.add(fn.call(this, it))
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val fn = scp.requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
scp.thisObj.toFlow(scp).collect {
|
||||
result.add(fn.invokeCallable(scp, scp.thisObj, it))
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "mapNotNull",
|
||||
@@ -172,46 +197,56 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
val transformed = fn.call(this, it)
|
||||
if( transformed != ObjNull) result.add(transformed)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val fn = scp.requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
scp.thisObj.toFlow(scp).collect {
|
||||
val transformed = fn.invokeCallable(scp, scp.thisObj, it)
|
||||
if( transformed != ObjNull) result.add(transformed)
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "take",
|
||||
doc = "Take the first N elements and return them as a list.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
var n = requireOnlyArg<ObjInt>().value.toInt()
|
||||
val result = mutableListOf<Obj>()
|
||||
if (n > 0) {
|
||||
thisObj.enumerate(this) {
|
||||
result.add(it)
|
||||
--n > 0
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
var n = scp.requireOnlyArg<ObjInt>().value.toInt()
|
||||
val result = mutableListOf<Obj>()
|
||||
if (n > 0) {
|
||||
scp.thisObj.enumerate(scp, object : EnumerateCallback {
|
||||
override suspend fun call(element: Obj): Boolean {
|
||||
result.add(element)
|
||||
return --n > 0
|
||||
}
|
||||
})
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "isEmpty",
|
||||
doc = "Whether the iterable has no elements.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
ObjBool(
|
||||
this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
.invokeInstanceMethod(this, "hasNext").toBool()
|
||||
.not()
|
||||
)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjBool(
|
||||
scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
.invokeInstanceMethod(scp, "hasNext").toBool()
|
||||
.not()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -220,25 +255,31 @@ val ObjIterable by lazy {
|
||||
doc = "Return a new list sorted using the provided comparator `(a, b) -> Int`.",
|
||||
params = listOf(ParamDoc("comparator")),
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val list = thisObj.callMethod<ObjList>(this, "toList")
|
||||
val comparator = requireOnlyArg<Statement>()
|
||||
list.quicksort { a, b ->
|
||||
comparator.call(this, a, b).toInt()
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val list = scp.thisObj.callMethod<ObjList>(scp, "toList")
|
||||
val comparator = scp.requireOnlyArg<Statement>()
|
||||
list.quicksort { a, b ->
|
||||
comparator.invokeCallable(scp, scp.thisObj, a, b).toInt()
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "reversed",
|
||||
doc = "Return a new list with elements in reverse order.",
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val list = thisObj.callMethod<ObjList>(this, "toList")
|
||||
list.list.reverse()
|
||||
list
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val list = scp.thisObj.callMethod<ObjList>(scp, "toList")
|
||||
list.list.reverse()
|
||||
return list
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@@ -38,44 +40,50 @@ val ObjIterator by lazy {
|
||||
doc = "Optional hint to stop iteration early and free resources.",
|
||||
returns = type("lyng.Void"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether another element is available.",
|
||||
returns = type("lyng.Bool"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
raiseNotImplemented("hasNext() is not implemented")
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("hasNext() is not implemented")
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next element.",
|
||||
returns = type("lyng.Any"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
raiseNotImplemented("next() is not implemented")
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("next() is not implemented")
|
||||
}
|
||||
)
|
||||
// Helper to consume iterator into a list
|
||||
addFnDoc(
|
||||
name = "toList",
|
||||
doc = "Consume this iterator and collect elements into a list.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val out = mutableListOf<Obj>()
|
||||
while (true) {
|
||||
val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool()
|
||||
if (!has) break
|
||||
val v = thisObj.invokeInstanceMethod(this, "next")
|
||||
out += v
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val out = mutableListOf<Obj>()
|
||||
while (true) {
|
||||
val has = scp.thisObj.invokeInstanceMethod(scp, "hasNext").toBool()
|
||||
if (!has) break
|
||||
val v = scp.thisObj.invokeInstanceMethod(scp, "next")
|
||||
out += v
|
||||
}
|
||||
return ObjList(out.toMutableList())
|
||||
}
|
||||
}
|
||||
ObjList(out.toMutableList())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -22,6 +22,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
|
||||
/**
|
||||
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
|
||||
@@ -33,8 +34,12 @@ class ObjKotlinIterator(val iterator: Iterator<Any?>) : Obj() {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("KotlinIterator", ObjIterator).apply {
|
||||
addFn("next") { thisAs<ObjKotlinIterator>().iterator.next().toObj() }
|
||||
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjKotlinIterator>().iterator.next().toObj()
|
||||
})
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjKotlinIterator>().iterator.hasNext().toObj()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,12 +55,14 @@ class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("KotlinIterator", ObjIterator).apply {
|
||||
addFn("next") {
|
||||
thisAs<ObjKotlinObjIterator>().iterator.next()
|
||||
}
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjKotlinObjIterator>().iterator.hasNext().toObj()
|
||||
}
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjKotlinObjIterator>().iterator.next()
|
||||
})
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjKotlinObjIterator>().iterator.hasNext().toObj()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,8 +78,8 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
|
||||
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||
val next = iterator.getInstanceMethod(scope, "next")
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
emit(next.invoke(scope, iterator))
|
||||
while (hasNext.invokeCallable(scope, iterator).toBool()) {
|
||||
emit(next.invokeCallable(scope, iterator))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@@ -111,13 +112,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
val next2 = it2.getInstanceMethod(scope, "next")
|
||||
|
||||
while (it1.hasNext()) {
|
||||
if (!hasNext2.invoke(scope, it2).toBool()) return 1 // I'm longer
|
||||
if (!hasNext2.invokeCallable(scope, it2).toBool()) return 1 // I'm longer
|
||||
val v1 = it1.next()
|
||||
val v2 = next2.invoke(scope, it2)
|
||||
val v2 = next2.invokeCallable(scope, it2)
|
||||
val d = v1.compareTo(scope, v2)
|
||||
if (d != 0) return d
|
||||
}
|
||||
return if (hasNext2.invoke(scope, it2).toBool()) -1 else 0
|
||||
return if (hasNext2.invokeCallable(scope, it2).toBool()) -1 else 0
|
||||
}
|
||||
return -2
|
||||
}
|
||||
@@ -169,9 +170,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
return list.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
for (item in list) {
|
||||
if (!callback(item)) break
|
||||
if (!callback.call(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +180,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
get() = type
|
||||
|
||||
override suspend fun toKotlin(scope: Scope): Any {
|
||||
return list.map { it.toKotlin(scope) }
|
||||
val res = ArrayList<Any?>(list.size)
|
||||
for (i in list) res.add(i.toKotlin(scope))
|
||||
return res
|
||||
}
|
||||
|
||||
suspend fun quicksort(compare: suspend (Obj, Obj) -> Int) = quicksort(compare, 0, list.size - 1)
|
||||
@@ -230,7 +233,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
override suspend fun lynonType(): LynonType = LynonType.List
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonArray(list.map { it.toJson(scope) })
|
||||
val res = ArrayList<JsonElement>(list.size)
|
||||
for (i in list) res.add(i.toJson(scope))
|
||||
return JsonArray(res)
|
||||
}
|
||||
|
||||
override suspend fun defaultToString(scope: Scope): ObjString {
|
||||
@@ -256,256 +261,290 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
doc = "Number of elements in this list.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val s = (this.thisObj as ObjList).list.size
|
||||
s.toObj()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return (scp.thisObj as ObjList).list.size.toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "add",
|
||||
doc = "Append one or more elements to the end of this list.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
for (a in args) l.add(a)
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
for (a in scp.args) l.add(a)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "insertAt",
|
||||
doc = "Insert elements starting at the given index.",
|
||||
params = listOf(ParamDoc("index", type("lyng.Int"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
||||
val l = thisAs<ObjList>()
|
||||
var index = requiredArg<ObjInt>(0).value.toInt()
|
||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (scp.args.size < 2) scp.raiseError("addAt takes 2+ arguments")
|
||||
val l = scp.thisAs<ObjList>()
|
||||
var index = scp.requiredArg<ObjInt>(0).value.toInt()
|
||||
for (i in 1..<scp.args.size) l.list.add(index++, scp.args[i])
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "removeAt",
|
||||
doc = "Remove element at index, or a range [start,end) if two indices are provided. Returns the list.",
|
||||
params = listOf(ParamDoc("start", type("lyng.Int")), ParamDoc("end", type("lyng.Int"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
val start = requiredArg<ObjInt>(0).value.toInt()
|
||||
if (args.size == 2) {
|
||||
val end = requireOnlyArg<ObjInt>().value.toInt()
|
||||
self.list.subList(start, end).clear()
|
||||
} else
|
||||
self.list.removeAt(start)
|
||||
self
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
val start = scp.requiredArg<ObjInt>(0).value.toInt()
|
||||
if (scp.args.size == 2) {
|
||||
val end = scp.requireOnlyArg<ObjInt>().value.toInt()
|
||||
self.list.subList(start, end).clear()
|
||||
} else
|
||||
self.list.removeAt(start)
|
||||
return self
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "removeLast",
|
||||
doc = "Remove the last element or the last N elements if a count is provided. Returns the list.",
|
||||
params = listOf(ParamDoc("count", type("lyng.Int"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
if (args.isNotEmpty()) {
|
||||
val count = requireOnlyArg<ObjInt>().value.toInt()
|
||||
val size = self.list.size
|
||||
if (count >= size) self.list.clear()
|
||||
else self.list.subList(size - count, size).clear()
|
||||
} else self.list.removeLast()
|
||||
self
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
if (scp.args.isNotEmpty()) {
|
||||
val count = scp.requireOnlyArg<ObjInt>().value.toInt()
|
||||
val size = self.list.size
|
||||
if (count >= size) self.list.clear()
|
||||
else self.list.subList(size - count, size).clear()
|
||||
} else self.list.removeLast()
|
||||
return self
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "removeRange",
|
||||
doc = "Remove a range of elements. Accepts a Range or (start, endInclusive). Returns the list.",
|
||||
params = listOf(ParamDoc("range")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
val list = self.list
|
||||
val range = requiredArg<Obj>(0)
|
||||
if (range is ObjRange) {
|
||||
val index = range
|
||||
when {
|
||||
index.start is ObjInt && index.end is ObjInt -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(index.start.toInt(), index.end.toInt() + 1)
|
||||
else
|
||||
list.subList(index.start.toInt(), index.end.toInt())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
val list = self.list
|
||||
val range = scp.requiredArg<Obj>(0)
|
||||
if (range is ObjRange) {
|
||||
val index = range
|
||||
when {
|
||||
index.start is ObjInt && index.end is ObjInt -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(index.start.toInt(), index.end.toInt() + 1)
|
||||
else
|
||||
list.subList(index.start.toInt(), index.end.toInt())
|
||||
}
|
||||
|
||||
index.isOpenStart && !index.isOpenEnd -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(0, index.end!!.toInt() + 1)
|
||||
else
|
||||
list.subList(0, index.end!!.toInt())
|
||||
}
|
||||
index.isOpenStart && !index.isOpenEnd -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(0, index.end!!.toInt() + 1)
|
||||
else
|
||||
list.subList(0, index.end!!.toInt())
|
||||
}
|
||||
|
||||
index.isOpenEnd && !index.isOpenStart -> {
|
||||
list.subList(index.start!!.toInt(), list.size)
|
||||
}
|
||||
index.isOpenEnd && !index.isOpenStart -> {
|
||||
list.subList(index.start!!.toInt(), list.size)
|
||||
}
|
||||
|
||||
index.isOpenStart && index.isOpenEnd -> {
|
||||
list
|
||||
}
|
||||
index.isOpenStart && index.isOpenEnd -> {
|
||||
list
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw RuntimeException("Can't apply range for index: $index")
|
||||
else -> {
|
||||
throw RuntimeException("Can't apply range for index: $index")
|
||||
}
|
||||
}.clear()
|
||||
} else {
|
||||
val start = range.toInt()
|
||||
val end = scp.requiredArg<ObjInt>(1).value.toInt() + 1
|
||||
self.list.subList(start, end).clear()
|
||||
}
|
||||
}.clear()
|
||||
} else {
|
||||
val start = range.toInt()
|
||||
val end = requiredArg<ObjInt>(1).value.toInt() + 1
|
||||
self.list.subList(start, end).clear()
|
||||
return self
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "sortWith",
|
||||
doc = "Sort this list in-place using a comparator function (a, b) -> Int.",
|
||||
params = listOf(ParamDoc("comparator")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val comparator = requireOnlyArg<Statement>()
|
||||
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() }
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val comparator = scp.requireOnlyArg<Statement>()
|
||||
scp.thisAs<ObjList>().quicksort { a, b -> comparator.invokeCallable(scp, scp.thisObj, a, b).toInt() }
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "shuffle",
|
||||
doc = "Shuffle elements of this list in-place.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjList>().list.shuffle()
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjList>().list.shuffle()
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "sum",
|
||||
doc = "Sum elements using dynamic '+' or optimized integer path. Returns null for empty lists.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
val l = self.list
|
||||
if (l.isEmpty()) return@addFnDoc ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
// Fast path: all ints → accumulate as long
|
||||
var i = 0
|
||||
var acc: Long = 0
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
acc += v.value
|
||||
i++
|
||||
} else {
|
||||
// Fallback to generic dynamic '+' accumulation starting from current acc
|
||||
var res: Obj = ObjInt(acc)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
val l = self.list
|
||||
if (l.isEmpty()) return ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
// Fast path: all ints → accumulate as long
|
||||
var i = 0
|
||||
var acc: Long = 0
|
||||
while (i < l.size) {
|
||||
res = res.plus(this, l[i])
|
||||
i++
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
acc += v.value
|
||||
i++
|
||||
} else {
|
||||
// Fallback to generic dynamic '+' accumulation starting from current acc
|
||||
var res: Obj = ObjInt(acc)
|
||||
while (i < l.size) {
|
||||
res = res.plus(scp, l[i])
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
return@addFnDoc res
|
||||
return ObjInt(acc)
|
||||
}
|
||||
// Generic path: dynamic '+' starting from first element
|
||||
var res: Obj = l[0]
|
||||
var k = 1
|
||||
while (k < l.size) {
|
||||
res = res.plus(scp, l[k])
|
||||
k++
|
||||
}
|
||||
return res
|
||||
}
|
||||
return@addFnDoc ObjInt(acc)
|
||||
}
|
||||
// Generic path: dynamic '+' starting from first element
|
||||
var res: Obj = l[0]
|
||||
var k = 1
|
||||
while (k < l.size) {
|
||||
res = res.plus(this, l[k])
|
||||
k++
|
||||
}
|
||||
res
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "min",
|
||||
doc = "Minimum element by natural order. Returns null for empty lists.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return@addFnDoc ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var minVal: Long = Long.MAX_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value < minVal) minVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var minVal: Long = Long.MAX_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value < minVal) minVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (hasOnlyInts) return ObjInt(minVal)
|
||||
}
|
||||
i++
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(scp, res) < 0) res = v
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
if (hasOnlyInts) return@addFnDoc ObjInt(minVal)
|
||||
}
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(this, res) < 0) res = v
|
||||
i++
|
||||
}
|
||||
res
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "max",
|
||||
doc = "Maximum element by natural order. Returns null for empty lists.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return@addFnDoc ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var maxVal: Long = Long.MIN_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value > maxVal) maxVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var maxVal: Long = Long.MIN_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value > maxVal) maxVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (hasOnlyInts) return ObjInt(maxVal)
|
||||
}
|
||||
i++
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(scp, res) > 0) res = v
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
if (hasOnlyInts) return@addFnDoc ObjInt(maxVal)
|
||||
}
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(this, res) > 0) res = v
|
||||
i++
|
||||
}
|
||||
res
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "indexOf",
|
||||
doc = "Index of the first occurrence of the given element, or -1 if not found.",
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
val needle = args.firstAndOnly()
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt && v.value == needle.value) return@addFnDoc ObjInt(i.toLong())
|
||||
i++
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
val needle = scp.args.firstAndOnly()
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt && v.value == needle.value) return ObjInt(i.toLong())
|
||||
i++
|
||||
}
|
||||
return ObjInt((-1).toLong())
|
||||
}
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
if (l[i].compareTo(scp, needle) == 0) return ObjInt(i.toLong())
|
||||
i++
|
||||
}
|
||||
return ObjInt((-1).toLong())
|
||||
}
|
||||
return@addFnDoc ObjInt((-1).toLong())
|
||||
}
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
if (l[i].compareTo(this, needle) == 0) return@addFnDoc ObjInt(i.toLong())
|
||||
i++
|
||||
}
|
||||
ObjInt((-1).toLong())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
@@ -78,21 +79,27 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||
doc = "Key component of this map entry.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMapEntry>().key }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMapEntry>().key
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "value",
|
||||
doc = "Value component of this map entry.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMapEntry>().value }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMapEntry>().value
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of components in this entry (always 2).",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { 2.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = 2.toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -181,9 +188,11 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonObject(
|
||||
map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap()
|
||||
)
|
||||
val res = mutableMapOf<String, JsonElement>()
|
||||
for ((k, v) in map) {
|
||||
res[k.toString(scope).value] = v.toJson(scope)
|
||||
}
|
||||
return JsonObject(res)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -224,94 +233,119 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
}
|
||||
}.apply {
|
||||
implementingNames.add("Delegate")
|
||||
addFn("getValue") {
|
||||
val self = thisAs<ObjMap>()
|
||||
val key = requiredArg<Obj>(1)
|
||||
self.map[key] ?: ObjNull
|
||||
}
|
||||
addFn("setValue") {
|
||||
val self = thisAs<ObjMap>()
|
||||
val key = requiredArg<Obj>(1)
|
||||
val value = requiredArg<Obj>(2)
|
||||
self.map[key] = value
|
||||
self
|
||||
}
|
||||
addFn("bind") {
|
||||
val mode = requiredArg<ObjEnumEntry>(1)
|
||||
if( mode.ordinal.value > 1)
|
||||
raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}")
|
||||
thisObj
|
||||
}
|
||||
addFn("getValue", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjMap>()
|
||||
val key = scp.requiredArg<Obj>(1)
|
||||
return self.map[key] ?: ObjNull
|
||||
}
|
||||
})
|
||||
addFn("setValue", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjMap>()
|
||||
val key = scp.requiredArg<Obj>(1)
|
||||
val value = scp.requiredArg<Obj>(2)
|
||||
self.map[key] = value
|
||||
return self
|
||||
}
|
||||
})
|
||||
addFn("bind", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val mode = scp.requiredArg<ObjEnumEntry>(1)
|
||||
if (mode.ordinal.value > 1)
|
||||
scp.raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}")
|
||||
return scp.thisObj
|
||||
}
|
||||
})
|
||||
addFnDoc(
|
||||
name = "getOrNull",
|
||||
doc = "Get value by key or return null if the key is absent.",
|
||||
params = listOf(ParamDoc("key")),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val key = args.firstAndOnly(pos)
|
||||
thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val key = scp.args.firstAndOnly(scp.pos)
|
||||
return scp.thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "getOrPut",
|
||||
doc = "Get value by key or compute, store, and return the default from a lambda.",
|
||||
params = listOf(ParamDoc("key"), ParamDoc("default")),
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val key = requiredArg<Obj>(0)
|
||||
thisAs<ObjMap>().map.getOrPut(key) {
|
||||
val lambda = requiredArg<Statement>(1)
|
||||
lambda.execute(this)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val key = scp.requiredArg<Obj>(0)
|
||||
return scp.thisAs<ObjMap>().map.getOrPut(key) {
|
||||
val lambda = scp.requiredArg<Statement>(1)
|
||||
lambda.execute(scp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of entries in the map.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjMap).map.size.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjMap).map.size.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "remove",
|
||||
doc = "Remove the entry by key and return the previous value or null if absent.",
|
||||
params = listOf(ParamDoc("key")),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjMap>().map.remove(requiredArg<Obj>(0))?.toObj() ?: ObjNull
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.thisAs<ObjMap>().map.remove(scp.requiredArg<Obj>(0))?.toObj() ?: ObjNull
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "clear",
|
||||
doc = "Remove all entries from this map. Returns the map.",
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjMap>().map.clear()
|
||||
thisObj
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjMap>().map.clear()
|
||||
return scp.thisObj
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "keys",
|
||||
doc = "List of keys in this map.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMap>().map.keys.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMap>().map.keys.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "values",
|
||||
doc = "List of values in this map.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjList(thisAs<ObjMap>().map.values.toMutableList()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjList(scp.thisAs<ObjMap>().map.values.toMutableList())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over map entries as MapEntry objects.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.MapEntry"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjMap>().map.entries.iterator())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -40,12 +40,12 @@ class ObjMutex(val mutex: Mutex): Obj() {
|
||||
params = listOf(ParamDoc("action")),
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val f = requiredArg<Statement>(0)
|
||||
) { scp ->
|
||||
val f = scp.requiredArg<Statement>(0)
|
||||
// Execute user lambda directly in the current scope to preserve the active scope
|
||||
// ancestry across suspension points. The lambda still constructs a ClosureScope
|
||||
// on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body.
|
||||
thisAs<ObjMutex>().mutex.withLock { f.execute(this) }
|
||||
scp.thisAs<ObjMutex>().mutex.withLock { f.execute(scp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
@@ -115,17 +116,17 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
start is ObjChar && end is ObjChar
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
if (start is ObjInt && end is ObjInt) {
|
||||
val s = start.value
|
||||
val e = end.value
|
||||
if (isEndInclusive) {
|
||||
for (i in s..e) {
|
||||
if (!callback(ObjInt.of(i))) break
|
||||
if (!callback.call(ObjInt.of(i))) break
|
||||
}
|
||||
} else {
|
||||
for (i in s..<e) {
|
||||
if (!callback(ObjInt.of(i))) break
|
||||
if (!callback.call(ObjInt.of(i))) break
|
||||
}
|
||||
}
|
||||
} else if (start is ObjChar && end is ObjChar) {
|
||||
@@ -133,11 +134,11 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
val e = end.value
|
||||
if (isEndInclusive) {
|
||||
for (c in s..e) {
|
||||
if (!callback(ObjChar(c))) break
|
||||
if (!callback.call(ObjChar(c))) break
|
||||
}
|
||||
} else {
|
||||
for (c in s..<e) {
|
||||
if (!callback(ObjChar(c))) break
|
||||
if (!callback.call(ObjChar(c))) break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -180,64 +181,79 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
doc = "Start bound of the range or null if open.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().start ?: ObjNull }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().start ?: ObjNull
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "end",
|
||||
doc = "End bound of the range or null if open.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().end ?: ObjNull }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().end ?: ObjNull
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isOpen",
|
||||
doc = "Whether the range is open on either side (no start or no end).",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isIntRange",
|
||||
doc = "True if both bounds are Int values.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isIntRange.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isIntRange.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isCharRange",
|
||||
doc = "True if both bounds are Char values.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isCharRange.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isCharRange.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isEndInclusive",
|
||||
doc = "Whether the end bound is inclusive.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isEndInclusive.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isEndInclusive.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjRange>()
|
||||
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
|
||||
val s = self.start
|
||||
val e = self.end
|
||||
if (s is ObjInt && e is ObjInt) {
|
||||
val start = s.value.toInt()
|
||||
val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
|
||||
// Only for ascending simple ranges; fall back otherwise
|
||||
if (start <= endExclusive) {
|
||||
return@addFnDoc ObjFastIntRangeIterator(start, endExclusive)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRange>()
|
||||
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
|
||||
val s = self.start
|
||||
val e = self.end
|
||||
if (s is ObjInt && e is ObjInt) {
|
||||
val start = s.value.toInt()
|
||||
val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
|
||||
// Only for ascending simple ranges; fall back otherwise
|
||||
if (start <= endExclusive) {
|
||||
return ObjFastIntRangeIterator(start, endExclusive)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ObjRangeIterator(self).apply { init(scp) }
|
||||
}
|
||||
}
|
||||
ObjRangeIterator(self).apply { init() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
|
||||
class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
|
||||
@@ -28,7 +29,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
|
||||
override val objClass: ObjClass get() = type
|
||||
|
||||
fun Scope.init() {
|
||||
fun init(scope: Scope) {
|
||||
val s = self.start
|
||||
val e = self.end
|
||||
if (s is ObjInt && e is ObjInt) {
|
||||
@@ -43,7 +44,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
else
|
||||
(e.value.code - s.value.code)
|
||||
} else {
|
||||
raiseError("not implemented iterator for range of $this")
|
||||
scope.raiseError("not implemented iterator for range of $this")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,12 +67,14 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("RangeIterator", ObjIterator).apply {
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjRangeIterator>().hasNext().toObj()
|
||||
}
|
||||
addFn("next") {
|
||||
thisAs<ObjRangeIterator>().next(this)
|
||||
}
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjRangeIterator>().hasNext().toObj()
|
||||
})
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjRangeIterator>().next(scp)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +97,12 @@ class ObjFastIntRangeIterator(private val start: Int, private val endExclusive:
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("FastIntRangeIterator", ObjIterator).apply {
|
||||
addFn("hasNext") { thisAs<ObjFastIntRangeIterator>().hasNext().toObj() }
|
||||
addFn("next") { thisAs<ObjFastIntRangeIterator>().next(this) }
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFastIntRangeIterator>().hasNext().toObj()
|
||||
})
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFastIntRangeIterator>().next(scp)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -21,6 +21,7 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addConstDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@@ -126,9 +127,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
// roundToInt: number rounded to the nearest integer
|
||||
addConstDoc(
|
||||
name = "roundToInt",
|
||||
value = statement(Pos.builtIn) {
|
||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
},
|
||||
value = statement(Pos.builtIn, f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
}),
|
||||
doc = "This real number rounded to the nearest integer.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
@@ -137,10 +138,11 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
name = "toInt",
|
||||
doc = "Truncate this real number toward zero to an integer.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjInt.of(thisAs<ObjReal>().value.toLong())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt.of(scp.thisAs<ObjReal>().value.toLong())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,9 +64,40 @@ sealed interface ObjRef {
|
||||
}
|
||||
}
|
||||
|
||||
interface RecordProvider {
|
||||
suspend fun getRecord(scope: Scope): ObjRecord
|
||||
}
|
||||
|
||||
fun interface FieldGetter {
|
||||
suspend fun call(obj: Obj, scope: Scope): ObjRecord
|
||||
}
|
||||
|
||||
fun interface FieldSetter {
|
||||
suspend fun call(obj: Obj, scope: Scope, value: Obj)
|
||||
}
|
||||
|
||||
fun interface IndexGetter {
|
||||
suspend fun call(obj: Obj, scope: Scope, index: Obj): Obj
|
||||
}
|
||||
|
||||
fun interface IndexSetter {
|
||||
suspend fun call(obj: Obj, scope: Scope, index: Obj, value: Obj)
|
||||
}
|
||||
|
||||
fun interface MethodInvoker {
|
||||
suspend fun call(obj: Obj, scope: Scope, args: Arguments): Obj
|
||||
}
|
||||
|
||||
/** Runtime-computed read-only reference backed by a lambda. */
|
||||
class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord = fn(scope)
|
||||
class ValueFnRef(private val provider: RecordProvider) : ObjRef {
|
||||
|
||||
constructor(fn: suspend (Scope) -> ObjRecord) : this(object : RecordProvider {
|
||||
override suspend fun getRecord(scope: Scope): ObjRecord = fn(scope)
|
||||
})
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return provider.getRecord(scope)
|
||||
}
|
||||
}
|
||||
|
||||
/** Unary operations supported by ObjRef. */
|
||||
@@ -523,16 +554,16 @@ class FieldRef(
|
||||
) : ObjRef {
|
||||
// 4-entry PIC for reads/writes (guarded by PerfFlags.FIELD_PIC)
|
||||
// Reads
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: FieldGetter? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: FieldGetter? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: FieldGetter? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: FieldGetter? = null
|
||||
|
||||
// Writes
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: FieldSetter? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: FieldSetter? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: FieldSetter? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: FieldSetter? = null
|
||||
|
||||
// Transient per-step cache to optimize read-then-write sequences within the same frame
|
||||
private var tKey: Long = 0L; private var tVer: Int = -1; private var tFrameId: Long = -1L; private var tRecord: ObjRecord? = null
|
||||
@@ -598,7 +629,7 @@ class FieldRef(
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
noteReadHit()
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@@ -612,7 +643,7 @@ class FieldRef(
|
||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@@ -627,7 +658,7 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@@ -643,7 +674,7 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@@ -660,7 +691,7 @@ class FieldRef(
|
||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { _, sc -> sc.raiseError(e.message ?: "no such field: $name") }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { _, sc -> sc.raiseError(e.message ?: "no such field: $name") }
|
||||
throw e
|
||||
}
|
||||
// Install move-to-front with a handle-aware getter; honor PIC size flag
|
||||
@@ -674,7 +705,7 @@ class FieldRef(
|
||||
val clsScope = base.classScope
|
||||
val capturedIdx = clsScope?.getSlotIndexOf(name)
|
||||
if (clsScope != null && capturedIdx != null) {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc ->
|
||||
val scope0 = (obj as ObjClass).classScope!!
|
||||
val r0 = scope0.getSlotRecord(capturedIdx)
|
||||
if (!r0.visibility.isPublic)
|
||||
@@ -682,14 +713,14 @@ class FieldRef(
|
||||
r0
|
||||
}
|
||||
} else {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
}
|
||||
}
|
||||
is ObjInstance -> {
|
||||
val cls = base.objClass
|
||||
val effectiveKey = cls.publicMemberResolution[name]
|
||||
if (effectiveKey != null) {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc ->
|
||||
if (obj is ObjInstance && obj.objClass === cls) {
|
||||
val rec = obj.instanceScope.objects[effectiveKey]
|
||||
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
|
||||
@@ -697,12 +728,12 @@ class FieldRef(
|
||||
} else obj.readField(sc, name)
|
||||
}
|
||||
} else {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// For instances and other types, fall back to name-based lookup per access (slot index may differ per instance)
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
}
|
||||
}
|
||||
return rec
|
||||
@@ -745,7 +776,7 @@ class FieldRef(
|
||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
noteWriteHit()
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
@@ -754,7 +785,7 @@ class FieldRef(
|
||||
val tK = wKey2; val tV = wVer2; val tS = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
@@ -764,7 +795,7 @@ class FieldRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
@@ -775,7 +806,7 @@ class FieldRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
// Slow path
|
||||
if (picCounters) PerfStats.fieldPicSetMiss++
|
||||
@@ -792,7 +823,7 @@ class FieldRef(
|
||||
val clsScope = base.classScope
|
||||
val capturedIdx = clsScope?.getSlotIndexOf(name)
|
||||
if (clsScope != null && capturedIdx != null) {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v ->
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v ->
|
||||
val scope0 = (obj as ObjClass).classScope!!
|
||||
val r0 = scope0.getSlotRecord(capturedIdx)
|
||||
if (!r0.isMutable)
|
||||
@@ -800,14 +831,14 @@ class FieldRef(
|
||||
if (r0.value.assign(sc, v) == null) r0.value = v
|
||||
}
|
||||
} else {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
}
|
||||
}
|
||||
is ObjInstance -> {
|
||||
val cls = base.objClass
|
||||
val effectiveKey = cls.publicMemberResolution[name]
|
||||
if (effectiveKey != null) {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv ->
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, nv ->
|
||||
if (obj is ObjInstance && obj.objClass === cls) {
|
||||
val rec = obj.instanceScope.objects[effectiveKey]
|
||||
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) {
|
||||
@@ -816,12 +847,12 @@ class FieldRef(
|
||||
} else obj.writeField(sc, name, nv)
|
||||
}
|
||||
} else {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// For instances and other types, fall back to generic write (instance slot indices may differ per instance)
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -853,14 +884,14 @@ class FieldRef(
|
||||
if (key != 0L) {
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
@@ -868,7 +899,7 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
@@ -877,12 +908,12 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
if (picCounters) PerfStats.fieldPicMiss++
|
||||
val rec = base.readField(scope, name)
|
||||
// install primary generic getter for this shape
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
return rec.value
|
||||
}
|
||||
}
|
||||
@@ -899,16 +930,16 @@ class IndexRef(
|
||||
private val isOptional: Boolean,
|
||||
) : ObjRef {
|
||||
// Tiny 4-entry PIC for index reads (guarded implicitly by RVAL_FASTPATH); move-to-front on hits
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: IndexGetter? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: IndexGetter? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: IndexGetter? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: IndexGetter? = null
|
||||
|
||||
// Tiny 4-entry PIC for index writes
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: IndexSetter? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: IndexSetter? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: IndexSetter? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: IndexSetter? = null
|
||||
|
||||
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
|
||||
is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion
|
||||
@@ -949,14 +980,14 @@ class IndexRef(
|
||||
if (key != 0L) {
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@@ -964,7 +995,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@@ -973,7 +1004,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
// Miss: resolve and install generic handler
|
||||
if (picCounters) PerfStats.indexPicMiss++
|
||||
@@ -983,7 +1014,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
}
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = IndexGetter { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
return v.asMutable
|
||||
}
|
||||
}
|
||||
@@ -1023,14 +1054,14 @@ class IndexRef(
|
||||
if (key != 0L) {
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@@ -1038,7 +1069,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@@ -1047,7 +1078,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
if (picCounters) PerfStats.indexPicMiss++
|
||||
val v = base.getAt(scope, idx)
|
||||
@@ -1056,7 +1087,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
}
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = IndexGetter { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
return v
|
||||
}
|
||||
}
|
||||
@@ -1093,19 +1124,19 @@ class IndexRef(
|
||||
else -> { key = 0L; ver = -1 }
|
||||
}
|
||||
if (key != 0L) {
|
||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } }
|
||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s.call(base, scope, idx, newValue); return } }
|
||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||
val tk = wKey2; val tv = wVer2; val ts = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||
s(base, scope, idx, newValue); return
|
||||
s.call(base, scope, idx, newValue); return
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||
s(base, scope, idx, newValue); return
|
||||
s.call(base, scope, idx, newValue); return
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
||||
@@ -1113,7 +1144,7 @@ class IndexRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||
s(base, scope, idx, newValue); return
|
||||
s.call(base, scope, idx, newValue); return
|
||||
} }
|
||||
// Miss: perform write and install generic handler
|
||||
base.putAt(scope, idx, newValue)
|
||||
@@ -1122,7 +1153,7 @@ class IndexRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
}
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = IndexSetter { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1173,10 +1204,10 @@ class MethodCallRef(
|
||||
private val isOptional: Boolean,
|
||||
) : ObjRef {
|
||||
// 4-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC)
|
||||
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: MethodInvoker? = null
|
||||
private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: MethodInvoker? = null
|
||||
private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: MethodInvoker? = null
|
||||
private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: MethodInvoker? = null
|
||||
|
||||
// Adaptive PIC (2→4) for methods
|
||||
private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false
|
||||
@@ -1274,7 +1305,7 @@ class MethodCallRef(
|
||||
if (key == mKey1 && ver == mVer1) {
|
||||
if (picCounters) PerfStats.methodPicHit++
|
||||
noteMethodHit()
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
mInvoker2?.let { inv ->
|
||||
@@ -1285,7 +1316,7 @@ class MethodCallRef(
|
||||
val tK = mKey2; val tV = mVer2; val tI = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
if (size4MethodsEnabled()) mInvoker3?.let { inv ->
|
||||
@@ -1297,7 +1328,7 @@ class MethodCallRef(
|
||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
if (size4MethodsEnabled()) mInvoker4?.let { inv ->
|
||||
@@ -1310,7 +1341,7 @@ class MethodCallRef(
|
||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
// Slow path
|
||||
@@ -1323,7 +1354,7 @@ class MethodCallRef(
|
||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") }
|
||||
throw e
|
||||
}
|
||||
// Install move-to-front with a handle-aware invoker; honor PIC size flag
|
||||
@@ -1361,15 +1392,15 @@ class MethodCallRef(
|
||||
val visibility = hierarchyMember.visibility
|
||||
val callable = hierarchyMember.value
|
||||
val decl = hierarchyMember.declaringClass ?: base.objClass
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a ->
|
||||
val inst = obj as ObjInstance
|
||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx))
|
||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
||||
callable.invoke(inst.instanceScope, inst, a)
|
||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
|
||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invokeCallable non-public method $name"))
|
||||
callable.invokeCallable(inst.instanceScope, inst, a)
|
||||
}
|
||||
} else {
|
||||
// Fallback to name-based lookup per call (handles extensions and root members)
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
}
|
||||
}
|
||||
is ObjClass -> {
|
||||
@@ -1377,13 +1408,13 @@ class MethodCallRef(
|
||||
val rec = clsScope?.get(name)
|
||||
if (rec != null) {
|
||||
val callable = rec.value
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> callable.invoke(sc, obj, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> callable.invokeCallable(sc, obj, a) }
|
||||
} else {
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -1447,7 +1478,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) {
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
// Not visible via slot, fallback to other lookups
|
||||
} else {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||
@@ -1499,7 +1530,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) {
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
@@ -1608,7 +1639,7 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||
if (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++
|
||||
}
|
||||
@@ -1652,7 +1683,7 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
return scope.resolve(rec, name)
|
||||
}
|
||||
}
|
||||
@@ -1698,7 +1729,7 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class ObjRegex(val regex: Regex) : Obj() {
|
||||
@@ -49,29 +50,36 @@ class ObjRegex(val regex: Regex) : Obj() {
|
||||
doc = "Whether the entire string matches this regular expression.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(args.firstAndOnly().toString().matches(thisAs<ObjRegex>().regex))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBool(scp.args.firstAndOnly().toString().matches(scp.thisAs<ObjRegex>().regex))
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "find",
|
||||
doc = "Find the first match in the given string.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
returns = type("lyng.RegexMatch", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRegex>().find(requireOnlyArg<ObjString>())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjRegex>().find(scp.requireOnlyArg<ObjString>())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "findAll",
|
||||
doc = "Find all matches in the given string.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.RegexMatch"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val s = requireOnlyArg<ObjString>().value
|
||||
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val s = scp.requireOnlyArg<ObjString>().value
|
||||
return ObjList(scp.thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,21 +133,27 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
|
||||
doc = "List of captured groups with index 0 as the whole match.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objGroups }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objGroups
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "value",
|
||||
doc = "The matched substring.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objValue }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objValue
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "range",
|
||||
doc = "Range of the match in the input (end-exclusive).",
|
||||
type = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objRange }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objRange
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
|
||||
@@ -96,39 +97,55 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
|
||||
doc = "Maximum number of elements the buffer can hold.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRingBuffer>().capacity.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRingBuffer>().capacity.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Current number of elements in the buffer.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRingBuffer>().buffer.size.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRingBuffer>().buffer.size.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements in insertion order (oldest to newest).",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val buffer = thisAs<ObjRingBuffer>().buffer
|
||||
ObjKotlinObjIterator(buffer.iterator())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val buffer = scp.thisAs<ObjRingBuffer>().buffer
|
||||
return ObjKotlinObjIterator(buffer.iterator())
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "add",
|
||||
doc = "Append an element; if full, the oldest element is dropped.",
|
||||
params = listOf(ParamDoc("value", type("lyng.Any"))),
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRingBuffer>()
|
||||
self.buffer.add(scp.requireOnlyArg<Obj>())
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "first",
|
||||
doc = "Return the oldest element in the buffer.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val buffer = (this.thisObj as ObjRingBuffer).buffer
|
||||
if (buffer.size == 0) ObjNull else buffer.first()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val buffer = (scp.thisObj as ObjRingBuffer).buffer
|
||||
return if (buffer.size == 0) ObjNull else buffer.iterator().next()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@@ -46,9 +47,9 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
return set.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
for (item in set) {
|
||||
if (!callback(item)) break
|
||||
if (!callback.call(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,56 +165,64 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
name = "size",
|
||||
doc = "Number of elements in this set.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().set.size.toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().set.size.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "intersect",
|
||||
doc = "Intersection with another set. Returns a new set.",
|
||||
params = listOf(ParamDoc("other", type("lyng.Set"))),
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().mul(this, args.firstAndOnly())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().mul(scp, scp.args.firstAndOnly())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements of this set.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().set.iterator().toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjSet>().set.iterator())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "union",
|
||||
doc = "Union with another set or iterable. Returns a new set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().plus(this, args.firstAndOnly())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().plus(scp, scp.args.firstAndOnly())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "subtract",
|
||||
doc = "Subtract another set or iterable from this set. Returns a new set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().minus(this, args.firstAndOnly())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().minus(scp, scp.args.firstAndOnly())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "remove",
|
||||
doc = "Remove one or more elements. Returns true if the set changed.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val set = thisAs<ObjSet>().set
|
||||
val n = set.size
|
||||
for( x in args.list ) set -= x
|
||||
if( n == set.size ) ObjFalse else ObjTrue
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val set = scp.thisAs<ObjSet>().set
|
||||
val n = set.size
|
||||
for( x in scp.args.list ) set -= x
|
||||
return if( n == set.size ) ObjFalse else ObjTrue
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@@ -139,193 +140,274 @@ data class ObjString(val value: String) : Obj() {
|
||||
name = "iterator",
|
||||
doc = "Iterator over characters of this string.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Char"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjKotlinIterator(thisAs<ObjString>().value.iterator()) }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjString>().value.iterator())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toInt",
|
||||
doc = "Parse this string as an integer or throw if it is not a valid integer.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjInt.of(
|
||||
thisAs<ObjString>().value.toLongOrNull()
|
||||
?: raiseIllegalArgument("can't convert to int: $thisObj")
|
||||
)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjInt.of(
|
||||
scp.thisAs<ObjString>().value.toLongOrNull()
|
||||
?: scp.raiseIllegalArgument("can't convert to int: ${scp.thisObj}")
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "startsWith",
|
||||
doc = "Whether this string starts with the given prefix.",
|
||||
params = listOf(ParamDoc("prefix", type("lyng.String"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjBool(scp.thisAs<ObjString>().value.startsWith(scp.requiredArg<ObjString>(0).value))
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "endsWith",
|
||||
doc = "Whether this string ends with the given suffix.",
|
||||
params = listOf(ParamDoc("suffix", type("lyng.String"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjBool(scp.thisAs<ObjString>().value.endsWith(scp.requiredArg<ObjString>(0).value))
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "length",
|
||||
doc = "Number of UTF-16 code units in this string.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt.of((scp.thisObj as ObjString).value.length.toLong())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "takeLast",
|
||||
doc = "Return a string with the last N characters.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.takeLast(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.takeLast(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "take",
|
||||
doc = "Return a string with the first N characters.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.take(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.take(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "drop",
|
||||
doc = "Drop the first N characters and return the remainder.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.drop(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.drop(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "dropLast",
|
||||
doc = "Drop the last N characters and return the remainder.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.dropLast(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.dropLast(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "lower",
|
||||
doc = "Lowercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.lowercase())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "lowercase",
|
||||
doc = "Lowercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.lowercase())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "upper",
|
||||
doc = "Uppercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.uppercase())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "uppercase",
|
||||
doc = "Uppercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.uppercase())
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "characters",
|
||||
doc = "List of characters of this string.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
ObjList(
|
||||
(this.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
|
||||
)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjList(
|
||||
(scp.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "last",
|
||||
doc = "The last character of this string or throw if the string is empty.",
|
||||
returns = type("lyng.Char"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjChar(thisAs<ObjString>().value.lastOrNull() ?: raiseNoSuchElement("empty string"))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjChar(scp.thisAs<ObjString>().value.lastOrNull() ?: scp.raiseNoSuchElement("empty string"))
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "encodeUtf8",
|
||||
doc = "Encode this string as UTF-8 bytes.",
|
||||
returns = type("lyng.Buffer"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBuffer(scp.thisAs<ObjString>().value.encodeToByteArray().asUByteArray())
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Alias for length: the number of characters (code units) in this string.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt.of((scp.thisObj as ObjString).value.length.toLong())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toReal",
|
||||
doc = "Parse this string as a real number (floating point).",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjReal.of(thisAs<ObjString>().value.toDouble())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjReal.of(scp.thisAs<ObjString>().value.toDouble())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "trim",
|
||||
doc = "Return a copy of this string with leading and trailing whitespace removed.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.trim().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.trim())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "isBlank",
|
||||
doc = "Whether this string is empty or contains only whitespace characters.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs<ObjString>().value.isBlank())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "isEmpty",
|
||||
doc = "Whether this string is empty.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs<ObjString>().value.isEmpty())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "isNotEmpty",
|
||||
doc = "Whether this string is not empty.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs<ObjString>().value.isNotEmpty())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "matches",
|
||||
doc = "Whether this string matches the given regular expression or pattern string.",
|
||||
params = listOf(ParamDoc("pattern")),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val s = requireOnlyArg<Obj>()
|
||||
val self = thisAs<ObjString>().value
|
||||
ObjBool(
|
||||
when (s) {
|
||||
is ObjRegex -> self.matches(s.regex)
|
||||
is ObjString -> {
|
||||
if (s.value == ".*") true
|
||||
else {
|
||||
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex()
|
||||
self.matches(re)
|
||||
}
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val s = scp.requireOnlyArg<Obj>()
|
||||
val self = scp.thisAs<ObjString>().value
|
||||
return ObjBool(
|
||||
when (s) {
|
||||
is ObjRegex -> self.matches(s.regex)
|
||||
is ObjString -> {
|
||||
if (s.value == ".*") true
|
||||
else {
|
||||
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex()
|
||||
self.matches(re)
|
||||
}
|
||||
}
|
||||
|
||||
else ->
|
||||
raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String")
|
||||
else ->
|
||||
scp.raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -21,6 +21,10 @@ import net.sergeych.lyng.*
|
||||
import net.sergeych.synctools.ProtectedOp
|
||||
import net.sergeych.synctools.withLock
|
||||
|
||||
interface ModuleBuilder {
|
||||
suspend fun build(scope: ModuleScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Import manager allow to register packages with builder lambdas and act as an
|
||||
* [ImportProvider]. Note that packages _must be registered_ first with [addPackage],
|
||||
@@ -40,16 +44,16 @@ class ImportManager(
|
||||
|
||||
private inner class Entry(
|
||||
val packageName: String,
|
||||
val builder: suspend (ModuleScope) -> Unit,
|
||||
val builder: ModuleBuilder,
|
||||
var cachedScope: ModuleScope? = null
|
||||
) {
|
||||
|
||||
suspend fun getScope(pos: Pos): ModuleScope {
|
||||
cachedScope?.let { return it }
|
||||
return ModuleScope(inner, pos, packageName).apply {
|
||||
cachedScope = this
|
||||
builder(this)
|
||||
}
|
||||
val ms = ModuleScope(inner, pos, packageName)
|
||||
cachedScope = ms
|
||||
builder.build(ms)
|
||||
return ms
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +94,7 @@ class ImportManager(
|
||||
* @param name package name
|
||||
* @param builder lambda to create actual package using the given [ModuleScope]
|
||||
*/
|
||||
fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) {
|
||||
fun addPackage(name: String, builder: ModuleBuilder) {
|
||||
op.withLock {
|
||||
if (name in imports)
|
||||
throw IllegalArgumentException("Package $name already exists")
|
||||
@@ -102,7 +106,7 @@ class ImportManager(
|
||||
* Bulk [addPackage] with slightly better performance
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) {
|
||||
fun addPackages(registrationData: List<Pair<String, ModuleBuilder>>) {
|
||||
op.withLock {
|
||||
for (pp in registrationData) {
|
||||
if (pp.first in imports)
|
||||
@@ -129,9 +133,11 @@ class ImportManager(
|
||||
*/
|
||||
fun addSourcePackages(vararg sources: Source) {
|
||||
for (s in sources) {
|
||||
addPackage(s.extractPackageName()) {
|
||||
it.eval(s)
|
||||
}
|
||||
addPackage(s.extractPackageName(), object : ModuleBuilder {
|
||||
override suspend fun build(scope: ModuleScope) {
|
||||
scope.eval(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -144,7 +150,11 @@ class ImportManager(
|
||||
var source = Source("tmp", s)
|
||||
val packageName = source.extractPackageName()
|
||||
source = Source(packageName, s)
|
||||
addPackage(packageName) { it.eval(source) }
|
||||
addPackage(packageName, object : ModuleBuilder {
|
||||
override suspend fun build(scope: ModuleScope) {
|
||||
scope.eval(source)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,16 +72,16 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
||||
if (!cond) raise(message())
|
||||
}
|
||||
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: ScopeCallable): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = pos
|
||||
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||
override suspend fun execute(scope: Scope): Obj = f.call(scope)
|
||||
}
|
||||
|
||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
|
||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: ScopeCallable): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||
override suspend fun execute(scope: Scope): Obj = f.call(scope)
|
||||
}
|
||||
|
||||
object NopStatement: Statement(true, true, ObjType.Void) {
|
||||
|
||||
@@ -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.
|
||||
@@ -20,6 +20,7 @@ package net.sergeych.lynon
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjInstance
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
|
||||
open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSettings.default) {
|
||||
@@ -79,15 +80,16 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
|
||||
private suspend fun decodeClassObj(scope: Scope): ObjClass {
|
||||
val className = decodeObject(scope, ObjString.type, null) as ObjString
|
||||
return scope.get(className.value)?.value?.let {
|
||||
if (it !is ObjClass)
|
||||
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
|
||||
it
|
||||
if (it is ObjClass) return it
|
||||
if (it is ObjInstance && it.objClass.className == className.value) return it.objClass
|
||||
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
|
||||
} ?: run {
|
||||
// Use Scope API that mirrors compiler-emitted ObjRef chain for qualified identifiers
|
||||
val evaluated = scope.resolveQualifiedIdentifier(className.value)
|
||||
if (evaluated !is ObjClass)
|
||||
scope.raiseClassCastError("Expected obj class but got ${evaluated::class.simpleName}")
|
||||
evaluated
|
||||
if (evaluated is ObjClass) return evaluated
|
||||
if (evaluated is ObjInstance && evaluated.objClass.className == className.value) return evaluated.objClass
|
||||
scope.raiseClassCastError("Expected obj class but got ${evaluated::class.simpleName}")
|
||||
evaluated as ObjClass // unreachable but for compiler
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,11 @@ object ObjLynonClass : ObjClass("Lynon") {
|
||||
|
||||
init {
|
||||
addClassConst("test", ObjString("test_const"))
|
||||
addClassFn("encode") {
|
||||
encodeAny(this, requireOnlyArg<Obj>())
|
||||
addClassFn("encode") { scp ->
|
||||
encodeAny(scp, scp.requireOnlyArg<Obj>())
|
||||
}
|
||||
addClassFn("decode") {
|
||||
decodeAny(this, requireOnlyArg<Obj>())
|
||||
addClassFn("decode") { scp ->
|
||||
decodeAny(scp, scp.requireOnlyArg<Obj>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ class BindingHighlightTest {
|
||||
// Find the specific usage inside string-literal invocation: "%s is directory"(name)
|
||||
val pattern = "\"%s is directory\"(name)"
|
||||
val lineIdx = text.indexOf(pattern)
|
||||
assertTrue(lineIdx >= 0, "Pattern with string invoke should be present in the snippet")
|
||||
assertTrue(lineIdx >= 0, "Pattern with string invokeCallable should be present in the snippet")
|
||||
val nameStart = lineIdx + pattern.indexOf("name")
|
||||
val nameEnd = nameStart + "name".length
|
||||
|
||||
|
||||
@@ -528,4 +528,52 @@ class MiniAstTest {
|
||||
assertTrue(xParam.contains("first line of x"), "should contain first line")
|
||||
assertTrue(xParam.contains("second line of x"), "should contain second line")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enum_minidocs_and_semicolon_robustness() = runTest {
|
||||
val code = """
|
||||
/** Enum doc */
|
||||
enum E { A };
|
||||
|
||||
/** Next doc */
|
||||
class C {}
|
||||
""".trimIndent()
|
||||
val (_, sink) = compileWithMini(code)
|
||||
val mini = sink.build()
|
||||
assertNotNull(mini)
|
||||
|
||||
val en = mini.declarations.filterIsInstance<MiniEnumDecl>().firstOrNull { it.name == "E" }
|
||||
assertNotNull(en)
|
||||
assertNotNull(en.doc)
|
||||
assertEquals("Enum doc", en.doc.summary)
|
||||
|
||||
val cl = mini.declarations.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == "C" }
|
||||
assertNotNull(cl)
|
||||
assertNotNull(cl.doc)
|
||||
assertEquals("Next doc", cl.doc.summary)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun empty_enum_support() = runTest {
|
||||
val code = "enum E {}"
|
||||
val (_, sink) = compileWithMini(code)
|
||||
val mini = sink.build()
|
||||
assertNotNull(mini)
|
||||
assertTrue(mini.declarations.any { it.name == "E" && it is MiniEnumDecl })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun modifiers_with_comment_robustness() = runTest {
|
||||
val code = """
|
||||
class X {
|
||||
static /** doc */ fun f() {}
|
||||
}
|
||||
""".trimIndent()
|
||||
val (_, sink) = compileWithMini(code)
|
||||
val mini = sink.build()
|
||||
assertNotNull(mini)
|
||||
val cls = mini.declarations.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == "X" }
|
||||
assertNotNull(cls)
|
||||
assertTrue(cls.members.any { it.name == "f" })
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.eval
|
||||
import net.sergeych.lyng.obj.ObjInstance
|
||||
import net.sergeych.lyng.obj.ObjList
|
||||
@@ -776,4 +777,138 @@ class OOTest {
|
||||
|
||||
assertEquals("success", result.toString(), "Parameter 'id' should shadow method 'id' in block")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOverrideVisibilityRules1() = runTest {
|
||||
val scope = Script.newScope()
|
||||
scope.eval("""
|
||||
interface Base {
|
||||
abstract protected fun foo()
|
||||
|
||||
fun bar() {
|
||||
// it must see foo() as it is protected and
|
||||
// is declared here (even as abstract):
|
||||
foo()
|
||||
}
|
||||
}
|
||||
class Derived : Base {
|
||||
protected val suffix = "!"
|
||||
|
||||
private fun fooPrivateImpl() = "bar"
|
||||
|
||||
override protected fun foo() {
|
||||
// it should access own private and all protected memberes here:
|
||||
fooPrivateImpl() + suffix
|
||||
}
|
||||
}
|
||||
class Derived2: Base {
|
||||
private var value = 42
|
||||
|
||||
private fun fooPrivateImpl() = value
|
||||
|
||||
override protected fun foo() {
|
||||
fooPrivateImpl()
|
||||
value++
|
||||
}
|
||||
}
|
||||
assertEquals("bar!", Derived().bar())
|
||||
val d = Derived2()
|
||||
assertEquals(42, d.bar())
|
||||
assertEquals(43, d.bar())
|
||||
""".trimIndent())
|
||||
scope.createChildScope().eval("""
|
||||
assertEquals("bar!", Derived().bar())
|
||||
assertEquals(42, Derived2().bar())
|
||||
""".trimIndent())
|
||||
}
|
||||
@Test
|
||||
fun testOverrideVisibilityRules2() = runTest {
|
||||
val scope = Script.newScope()
|
||||
val fn = scope.eval("""
|
||||
interface Base {
|
||||
abstract fun foo()
|
||||
|
||||
fun bar() {
|
||||
// it must see foo() as it is protected and
|
||||
// is declared here (even as abstract):
|
||||
foo()
|
||||
}
|
||||
}
|
||||
class Derived : Base {
|
||||
protected val suffix = "!"
|
||||
|
||||
private fun fooPrivateImpl() = "bar"
|
||||
|
||||
override fun foo() {
|
||||
// it should access own private and all protected memberes here:
|
||||
fooPrivateImpl() + suffix
|
||||
}
|
||||
}
|
||||
class Derived2: Base {
|
||||
private var value = 42
|
||||
|
||||
private fun fooPrivateImpl() = value
|
||||
|
||||
override fun foo() {
|
||||
fooPrivateImpl()
|
||||
value++
|
||||
}
|
||||
}
|
||||
assertEquals("bar!", Derived().bar())
|
||||
val d = Derived2()
|
||||
|
||||
fun callBar() = d.bar()
|
||||
|
||||
assertEquals(42, callBar())
|
||||
assertEquals(43, callBar())
|
||||
|
||||
callBar
|
||||
""".trimIndent()) as Statement
|
||||
val s2 = Script.newScope()
|
||||
assertEquals(44L, fn.invokeCallable(scope, fn).toKotlin(s2))
|
||||
assertEquals(45L, fn.invokeCallable(s2, fn).toKotlin(s2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToStringWithTransients() = runTest {
|
||||
eval("""
|
||||
class C(amount,@Transient transient=0) {
|
||||
val l by lazy { transient + amount }
|
||||
fun lock() {
|
||||
if( transient < 10 )
|
||||
C(amount).also { it.transient = transient + 10 }
|
||||
else
|
||||
this
|
||||
}
|
||||
}
|
||||
println(C(1))
|
||||
println(C(1).lock().amount)
|
||||
println(C(1).lock().lock().amount)
|
||||
""".trimIndent())
|
||||
}
|
||||
@Test
|
||||
fun testToStringWithTransient() = runTest {
|
||||
eval("""
|
||||
class C(amount,@Transient transient=0) {
|
||||
val l by lazy { transient + amount }
|
||||
fun lock() {
|
||||
if( transient < 10 )
|
||||
C(amount).also { it.transient = transient + 10 }
|
||||
else
|
||||
this
|
||||
}
|
||||
}
|
||||
println(C(1))
|
||||
println(C(1).lock().amount)
|
||||
println(C(1).lock().lock().amount)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToJsonString() = runTest {
|
||||
eval("""
|
||||
class T(a,b,@Transient c=0)
|
||||
assertEquals("{\"a\":\"foo\",\"b\":\"bar\"}",T("foo", "bar").toJsonString())
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
@@ -138,10 +138,10 @@ class ScriptTest {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("TestIterable", ObjIterable).apply {
|
||||
addFn("iterator") {
|
||||
ObjTestIterator(thisAs<ObjTestIterable>())
|
||||
addFn("iterator") { scp ->
|
||||
ObjTestIterator(scp.thisAs<ObjTestIterable>())
|
||||
}
|
||||
addFn("cancelCount") { thisAs<ObjTestIterable>().cancelCount.toObj() }
|
||||
addFn("cancelCount") { scp -> scp.thisAs<ObjTestIterable>().cancelCount.toObj() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,10 +158,10 @@ class ScriptTest {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("TestIterator", ObjIterator).apply {
|
||||
addFn("hasNext") { thisAs<ObjTestIterator>().hasNext().toObj() }
|
||||
addFn("next") { thisAs<ObjTestIterator>().next() }
|
||||
addFn("cancelIteration") {
|
||||
thisAs<ObjTestIterator>().cancelIteration()
|
||||
addFn("hasNext") { scp -> scp.thisAs<ObjTestIterator>().hasNext().toObj() }
|
||||
addFn("next") { scp -> scp.thisAs<ObjTestIterator>().next() }
|
||||
addFn("cancelIteration") { scp ->
|
||||
scp.thisAs<ObjTestIterator>().cancelIteration()
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
@@ -2229,8 +2229,8 @@ class ScriptTest {
|
||||
@Test
|
||||
fun testThrowFromKotlin() = runTest {
|
||||
val c = Script.newScope()
|
||||
c.addFn("callThrow") {
|
||||
raiseIllegalArgument("fromKotlin")
|
||||
c.addFn("callThrow") { scp ->
|
||||
scp.raiseIllegalArgument("fromKotlin")
|
||||
}
|
||||
c.eval(
|
||||
"""
|
||||
@@ -2738,8 +2738,8 @@ class ScriptTest {
|
||||
companion object {
|
||||
|
||||
val klass = ObjClass("TestFoo").apply {
|
||||
addFn("test") {
|
||||
thisAs<ObjTestFoo>().value
|
||||
addFn("test") { scp ->
|
||||
scp.thisAs<ObjTestFoo>().value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3169,6 +3169,19 @@ class ScriptTest {
|
||||
assertEquals( n1 - now, 7.seconds )
|
||||
assertEquals( now - n1, -7.seconds )
|
||||
|
||||
val t3 = Instant("2024-01-01T12:00:00.123456Z")
|
||||
val t4 = t3.truncateToMinute
|
||||
assertEquals(t4.toRFC3339(), "2024-01-01T12:00:00Z")
|
||||
assertEquals(
|
||||
"2024-01-01T12:00:00Z",
|
||||
Instant("2024-01-01T12:00:59.999Z").truncateToMinute().toRFC3339()
|
||||
)
|
||||
|
||||
val t5 = t3.truncateToSecond
|
||||
assertEquals(t5.toRFC3339(), "2024-01-01T12:00:00Z")
|
||||
|
||||
val t6 = t3.truncateToMillisecond
|
||||
assertEquals(t6.toRFC3339(), "2024-01-01T12:00:00.123Z")
|
||||
""".trimIndent()
|
||||
)
|
||||
delay(1000)
|
||||
@@ -3209,6 +3222,137 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDateTimeComprehensive() = runTest {
|
||||
eval("""
|
||||
import lyng.time
|
||||
import lyng.serialization
|
||||
|
||||
// 1. Timezone variations
|
||||
val t1 = Instant("2024-01-01T12:00:00Z")
|
||||
|
||||
val dtZ = t1.toDateTime("Z")
|
||||
assertEquals(dtZ.timeZone, "Z")
|
||||
assertEquals(dtZ.hour, 12)
|
||||
|
||||
val dtP2 = t1.toDateTime("+02:00")
|
||||
assertEquals(dtP2.timeZone, "+02:00")
|
||||
assertEquals(dtP2.hour, 14)
|
||||
|
||||
val dtM330 = t1.toDateTime("-03:30")
|
||||
assertEquals(dtM330.timeZone, "-03:30")
|
||||
assertEquals(dtM330.hour, 8)
|
||||
assertEquals(dtM330.minute, 30)
|
||||
|
||||
// 2. RFC3339 representations
|
||||
// Note: ObjDateTime.toString() currently uses localDateTime.toString() + timeZone.toString()
|
||||
// We should verify what it actually produces.
|
||||
val s1 = dtP2.toRFC3339()
|
||||
// kotlinx-datetime LocalDateTime.toString() is ISO8601
|
||||
// TimeZone.toString() for offsets is usually the offset string itself
|
||||
println("dtP2 RFC3339: " + s1)
|
||||
|
||||
// 3. Parsing
|
||||
val t2 = Instant("2024-02-29T10:00:00+01:00")
|
||||
assertEquals(t2.toDateTime("Z").hour, 9)
|
||||
|
||||
// val dt3 = DateTime(t1, "Europe/Prague")
|
||||
// assertEquals(dt3.timeZone, "Europe/Prague")
|
||||
|
||||
// 4. Serialization (Lynon)
|
||||
val bin = Lynon.encode(dtP2)
|
||||
val dtP2_dec = Lynon.decode(bin)
|
||||
assertEquals(dtP2, dtP2_dec)
|
||||
assertEquals(dtP2_dec.hour, 14)
|
||||
assertEquals(dtP2_dec.timeZone, "+02:00")
|
||||
|
||||
// 5. Serialization (JSON)
|
||||
// val json = Lynon.toJson(dtM330) // toJson is not on Lynon yet
|
||||
// println("JSON: " + json)
|
||||
|
||||
// 6. Arithmetic edge cases
|
||||
val leapDay = Instant("2024-02-29T12:00:00Z").toDateTime("Z")
|
||||
val nextYear = leapDay.addYears(1)
|
||||
assertEquals(nextYear.year, 2025)
|
||||
assertEquals(nextYear.month, 2)
|
||||
assertEquals(nextYear.day, 28) // Normalized
|
||||
|
||||
val monthEnd = Instant("2024-01-31T12:00:00Z").toDateTime("Z")
|
||||
val nextMonth = monthEnd.addMonths(1)
|
||||
assertEquals(nextMonth.month, 2)
|
||||
assertEquals(nextMonth.day, 29) // 2024 is leap year
|
||||
|
||||
// 7. Day of week
|
||||
assertEquals(Instant("2024-01-01T12:00:00Z").toDateTime("Z").dayOfWeek, 1) // Monday
|
||||
assertEquals(Instant("2024-01-07T12:00:00Z").toDateTime("Z").dayOfWeek, 7) // Sunday
|
||||
|
||||
// 8. DateTime to/from Instant
|
||||
val inst = dtP2.toInstant()
|
||||
assertEquals(inst, t1)
|
||||
assertEquals(dtP2.toEpochSeconds(), t1.epochWholeSeconds)
|
||||
|
||||
// 9. toUTC and toTimeZone
|
||||
val dtUTC = dtP2.toUTC()
|
||||
assertEquals(dtUTC.timeZone, "UTC")
|
||||
assertEquals(dtUTC.hour, 12)
|
||||
|
||||
val dtPrague = dtUTC.toTimeZone("+01:00")
|
||||
// Equivalent to Prague winter
|
||||
assertEquals(dtPrague.hour, 13)
|
||||
|
||||
// 10. Component-based constructor
|
||||
val dtComp = DateTime(2024, 5, 20, 15, 30, 45, "+02:00")
|
||||
assertEquals(dtComp.year, 2024)
|
||||
assertEquals(dtComp.month, 5)
|
||||
assertEquals(dtComp.day, 20)
|
||||
assertEquals(dtComp.hour, 15)
|
||||
assertEquals(dtComp.minute, 30)
|
||||
assertEquals(dtComp.second, 45)
|
||||
assertEquals(dtComp.timeZone, "+02:00")
|
||||
|
||||
// 11. parseRFC3339
|
||||
val dtParsed = DateTime.parseRFC3339("2024-05-20T15:30:45+02:00")
|
||||
assertEquals(dtParsed.year, 2024)
|
||||
assertEquals(dtParsed.hour, 15)
|
||||
assertEquals(dtParsed.timeZone, "+02:00")
|
||||
|
||||
val dtParsedZ = DateTime.parseRFC3339("2024-05-20T15:30:45Z")
|
||||
assertEquals(dtParsedZ.timeZone, "Z")
|
||||
assertEquals(dtParsedZ.hour, 15)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInstantComponents() = runTest {
|
||||
eval("""
|
||||
import lyng.time
|
||||
val t1 = Instant("1970-05-06T07:11:56Z")
|
||||
val dt = t1.toDateTime("Z")
|
||||
assertEquals(dt.year, 1970)
|
||||
assertEquals(dt.month, 5)
|
||||
assertEquals(dt.day, 6)
|
||||
assertEquals(dt.hour, 7)
|
||||
assertEquals(dt.minute, 11)
|
||||
assertEquals(dt.second, 56)
|
||||
assertEquals(dt.dayOfWeek, 3) // 1970-05-06 was Wednesday
|
||||
assertEquals("1970-05-06T07:11:56Z", t1.toRFC3339())
|
||||
assertEquals("1970-05-06T07:11:56Z", t1.toSortableString())
|
||||
|
||||
val dt2 = dt.toTimeZone("+02:00")
|
||||
assertEquals(dt2.hour, 9)
|
||||
assertEquals(dt2.timeZone, "+02:00")
|
||||
|
||||
val dt3 = dt.addMonths(1)
|
||||
assertEquals(dt3.month, 6)
|
||||
assertEquals(dt3.day, 6)
|
||||
|
||||
val dt4 = dt.addYears(1)
|
||||
assertEquals(dt4.year, 1971)
|
||||
|
||||
assertEquals(dt.toInstant(), t1)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoubleImports() = runTest {
|
||||
val s = Scope.new()
|
||||
@@ -4336,7 +4480,7 @@ class ScriptTest {
|
||||
val dummyThis = Obj()
|
||||
// but we should be able to call it directly
|
||||
val otherScope = baseScope.createChildScope()
|
||||
val r = (exports["exportedFunction".toObj()] as Statement).invoke(otherScope, dummyThis, ObjInt(50))
|
||||
val r = (exports["exportedFunction".toObj()] as Statement).invokeCallable(otherScope, dummyThis, ObjInt(50))
|
||||
println(r)
|
||||
assertEquals(51, r.toInt())
|
||||
}
|
||||
@@ -4855,5 +4999,41 @@ class ScriptTest {
|
||||
assertEquals(["bar"], A().f2("bar"))
|
||||
""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClamp() = runTest {
|
||||
eval("""
|
||||
// Global clamp
|
||||
assertEquals(5, clamp(5, 0..10))
|
||||
assertEquals(0, clamp(-5, 0..10))
|
||||
assertEquals(10, clamp(15, 0..10))
|
||||
|
||||
// Extension clamp
|
||||
assertEquals(5, 5.clamp(0..10))
|
||||
assertEquals(0, (-5).clamp(0..10))
|
||||
assertEquals(10, 15.clamp(0..10))
|
||||
|
||||
// Exclusive range
|
||||
assertEquals(9, 15.clamp(0..<10))
|
||||
assertEquals(0, (-5).clamp(0..<10))
|
||||
|
||||
// Open-ended range
|
||||
assertEquals(10, 15.clamp(..10))
|
||||
assertEquals(-5, (-5).clamp(..10))
|
||||
assertEquals(5, 5.clamp(0..))
|
||||
assertEquals(0, (-5).clamp(0..))
|
||||
|
||||
// Character range
|
||||
assertEquals('e', 'e'.clamp('a'..'z'))
|
||||
assertEquals('a', ' '.clamp('a'..'z'))
|
||||
assertEquals('z', '}'.clamp('a'..'z'))
|
||||
assertEquals('y', '}'.clamp('a'..<'z'))
|
||||
|
||||
// Real numbers (boundaries are inclusive in current impl for Real)
|
||||
assertEquals(5.5, 5.5.clamp(0.0..10.0))
|
||||
assertEquals(0.0, (-1.5).clamp(0.0..10.0))
|
||||
assertEquals(10.0, 15.5.clamp(0.0..10.0))
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class DelegationTest {
|
||||
fun testFunDelegation() = runTest {
|
||||
eval("""
|
||||
class ActionDelegate() {
|
||||
fun invoke(thisRef, name, args...) {
|
||||
fun invokeCallable(thisRef, name, args...) {
|
||||
"Called %s with %d args: %s"(name, args.size, args.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.obj.ObjInstance
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import net.sergeych.lyng.obj.ObjNull
|
||||
import net.sergeych.lyng.obj.toBool
|
||||
import net.sergeych.lynon.lynonDecodeAny
|
||||
import net.sergeych.lynon.lynonEncodeAny
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class TransientTest {
|
||||
|
||||
@Test
|
||||
fun testTransient() = runTest {
|
||||
val script = """
|
||||
class TestTransient(@Transient val a, val b) {
|
||||
@Transient var c = 10
|
||||
var d = 20
|
||||
|
||||
fun check() {
|
||||
a == 1 && b == 2 && c == 10 && d == 20
|
||||
}
|
||||
}
|
||||
|
||||
val t = TestTransient(1, 2)
|
||||
t.c = 30
|
||||
t.d = 40
|
||||
t
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Scope()
|
||||
val t = scope.eval(script) as ObjInstance
|
||||
|
||||
// Check initial state
|
||||
assertEquals(1, (t.readField(scope, "a").value as ObjInt).value)
|
||||
assertEquals(2, (t.readField(scope, "b").value as ObjInt).value)
|
||||
assertEquals(30, (t.readField(scope, "c").value as ObjInt).value)
|
||||
assertEquals(40, (t.readField(scope, "d").value as ObjInt).value)
|
||||
|
||||
// Serialize
|
||||
val serialized = lynonEncodeAny(scope, t)
|
||||
println("[DEBUG_LOG] Serialized size: ${serialized.size}")
|
||||
|
||||
// Deserialized
|
||||
val t2 = lynonDecodeAny(scope, serialized) as ObjInstance
|
||||
|
||||
// b and d should be preserved
|
||||
assertEquals(2, (t2.readField(scope, "b").value as ObjInt).value)
|
||||
assertEquals(40, (t2.readField(scope, "d").value as ObjInt).value)
|
||||
|
||||
// a and c should be transient (lost or default/null)
|
||||
// For constructor args, we currently set ObjNull if transient
|
||||
assertEquals(ObjNull, t2.readField(scope, "a").value)
|
||||
// For class fields, if it's transient it's not serialized, so it gets its initial value during construction
|
||||
assertEquals(10, (t2.readField(scope, "c").value as ObjInt).value)
|
||||
|
||||
// Check JSON
|
||||
val json = t.toJson(scope).toString()
|
||||
println("[DEBUG_LOG] JSON: $json")
|
||||
assertFalse(json.contains("\"a\":"))
|
||||
assertFalse(json.contains("\"c\":"))
|
||||
assertNotNull(json.contains("\"b\":2"))
|
||||
assertNotNull(json.contains("\"d\":40"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTransientDefaultAndEquality() = runTest {
|
||||
val script = """
|
||||
class TestExt(@Transient val a = 100, val b) {
|
||||
@Transient var c = 200
|
||||
var d = 300
|
||||
}
|
||||
|
||||
val t1 = TestExt(b: 2)
|
||||
t1.c = 300
|
||||
t1.d = 400
|
||||
|
||||
val t2 = TestExt(a: 50, b: 2)
|
||||
t2.c = 500
|
||||
t2.d = 400
|
||||
|
||||
// Equality should ignore transient fields a and c
|
||||
val equal = (t1 == t2)
|
||||
|
||||
[t1, t2, equal]
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Scope()
|
||||
val result = (scope.eval(script) as net.sergeych.lyng.obj.ObjList).list
|
||||
val t1 = result[0] as ObjInstance
|
||||
val t2 = result[1] as ObjInstance
|
||||
val equal = result[2].toBool()
|
||||
|
||||
assertEquals(true, equal, "Objects should be equal despite different transient fields")
|
||||
|
||||
// Serialize t1
|
||||
val serialized = lynonEncodeAny(scope, t1)
|
||||
val t1d = lynonDecodeAny(scope, serialized) as ObjInstance
|
||||
|
||||
// a should have its default value 100, not null or 10
|
||||
assertEquals(100, (t1d.readField(scope, "a").value as ObjInt).value)
|
||||
// c should have its initial value 200
|
||||
assertEquals(200, (t1d.readField(scope, "c").value as ObjInt).value)
|
||||
// b and d should be preserved
|
||||
assertEquals(2, (t1d.readField(scope, "b").value as ObjInt).value)
|
||||
assertEquals(400, (t1d.readField(scope, "d").value as ObjInt).value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStaticTransient() = runTest {
|
||||
val script = """
|
||||
class TestStatic {
|
||||
@Transient static var x = 10
|
||||
static var y = 20
|
||||
}
|
||||
TestStatic.x = 30
|
||||
TestStatic.y = 40
|
||||
TestStatic
|
||||
""".trimIndent()
|
||||
val scope = Scope()
|
||||
scope.eval(script)
|
||||
// Static fields aren't serialized yet, but we ensure the parser accepts it
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTransientSize() = runTest {
|
||||
val script = """
|
||||
class Data1(val a, val b) {
|
||||
var c = 30
|
||||
}
|
||||
class Data2(val a, val b, @Transient val x) {
|
||||
var c = 30
|
||||
@Transient var y = 40
|
||||
}
|
||||
|
||||
val d1 = Data1(10, 20)
|
||||
val d2 = Data2(10, 20, 100)
|
||||
d2.y = 200
|
||||
|
||||
[d1, d2]
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Scope()
|
||||
val result = (scope.eval(script) as net.sergeych.lyng.obj.ObjList).list
|
||||
val d1 = result[0] as ObjInstance
|
||||
val d2 = result[1] as ObjInstance
|
||||
|
||||
val s1 = lynonEncodeAny(scope, d1)
|
||||
val s2 = lynonEncodeAny(scope, d2)
|
||||
|
||||
println("[DEBUG_LOG] Data1 size: ${s1.size}")
|
||||
println("[DEBUG_LOG] Data2 size: ${s2.size}")
|
||||
|
||||
assertEquals(s1.size, s2.size, "Serialized sizes should match because transient fields are not serialized")
|
||||
|
||||
val j1 = d1.toJson(scope).toString()
|
||||
val j2 = d2.toJson(scope).toString()
|
||||
|
||||
println("[DEBUG_LOG] Data1 JSON: $j1")
|
||||
println("[DEBUG_LOG] Data2 JSON: $j2")
|
||||
|
||||
assertEquals(j1.length, j2.length, "JSON lengths should match")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testObjectTransient() = runTest {
|
||||
val script = """
|
||||
object MyObject {
|
||||
@Transient var temp = 10
|
||||
var persistent = 20
|
||||
}
|
||||
MyObject.temp = 30
|
||||
MyObject.persistent = 40
|
||||
MyObject
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Scope()
|
||||
val obj = scope.eval(script) as ObjInstance
|
||||
|
||||
val serialized = lynonEncodeAny(scope, obj)
|
||||
val deserialized = lynonDecodeAny(scope, serialized) as ObjInstance
|
||||
|
||||
// persistent should be 40
|
||||
assertEquals(40, (deserialized.readField(scope, "persistent").value as ObjInt).value)
|
||||
// temp should be restored to 10
|
||||
assertEquals(10, (deserialized.readField(scope, "temp").value as ObjInt).value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStaticTransientToJson() = runTest {
|
||||
val script = """
|
||||
class TestStatic {
|
||||
@Transient static var s1 = 10
|
||||
static var s2 = 20
|
||||
private static var s3 = 30
|
||||
}
|
||||
TestStatic
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Scope()
|
||||
val cls = scope.eval(script) as net.sergeych.lyng.obj.ObjClass
|
||||
|
||||
val json = cls.toJson(scope).toString()
|
||||
println("[DEBUG_LOG] Class JSON: $json")
|
||||
|
||||
// s2 should be in JSON
|
||||
assertNotNull(json.contains("\"s2\":20"))
|
||||
// s1 should NOT be in JSON (transient)
|
||||
assertFalse(json.contains("\"s1\":"))
|
||||
// s3 should NOT be in JSON (private)
|
||||
assertFalse(json.contains("\"s3\":"))
|
||||
// __class_name should be there
|
||||
assertNotNull(json.contains("\"__class_name\":\"TestStatic\""))
|
||||
|
||||
// Test serialization/deserialization of the class itself
|
||||
val serialized = lynonEncodeAny(scope, cls)
|
||||
val deserialized = lynonDecodeAny(scope, serialized) as net.sergeych.lyng.obj.ObjClass
|
||||
assertEquals(cls, deserialized)
|
||||
}
|
||||
}
|
||||
@@ -882,6 +882,22 @@ class LyngFormatterTest {
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val cfg = LyngFormatConfig(indentSize = 4, continuationIndentSize = 4)
|
||||
val out = LyngFormatter.reindent(src, cfg)
|
||||
assertEquals(expected, out)
|
||||
}
|
||||
@Test
|
||||
fun multlinedShortFunIndentation() {
|
||||
val src = """
|
||||
static fun create(walletId, trId, amount) =
|
||||
Cells.createNew(Commission(amount) => ["commission", "w:" + walletId, "tr:" + trId, createdTag(), "pending"])
|
||||
""".trimIndent()
|
||||
|
||||
val expected = """
|
||||
static fun create(walletId, trId, amount) =
|
||||
Cells.createNew(Commission(amount) => ["commission", "w:" + walletId, "tr:" + trId, createdTag(), "pending"])
|
||||
""".trimIndent()
|
||||
|
||||
val cfg = LyngFormatConfig(indentSize = 4, continuationIndentSize = 4)
|
||||
val out = LyngFormatter.reindent(src, cfg)
|
||||
assertEquals(expected, out)
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object PerfDefaults {
|
||||
actual val LOCAL_SLOT_PIC: Boolean = true
|
||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = true
|
||||
actual val LOCAL_SLOT_PIC: Boolean = false
|
||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = false
|
||||
|
||||
actual val ARG_BUILDER: Boolean = true
|
||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||
actual val SCOPE_POOL: Boolean = true
|
||||
actual val ARG_BUILDER: Boolean = false
|
||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = false
|
||||
actual val SCOPE_POOL: Boolean = false
|
||||
|
||||
actual val FIELD_PIC: Boolean = true
|
||||
actual val METHOD_PIC: Boolean = true
|
||||
actual val FIELD_PIC: Boolean = false
|
||||
actual val METHOD_PIC: Boolean = false
|
||||
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 = "<pre><code>" + htmlEscape(code) + "</code></pre>"
|
||||
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 = "<pre><code>" + htmlEscape(slideCode) + "</code></pre>"
|
||||
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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user